Interactive area chart using Protovis - javascript

This is quite a daunting project to a Protovis newcomer, but maybe you could help me split it into digestible chunks?
What I would like to build is an "interactive Area chart", as sketched here:
First of all, it's the data ...
I have data for provinces in Excel:
Province Year 10 100 1000 10000
A 1970 2 4 6 3
A 1971 3 6 8 5
B 1970 6 9 12 6
B 1971 4 8 11 8
.... ... . . . .
For each province and year, I would like to be able to draw an area chart:
vis.add(pv.Area)
.data(data.ProvinceA[1970])
.bottom(1)
.interpolate("basis")
.left(function(d) x(d.x))
.height(function(d) y(d.y))
.fillStyle("rgb(21,173,210)")
.anchor("top").add(pv.Line)
.lineWidth(3);
Then I would like to add 2 types of interactivity:
Selection of Province
Time slider
Together, the selection checkboxes and the time slider determine which areas are visible at any given time.
If, for example, Province A is selected and the year is 1984, only that area is displayed. If the time slider is now dragged, the corresponding years are now displayed for Province A. If another Province is checked, the areas are overlayed and both areas are redrawn when the time slider moves.
Protovis questions:
How do I format the data (province, year, x, y) for this application?
How do I achieve the binding of checkboxes to area?
How do I implement the time slider? Within Protovis or like an external component with listeners that trigger re-rendering of the graph?

Formatting data: The first step is to get it into JSON, using some external tool (I really like Google Refine for this, though it's a pretty big tool if this is all you need it for - try Mr. Data Converter for a quick and dirty option). These tools will probably give you data as a JSON object, like this:
`[{"Province":"A", "Year":"1970", "10":2, "100":4, "1000":6, "10000":3}, ...]`
Once you have the data available as JSON, you'll want to get it into shape for your vis. You're going to want to pass each pv.Area an array of values - from your description it looks like you want the [10, 100, 1000, 10000] values. Protovis has a lot of tools for manipulating data - see the pv.Nest operator. There are lots of ways you might approach this - I might do this:
data = pv.nest(data)
.key(function(x) {return x.Province})
.key(function(x) {return x.Year})
.rollup(function(v) {
return [v[0]['10'], v[0]['100'], v[0]['1000'], v[0]['10000']];
});
which gives you an object like:
{
A: {
1970: [2,4,6,3]
// ...
},
// ...
}
This sets you up for the interface elements. Keep the array of checked Provinces and the current year in global variables:
var currentProvinces = ['A', 'B', ...];
var currentYear = 1970;
and set up your area to reference those variables:
// a containing panel to help with layout and data
var panel = vis.add(pv.Panel)
.data(function() currentProvinces); // making this a function allows it to
// be re-evaluated later
// the area itself
var area = panel.add(pv.Area)
.data(function(province) data[province][currentYear]);
// plus more area settings as needed
Now use some other library - I'm partial to jQuery, with jQuery UI for the slider - to create your interface elements. The onchange function for each element just needs to set the corresponding global variable and call vis.render() (assuming your root panel is called vis). This should be pretty simple - see here for a Protovis example using jQuery UI to make a time slider very similar to what you have in mind.

I think that you are trying to make that pair of charts:
http://mbostock.github.io/protovis/ex/zoom.html

Related

D3.js: Trying to build flat calendar

Newbie here.I am trying to build a horizontal calendar to indicate people on vacations. I have placed the mockup below with annotation of key feature, I'm trying to incorporate.
Legend Y - Indicates the person
Legend X - Indicates the Month and year.
Each cell is color coded based on holiday or vacation or weekend.
The calendar part (not including legend area) should be draggable.
I have got rough implementation going...I am able to get the first horizontal calendar,
Code: http://jsbin.com/xumabavo/1/edit
Is my approach correct?
how to stack each other?
How to enable dragging and render dates before and after the window?
Thanks for helping out.
Is my approach correct?
Yes. Mockup is crucial. Prototype as well. It looks to me that you have confidence, determination, and intelligence necessary for this project.
How to stack each other?
I gather you will get overall data as an array of individual data specific for particular person. You can use index of that array (index corresponds to a person) to shift all visual elements to the appropriate row - in other words, to stack the data. Callback functions can have two parameters d (data) and i (index), something like this:
.style( "background-color", function( d, i ) {
// d is datum being rendered
// i is datum's index in dataset
// return value based on logic
} )
How to enable dragging and render dates before and after the window?
It seems to me you would benefit from analyzing this small app: link

d3.v3 scatterplot with all circles the same radius

Every example I have found shows all of the scatter plot points to be of random radii. Is it possible to have them all the same size? If I try to statically set the radius all of the circles will be very small (I'm assuming the default radius). However, if I use Math.random() as in most examples there are circles large and small. I want all the circles to be large. Is there a way to do that? Here's the code snippet forming the graph data using Math.random() (this works fine for some reason):
function scatterData(xData, yData)
{
var data = [];
for (var i = 0; i < seismoNames.length; i++)
{
data.push({
key: seismoNames[i],
values: []
});
var xVals=""+xData[i];
xVals=xVals.split(",");
var yVals=""+yData[i];
yVals=yVals.split(",");
for (var j = 0; j < xVals.length; j++)
{
data[i].values.push({
x: xVals[j],
y: yVals[j],
size: Math.random()
});
}
}
return data;
}
Math.random() spits out values between 0 and 1 such as 0.164259538891095 and 0.9842195005008699. I have tried putting these as static values in the 'size' attribute, but no matter what the circles are always really small. Is there something I'm missing?
Update: The NVD3 API has changed, and now uses pointSize, pointSizeDomain, etc. instead of just size. The rest of the logic for exploring the current API without complete documentation still applies.
For NVD3 charts, the idea is that all adjustments you make can be done by calling methods on the chart function itself (or its public components) before calling that function to draw the chart in a specific container element.
For example, in the example you linked too, the chart function was initialized like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range());
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
The .showDistX() and .showDistY() turn on the tick-mark distribution in the axes; .color() sets the series of colours you want to use for the different categories. The next too lines access the default axis objects within the chart and set the number format to be a two-digit decimal. You can play around with these options by clicking on the scatterplot option from the "Live Code" page.
Unfortunately, the makers of the NVD3 charts don't have a complete documentation available yet describing all the other options you can set for each chart. However, you can use the javascript itself to let you find out what methods are available.
Inspecting a NVD3.js chart object to determine options
Open up a web page that loads the d3 and nvd3 library. The live code page on their website works fine. Then open up your developer's console command line (this will depend on your browser, search your help pages if you don't know how yet). Now, create a new nvd3 scatter chart function in memory:
var testChart = nv.models.scatterChart();
On my (Chrome) console, the console will then print out the entire contents of the function you just created. It is interesting, but very long and difficult to interpret at a glance. And most of the code is encapsulated so you can't change it easily. You want to know which properties you can change. So run this code in the next line of your console:
for (keyname in testChart){console.log(keyname + " (" + typeof(testChart[keyname]) + ")");}
The console should now print out neatly the names of all the methods and objects that you can access from that chart function. Some of these will have their own methods and objects you can access; discover what they are by running the same routine, but replacing the testChart with testChart.propertyName, like this:
for (keyname in testChart.xAxis){console.log(keyname + " (" + typeof(testChart.xAxis[keyname]) + ")");}
Back to your problem. The little routine I suggested above doesn't sort the property names in any order, but skimming through the list you should see three options that relate to size (which was the data variable that the examples were using to set radius)
size (function)
sizeDomain (function)
sizeRange (function)
Domain and range are terms used by D3 scales, so that gives me a hint about what they do. Since you don't want to scale the dots, let's start by looking at just the size property. If you type the following in the console:
testChart.size
It should print back the code for that function. It's not terribly informative for what we're interested in, but it does show me that NVD3 follows D3's getter/setter format: if you call .property(value) you set the property to that value, but if you call .property() without any parameters, it will return back the current value of that property.
So to find out what the size property is by default, call the size() method with no parameters:
testChart.size()
It should print out function (d) { return d.size || 1}, which tells us that the default value is a function that looks for a size property in the data, and if it doesn't exist returns the constant 1. More generally, it tells us that the value set by the size method determines how the chart gets the size value from the data. The default should give a constant size if your data has no d.size property, but for good measure you should call chart.size(1); in your initialization code to tell the chart function not to bother trying to determine size from the data and just use a constant value.
Going back to the live code scatterplot can test that out. Edit the code to add in the size call, like this:
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.color(d3.scale.category10().range())
.size(1);
chart.xAxis.tickFormat(d3.format('.02f'));
chart.yAxis.tickFormat(d3.format('.02f'));
Adding that extra call successfully sets all the dots to the same size -- but that size is definitely not 1 pixel, so clearly there is some scaling going on.
First guess for getting bigger dots would be to change chart.size(1) to chart.size(100). Nothing changes, however. The default scale is clearly calculating it's domain based on the data and then outputting to a standard range of sizes. This is why you couldn't get big circles by setting the size value of every data element to 0.99, even if that would create a big circle when some of the data was 0.01 and some was 0.99. Clearly, if you want to change the output size, you're going to have to set the .sizeRange() property on the chart, too.
Calling testChart.sizeRange() in the console to find out the default isn't very informative: the default value is null (nonexistent). So I just made a guess that, same as the D3 linear scale .range() function, the expected input is a two-element array consisting of the max and min values. Since we want a constant, the max and min will be the same. So in the live code I change:
.size(1);
to
.size(1).sizeRange([50,50]);
Now something's happening! But the dots are still pretty small: definitely not 50 pixels in radius, it looks closer to 50 square pixels in area. Having size computed based on the area makes sense when sizing from the data, but that means that to set a constant size you'll need to figure out the approximate area you want: values up to 200 look alright on the example, but the value you choose will depend on the size of your graph and how close your data points are to each other.
--ABR
P.S. I added the NVD3.js tag to your question; be sure to use it as your main tag in the future when asking questions about the NVD3 chart functions.
The radius is measured in pixels. If you set it to a value less than one, yes, you will have a very small circle. Most of the examples that use random numbers also use a scaling factor.
If you want all the circles to have a constant radius you don't need to set the value in the data, just set it when you add the radius attribute.
Not sure which tutorials you were looking at, but start here: https://github.com/mbostock/d3/wiki/Tutorials
The example "Three little circles" does a good step-by-step of the different things you can do with circles:
http://mbostock.github.io/d3/tutorial/circle.html

d3 line transition

A line chart displays a value over a given period of time (e.g. the last 2 days), one value per day. Users can change this time horizon (to e.g. the last four days).
Problem: the line transitions are really ugly. I figure the problem is the change in data:
state 1: => state 2:
days value days value
-------------- ---------------
today-2 5 today-4 3
today-1 8 today-3 9
today-2 5
today-1 8
In above case, for example, the former first data point with a value of 5 now transitions to the value of 3 and is shifted left to today-4 on the x-axis. What I would like to have is that 5 and 8 shifted to the right and remained at their values of 5 and 8, while the two new data points enter the stage from 0. Hope you can imagine what I mean.. if not have a look at this image that shows the current state of tragedy ( transition from 1 week => 4 weeks ):
Now, I know that when attaching the new data, a key can be assigned ( e.g. in this example, the key would be the date of the value) and I've got that working for circles (that are hidden in this chart and only visible when you mouse-over a value). These circles transition perfectly. Unfortunatly, I could not get this working for the lines here because of the way I structured my charts, I guess:
each chart is built by an "init_chartX" function that initializes an
"empty" chart (e.g. append a path for line)
each chart is updated by an "update_chartX" function.
So in the init function, I set up the line:
// Add paths for line1 and line2
svg.append("g")
.append("path")
.attr("class", "line1");
And when I try to add the data with key in the update function..
svg.selectAll(".line1").data(data, function (d) { return formatDate(d.date); })
..the result is an exception because "d" is not defined. I assume this is because of the setup of a ".line1" element in the init function, it works just fine when used on circles that are not setup in the init function:
var dots1 = svg.selectAll(".circle1").data(data, function (d) { return formatDate(d.date); });
dots1.enter().insert("circle")
.attr("class", "circle1");
The circles are not setup in the initfunction, they are just added on the fly. For the line on the other hand, I could not figure out how to get this done.
The answer to my question could be a link or some usefull tip..I've seen the Path Transition pages of Mike Bostock already, also the general update pattern tutorials.. maybe I've been blind there. Thanks for any help!
Wow, finally found this. Alright, no key-binding lines. That will make smooth transitions for changing data with different length a bit harder but I'm glad I finally know why I kept getting an error ..
EDIT
I found a solution: reverse the data before attaching it to the lines. So my example from above becomes:
state 1: => state 2:
days value days value
-------------- ---------------
today-1 8 today-1 8
today-2 5 today-2 5
today-3 9
today-4 3
and the transition works as supposed.. So easy, stupid me I didnt figure that out earlier. (the x-axis is not effected by that, it is still growing in time to the right)

d3js set tickValues for time scale axis

I've searched in the official d3.js documentation, as well as, here in stackoverflow to find a way to add custom tickValues to a time scale axis; However, i haven't stumble across any documentation that confirms that something like that is possible.
So in essence, i have a time scale axis and i would like to show specific hours
e.g. i'd like to do something like this :
xHourAxis
.ticks(d3.time.hours, 2)
.tickFormat(d3.time.format('%I %p'))
.tickValues(2, 4, 6, 8, 10, 12) ;
So i want to display tick values every 2 hours, but not including the first (12 am) and the last (12 pm).
Does anyone know if there is any workaround for that?
Nearly there, but your code has two problems: First the tick values must be specified in an array, and second those values should be Javascript date objects. i.e. you just provide an array of dates to tickValues so your code would looks something like this:
xHourAxis
.tickFormat(d3.time.format('%I %p'))
.tickValues([new Date(2000,10,5), new Date(2005,2,7), new Date(2007,11,11)]);
Also, note that you needn't call the ticks() if you are going to later specify custom values.

jQuery Flot Tick/Date Alignment

Code
Example of my problem: http://jsfiddle.net/x46RQ/
Goal
I want the graph to be a bar graph like so: http://jsfiddle.net/Lbd85/ but obviously with dates as the x axis. If I add my data into that fiddle, it messes up like the one listed above as seen here: http://jsfiddle.net/73G7Z/
Questions
Why are all 3 days provided in the data variable not displaying?
Why are the bars not lined up with their appropriate x-axis ticks?
Why does changing the data and mode to time totally mess up what would otherwise be a functional and accurate bar graph?
Environment
jQuery 1.7.1
jQuery Mobile 1.0.1
Flot 0.7
Thanks
Let me know if any additional information is required.
Part #1, You specified a min y value of 0 in your flot options, and your data point #2 has a value of zero. So it's there but just very small, almost invisible.
Part #2, you have to offset your dates by the users timezone:
Something like this:
var tzOffset = new Date();
tzOffset = tzOffset.getTimezoneOffset()*60*1000;
data.push([(new Date("2012/02/20").getTime()-tzOffset), 1]);
Part #3, Your graph is a mess because you specified a width when in fact the option you were looking for is barWidth and you need to specify the width in terms of time, i.e. milliseconds. See here for how. Something like barWidth: 12*60*60*1000 looks OK.
So in summary, this is what it will look like: http://jsfiddle.net/ncTd3/

Categories

Resources