1

I'm trying to do some re-factoring on my charts to make them re-usable using this as a guide: http://bost.ocks.org/mike/chart/

I'm having problems drawing the lines in my multi-line graph though - specifically passing the data to the x and y values. If I hard code the element names it works, but if I try to use the xValue and yValue objects this does not work. I'm assuming that this is because I'm trying to call a function within the parameter of an other object, but I'm not sure how to get around this. In the exmaple Mike uses d[0] and d[1], but this won't work with JSON data (or I'm not sure how to make it work).

I've posted this JSFiddle so you can see the code. The problem lines are 125 to 131 which in turn is being called from line 165.

var main_line = d3.svg.line()
    .interpolate("cardinal")

    // Hard coding the elements works
    //.x(function(d) { return main_x(d.date); })
    //.y(function(d) { return main_y(d.buildFixTime); });

    // Passing xValue and yValue does not work
    .x(function(d) { return main_x(xValue); })
    .y(function(d) { return main_y(yValue); });

http://jsfiddle.net/goodspeedj/fDyLY/

Thank you in advance.

JamesE
  • 3,833
  • 9
  • 44
  • 82

2 Answers2

2

You need to redefine your accessor method within .x() and .y(). The accessor method defines the way that a datum is pulled out of the data that is bound to the selection that you call the line generator on.

Suppose you have a relatively flat data structure such as the following.

data = [{x : 1, y : 2}, {x:1, y:3}, {x:4, y:5}];

You then bind the data to a selection with the following statement

d3.select("body").datum(data).append("path").attr("d",lineGenerator);

Quite a bit is going on underneath this statement. I'll give you a bit more of a walkthrough after showing you a commonly used example.

The important aspect to understand is that similarly to other calls in d3 such as

var exampleRectangles = d3.select("body")
.data(data).enter()
.append("rect")
.attr("width",2)
.attr("height", 3)
.attr("x",function(datum){return datum.x}) // pay attention to this line
.attr("y",0);

d3 is implicitly iterating over each element in your data. For each datum in your data array, in this case there is a total of three datum, you are going to add a rectangle to the dom.

In the line that I tell you to pay attention to you notice that you're defining an anonymous (unnamed) function. What is that datum parameter coming from? It's implicitly being passed to your anonymous function.

So each rectangle has it's own corresponding datum {x : 1, y : 2}, {x:1, y:3}, {x:4, y:5} respectively. Each rectangle's x coordinate is defined by the respective datum.x attribute. Under the sheets, d3 is implicitly looping over the data array that you've defined. A similar approach to the example d3 code could be written as above.

for (var i = 0; i < data.length; i++)
{

   d3.select("body").append("rect")
    .attr("width",2)
    .attr("height", 3)
    .attr("x",data[i].x)
    .attr("y",0);
}

This follows from the notion of data driven documents (d3). For each item added (a rectangle in the above example a piece of data is tied to it. In the above example you see that there is something kind of similar to your .x() and .y() accessor functions :

.attr("x",function(datum){return datum.x})

This function is telling d3 how to filter over the total datum that's being passed to the .attr() accessor method.

So, you need to determine which data you need to get a hold of to make your .attr("d", lineGenerator)call make sense. The difference between your.datum(data)call and the typical.data(data)call is that instead of parceling the data that's being passed to.data(data)`, the whole array is given as a single piece of data to the line generator function (similar to main_line(data), wherein it will again implicitly loop over the points to construct your path.

So, what you need to do is determine what a single datum will be defined as for your function to operate on.

I'm not going to define that as I don't seem to know quite which information you are operating on, but I would hazard a guess at something like.

.x(xAccessor)
.y(yAccessor)

function xAccessor(datum)
{
return xScale(datum._id.month);
}

function yAccessor(datum)
{
return yScale(datum.buildFixTime);
}
arete
  • 1,903
  • 4
  • 17
  • 23
1

The way you have it set up, xValue and yValue are functions; you have to actually execute them on something to get a value back.

.x(function(d) { return main_x( xValue(d) ); })
.y(function(d) { return main_y( yValue(d) ); });

If you weren't using a scale, you could use

.x(xValue)
.y(yValue);

but only because if you pass in a function d3 executes it for you with the data as a parameter. And that only works for d3 methods that expect functions as possible input -- the scale functions expect data values as input.

I wrote a long piece work for another user last week that you may find useful, explaining methods that accept functions as parameters.

Community
  • 1
  • 1
AmeliaBR
  • 27,344
  • 6
  • 86
  • 119