A domain-specific language that transpiles to d3.js
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
data-monster
docs
examples
img
resources
.gitignore
README.md

README.md

data monster logo

Data Monster

What is Data Monster?

Data Monster is a domain-specific language that transpiles to d3.js files (plus some helper HTML and CSS files, if necessary).

This is its alpha.

To draw something like this:

scatterplot

you use input like this:


(data: 'van_gogh_additional_measurements.tsv'
  (clean: #{ d.Shape_Count = +d.Shape_Count,
             d.ratio = +d["Image_Height/Image_Width "]                    
            }
  )
  (canvas: 1000 600 {20 20 60 60} '#scatterplot'
    (color: category10)
    (scale-x: linear 
              domain: { 0 maxX } 
              range:  { 0 width } ) 
    (elem: circle: { cx: d.ratio, cy: d.Shape_Count, r: 4, fill: d.Year }
           attr: { 'class': 'dot' }
           tooltips: true
           click: #{ function(d) { window.open('https://www.google.com/search?site=imghp&tbm=isch&q=van+gogh+'+d.Title); }})
    (axis-x: attr: { 'class': 'label', 'x': width, 'y': 50 }
             style: { 'text-anchor': 'end' }
             text: 'Height: Width Ratio' )
    (axis-y: attr: { 'class': 'label', 'y': -10 }
             style: { 'text-anchor': 'end' }
             text: 'Num Shapes' )
  )
)

to generate output like this:

function draw_canvas_2e16a1dd(data) {
    var margin = {
            top: 20,
            right: 20,
            bottom: 60,
            left: 60
        },
        width = 920,
        height = 520;
    var maxY = d3.max(data, function(d) {
            return d.Shape_Count
        }),
        maxX = d3.max(data, function(d) {
            return d.ratio
        });
    maxY = maxY + (maxY * .25) // Make it a little taller 
    var xScale = d3.scale.linear()
        .domain([0, maxX])
        .range([0, width]),
        yScale = d3.scale.linear()
        .domain([0, maxY])
        .range([height, 0]),
        color = d3.scale.category10();
    var svg = d3.select('#scatterplot')
        .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
    svg.selectAll('circle')
        .data(data)
        .enter()
        .append('g')
        .attr('class', 'elements')
        .append('circle')
        .attr({
            cx: function(d) {
                return xScale(d.ratio)
            },
            cy: function(d) {
                return yScale(d.Shape_Count)
            },
            r: 4,
            fill: function(d) {
                return color(d.Year)
            }
        })
        .attr({
            "class": "dot"
        })
        .on('mouseover', function(d) {
            var xPosition = event.clientX + scrollX < width - 200 ? event.clientX + scrollX : event.clientX + scrollX - 200,
                yPosition = event.clientY + scrollY + 100 > height ? event.clientY + scrollY - 25 : event.clientY + scrollY + 5,
                text = d.ratio + '; ' + d.Shape_Count;
            d3.select('#tooltip')
                .style('left', xPosition + 'px')
                .style('top', yPosition + 'px')
                .select('#values')
                .text(text);
            d3.select('#tooltip')
                .classed('hidden', false);
        })
        .on('mouseout', function() {
            d3.select('#tooltip')
                .classed('hidden', true);
        })
        .on('click', function(d) {
            window.open('https://www.google.com/search?site=imghp&tbm=isch&q=van+gogh+' + d.Title);
        });
    var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom');
    svg.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis)
        .append('text')
        .attr({
            "class": "label",
            "x": 920,
            "y": 50
        })
        .style({
            "text-anchor": "end"
        })
        .text("Height: Width Ratio");
    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');
    svg.append('g')
        .attr('class', 'y axis')
        .call(yAxis)
        .append('text')
        .attr({
            "class": "label",
            "y": -10
        })
        .style({
            "text-anchor": "end"
        })
        .text("Num Shapes");
};

function draw_data_63dc8d12(rawData) {
    rawData.forEach(function(d) {
        return d.Shape_Count = +d.Shape_Count, d.ratio = +d["Image_Height/Image_Width "]
    });
    draw_canvas_2e16a1dd(rawData);
}

queue()
    .defer(d3.tsv, 'van_gogh_additional_measurements.tsv')
    .await(function(err, data) {
        if (err) {
            console.log(err);
        }
        draw_data_63dc8d12(data);
    });

and then you can use it like any other d3.js file you have written.

Why would I use this?

Because you love monsters. And data. And seek to find its secrets by imbibing it.

Alternatively, you might be making some charts and not want to type a whole bunch of d3.js. You might be someone who starts chart projects by copy-pasting from an example or a previous chart. You might just want to throw up a prototype real fast to see what you can see.

While you can get a basic chart from the Data Monster without knowing d3, in general the language assumes you know something about the library. The Monster's goal is less to create a beginner-friendly interface than to create a shorthand for folks who have done d3 before and expect to do so again.

Data Monster is also meant to be useful to folks using the library to create SVG drawings that may not be data-driven charts. So, while there are some defaults set up to make chart generation easier, Data Monster can be used to create an SVG and deploy your own functions in a d3 framework.

How do I monster?

Installing

Eventually this will be up on NPM, but till then there are a few steps.

  1. Clone data-monster repo or download the files in the data-monster folder
  2. Manually add them to node_modules directory
  3. Require data-monster in your package.json
"dependencies": {
    "data-monster": "0.0.1" }
  1. Data Monster can be installed locally or globally. If you install it globally, it is a little easier to call, but whatever makes you happy.
Global
npm install -g node_modules/data-monster
Local
npm install node_modules/data-monster

Now you're ready to go!

Chomping (aka compiling) the file

If you installed data monster globally, you can run

chomp <filename0> ... <filenameN> [optional output directory] 

in any directory with a .dm file.

Otherwise, with a local install, you can

npm run chomp <.dm files>

In both cases, the -a flag can also be used to chomp all the files in the current directory.

Dependencies

Data Monster–generated files use the queue library for async file loading, so you will need to include it in your dependencies.

Writing a Chompable File

The Data Monster syntax is LISP-inspired, with parentheses used to denote the scope of an expression. At the same time, it stays honest to the Javascript it will eventually become.

I've roughed out the basic structure below; check out chomp me for full details or look at the examples folder.

A Few Basics

The only essential unit of a DM file is the canvas block, specified:

(canvas: <width> <height> <appendToElement>)

which takes two numbers plus the string used to select the DOM element to which the SVG will be appended.

One or many canvases will usually be wrapped in a data element:

(data: <filepath> 
	(canvas: <width> <height> <appendToElement>)
	(canvas: <width> <height> <appendToElement>)
	(canvas: <width> <height> <appendToElement>))

A DM file can contain as many of these blocks as desired.

Canvases themselves will hold elems — specifications for SVG elements, like lines, circles, etc.

(canvas: <width> <height> <appendToElement>
	(elem: <element>: { <required attributes> }))

The required attributes is a hash map that contains values required by SVG to draw the element.

Data monster includes some special expressions for common chart elements, like axes, scales and tooltips. These too are attached to the canvas.

Finally, the workhorse canvas also accepts other d3 methods you would like to call on the SVG.

Both data and canvas enclosures also accept function statements, which add plain Javascript functions into the generated file. They are specified:

(funcs: #{<function1>} ... #{<functionN>})

For more details, check out chomp me or look at the examples folder.

Why would you build this?

Entertainment & laziness, basically.

It all started in Radical Computer Science, a class at the School for Poetic Computation where we learned to build our own LISP interpreters with plt.js and peg.js. Now that I am at Hacker School (recently renamed the Recurse Center), I have the time to build up the Monster as my own language. Target: d3. But why d3?

Thanks to the way most people learned d3.js — copying examples they liked and substituting in their own data — d3 is highly idiomatic; that is, most people write their charts similarly, even after they move past copy-paste.

At the same time, one of d3's great virtues — it's flexibility — means it can take a lot of code to write a chart.

So Data Monster was born to generate the necessary d3 with as little specification from the user as possible — but without sacrificing flexibility.

Feedback, the Future + Contributing

That is a big goal and this is a little alpha; lots of things are broken or maybe could be better implemented.

I plan to continue adding functionality to the Monster for the foreseeable future and am interested in both general feedback and specific requests.

Please open an issue, shoot me an email at hi@sarahgp.com, or check out the contributing doc.

License

MIT