-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Adding New Charts
##Getting started
Let's create a new script file for our chart (e.g. chart.js
) within the charts/
folder and include it in the index.html
:
<script src="charts/chart.js"></script>
While it is not strictly necessary, it is always better to wrap the whole chart code within a self-executing function, in order to isolate the scope and do not worry about other scripts' variable names.
(function(){
// your code here...
})();
##Creating the Model
The first step to add a new chart in RAW is defining which model the chart should use for the data. This is due to the fact that RAW works with tabular data, while many D3 layouts requires specific data structures (e.g. hierarchies for Bubble Charts or nodes-links for Force-directed Graphs). It becomes necessary then to transform the data records into the appropriate structure our chart works with. In this way users will be able to specify which columns they want to use - and how they want to use them - to construct the appropriate data structure.
Let's use a concrete example. We want to create a simple scatter plot chart to display two numeric dimensions in the dataset using Cartesian coordinates. Since each point in the scatter plot is defined by two coordinates, our chart will expect an array of objects containing an x
and an y
value, for each record in our dataset. Something like this:
[
{ x: 0.1234, y: 56789 },
{ x: 1.4321, y: 76895 },
...
]
###Dimensions, or Letting Users Control Data Transformation
We need to define a new model that allows the creation of the points array, starting from the data set. First of all, we need to construct the model:
var model = raw.model();
Then, we need to define the dimensions we want to expose to the user for the creation of the structure. For each dimension we define, RAW will create a GUI component to let the user associate one or more columns to that dimension.
As we have seen, for our scatter plot points, we need to define an x
and y
numeric dimension. Let's start with the x
and create the dimension:
var x = model.dimension();
Adding a title is necessary to let the users know what the dimension is for:
x.title('X Axis');
Since the scatterplot works with numeric dimensions, we need to tell the users to associate this dimension only to numeric columns in their data. We do that by specifying its types:
x.types(Number);
Please, note that types are indicated using the native JavaScript class. Currently, the following data types are available: Number
, String
, Date
.
See the API Reference for more information.
However, since models allow function chaining, we could write the previous chunks of code in this way too:
var x = model.dimension()
.title("X Axis")
.types(Number);
Defining the y
dimension is pretty similar:
var y = model.dimension()
.title("Y Axis")
.types(Number);
###Map Function
Now that we have defined the dimensions of our model, we need to define the actual function to transform the data into the objects we want. We define this transformation within model's map
function:
model.map(function(data) {
return data.map(function(d) {
return {
x : +x(d),
y : +y(d)
}
})
})
The function we define for model.map
will receive the whole data set and its returning value will be passed directly to the chart. As you can see, in our case this function is pretty simple: for each object in the data, it creates a new object with the two properties x
and y
we need for our scatterplot. The values of those properties will depend on the columns the user associated to those dimensions (throught the GUI).
Dimensions work as accessors, when we pass them an object they return the value (of the column they are associated) for that object.
##The Chart
var chart = raw.chart()
.title("Simple Scatter Plot")
.model(model)
###Options, or Letting Users Customize the Visualization
Similarly to the dimensions in the model, options define those variables that we want to expose to the users to allow them to customize the visualization. But instead of controlling data transformation, chart options control visual properties of the chart (e.g. width, height, radius, paddings, colors, ...). Any time we want to let the user control one of these aspect we can create an option. Similarly to dimensions, RAW will create an appropriate GUI element to let the user control that property.
In our case, we want the user to control the size of the chart (i.e. width and height) as well as its margins. Let's start with the width..
First of all we need to construct the options:
var width = chart.number();
Since the chart width is a numeric value, we need to control it through a number option. Currently, RAW allows the creation of the following option types: numbers, checkbox, lists, colors and text. Each of these options provides its own methods, so please refer to API reference for more details about them.
We provide a title to indicate what this option is about:
width.title('Width')
And an initial value:
width.defaultValue(900)
As before, we can write the same code in this way:
var width = chart.number()
.title('Width')
.defaultValue(900)
Similarly, we create an option for both the height
var height = chart.number()
.title('Height')
.defaultValue(600)
and the margin
var margin = chart.number()
.title('Margin')
.defaultValue(10)
###Drawing Function
Finally, we need to define the chart, using the draw
function. This function is called passing two arguments: the selection and the data. The selection represents the svg element (as a common D3 selection) where the visualization will appear in RAW, while the data is the data structure resulting from running the model on the original records:
chart.draw(function(selection,data) {
// selection is the svg element
// data is the data structure resulting from the application of the model
// generate chart here...
})
Important! This function will be called any time the users change an option value. The svg will be cleared everytime before calling this function. In this way, D3's enter-update-exit pattern does not make much sense within RAW's charts, since the selection is always empty when passed to the draw function. Since RAW is meant to be a tool for the production of non-interactive visualizations, to be furtherly elaborated using vector-graphics tools, this should not be perceived as a limitation, but, at the contrary, as a way to simplify charts' drawing code.
The first thing we do within our drawing function is to set the size of the svg using the chart options - width
and height
- we have defined above:
selection
.attr("width", width())
.attr("height", height())
As you can see, to get the value of each option we simply call it. The value will correspond to the one set by the user thorugh the GUI element connected to it. The type of value can vary according to the type of option. In this case, since the two options are numeric ones, the returned values will be numbers.
Our scatter plot will need two linear scales, to properly scale the x
and y
values of the points to our svg size. To calculate the domain of the scales we will use the d3.max function on the array of points, specifying which key to use (let's assuming all the values will be positive and thus having 0 as the minimum value).
Since we want to allow the user define a margin
for our visualization, we will include that into the scales:
var xScale = d3.scale.linear()
.domain([0, d3.max(data, function (d){ return d.x; })])
.range([margin(), width()-margin()]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d){ return d.y; })])
.range([height()-margin(), margin()]);
Now we can draw the points, by using simple svg circle elements:
selection.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", 5)
Summing up, our draw function will look like this:
chart.draw(function (selection, data){
// svg size
selection
.attr("width", width())
.attr("height", height())
// x and y scale
var xScale = d3.scale.linear()
.domain([0, d3.max(data, function (d){ return d.x; })])
.range([margin(), width()-margin()]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d){ return d.y; })])
.range([height()-margin(), margin()]);
// let's plot the dots
selection.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", 5)
})
You can find the code for this chart here.