简体   繁体   中英

multiple circles / lines using d3.js

I have a d3.js problem and have struggled with this for a while and just can not seem to solve it. I believe it is pretty easy, but am missing something very basic.

Specifically, I have the following code, which generates a line and 2 circles for the 1st entry in the JSON - I have 'hardcoded' it for the first entry.

I'd like to now add the 2nd and 3rd entries of the JSON file to the graph and have control over line and circle colors and then generalize the code.

From reading the documentation and StackOverflow, it seems like the proper approach is to use nesting, but I can't seem to make it work?

The code is on jsfiddle at the following URL and the javascript is below.

http://jsfiddle.net/GVmVk/

        // INPUT
        dataset2 = 
        [
            {   
                movie : "test",     
                results : 
                [
                    { week: "20130101", revenue: "60"},
                    { week: "20130201", revenue: "80"}
                ]
            },
            {
                movie : "beta",     
                results : 
                [
                    { week: "20130101", revenue: "40"},
                    { week: "20130201", revenue: "50"}
                ]
            },
            {
                movie : "gamm",     
                results : 
                [
                    { week: "20130101", revenue: "10"},
                    { week: "20130201", revenue: "20"}
                ]
            }           
        ];


        console.log("1");

        var parseDate = d3.time.format("%Y%m%d").parse;

        var lineFunction = d3.svg.line()
            .x(function(d) { return xScale(parseDate(String(d.week))); })
            .y(function(d) { return yScale(d.revenue); })
            .interpolate("linear");

        console.log("2");

        //SVG Width and height
        var w = 750;
        var h = 250;


        //X SCALE AND AXIS STUFF

        //var xMin = 0;
        //var xMax = 1000;

        var xScale = d3.time.scale()
            .domain([parseDate("20130101"),parseDate("20131231")])
            .range([0, w]);

        console.log(parseDate("20130101"));
        console.log("3");

        var xAxis = d3.svg.axis()
              .scale(xScale)
              .orient("bottom");

        console.log("4S");

        //Y SCALE AND AXIS STUFF

        var yScale = d3.scale.linear()
                 .domain([0, 100])
                 .range([h, 0]);

        var yAxis = d3.svg.axis()
              .scale(yScale)
              .orient("left")
              .ticks(5);

        //Create SVG element
        var svg = d3.select("body")
                    .append("svg")
                    .attr("width", w)
                    .attr("height", h);

        console.log("4S1");

        //CREATE X-AXIS
        svg.append("g")
            .attr("class", "axis") 
            .attr("transform", "translate(0," + (h - 30) + ")")
            .call(xAxis);

        //Create Y axis
        svg.append("g")
            .attr("class", "axis")
            .attr("transform", "translate(" + 25 + ",0)")
            .call(yAxis);



        svg.selectAll("circle")
            .data(dataset2[0].results)
           .enter()
            .append("circle")
            .attr("cx", function(d) {
    //      console.log(d[0]);
            console.log(parseDate(d.week));
            return xScale(parseDate(d.week));
        })
        .attr("cy", function (d) {
            return yScale(d.revenue);
        })
        .attr("r", 3);    



        //create line
        var lineGraph = svg.append("path")
            .attr("d", lineFunction(dataset2[0].results))
            .attr("class", "line");

The word "nesting" comes up in two contexts in d3 -- creating nested data arrays with d3.nest , and using nested data to create nested selections.

Your data is already in the correct format for a nested selection -- an array of objects, each of which has a sub-array of individual data points. So you don't need to worry about manipulating the data, you just need to go straight to joining your data to your elements in nested d3 selections:

I'm going to take you through it quickly, but the following tutorials will be good reference for the future:

On to your example: you have a top-level data structure that is an array of movie objects, each of which contains a sub-array of weekly revenue values. The first thing you need to decide is what type of elements you want associated with each level of data. You're drawing a line and a set of circles for the data in the sub-array, but aren't currently adding anything for the top-level array objects (the movies). You need to add something for them in order for nested selections to work, and it needs to be something that can contain your line and circle. In SVG, that's almost always going to be a <g> (grouping) element.

To efficiently create one <g> element for every object in your data array -- and to attach the data objects to the elements for future reference -- you create an empty selection, join your data to it, then use the enter() method of the data join selection to add elements for each data object that didn't match an element. In this case, since we don't have any elements to start, all the data objects will be in the enter() selection. However, the same pattern also works when updating some of the data.

var movies = svg  //start with your svg selection, 
     //it will become the parent to the entering <g> elements
   .selectAll("g.movie") //select all <g> elements with class "movie" 
                         //that are children of the <svg> element
                         //contained in the `svg` selection
                         //this selection will currently be empty

   .data( dataset2 ); //join the selection to a data array
                     //each object in the array will be associated with 
                     //an element in the selection, if those elements exist

                     //This data-joined selection is now saved as `movies`

movies.enter() //create a selection for the data objects that didn't match elements
   .append("g") //add a new <g> element for each data object
   .attr("class", "movie") //set it's class to match our selection criteria

       //for each movie group, we're going to add *one* line (<path> element), 
       //and then a create subselection for the circles

   .append("path") //add a <path> within *each* new movie <g> element
                    //the path will *inherit* the data from the <g> element
      .attr("class", "line"); //set the class for your CSS

var lineGraph = movies.select("path.line")
       //All the entered elements are now available within the movies selection
       //(along with any existing elements that we were updating).
       //Using select("path") selects the first (and only) path within the group
       //regardless of whether we just created it or are updating it.

      .attr("d", function(d){ return lineFunction(d.results); });
       //the "d" attribute of a path describes its shape;
       //the lineFunction creates a "d" definition based on a data array.
       //If the data object attached to the path had *only* been the d.results array
       //we could have just done .attr("d", lineFunction), since d3
       //automatically passes the data object to any function given as the value
       //of an .attr(name, value) command.  Instead, we needed to create an
       //anonymous function to accept the data object and extract the sub-array.

var circles = movies.selectAll("circle")
         //there will be multiple circles for each movie group, so we need a 
         //sub-selection, created with `.selectAll`.
         //again, this selection will initially be empty.
       .data( function(d) {return d.results; });
         //for the circles, we define the data as a function
         //The function will be called once for each *movie* element,
         //and passed the movie element's data object.
         //The resulting array will be assigned to circles within that particular
         //movie element (or to an `enter()` selection, if the circles don't exist).

circles.enter() //get the data objects that don't have matching <circle> elements
    .append("circle") //create a circle for each
                      //the circles will be added to the appropriate "g.movie"
                      //because of the nested selection structure
    .attr("r", 3); //the radius doesn't depend on the data, 
                   //so you can set it here, when the circle is created,
                   //the same as you would set a class.

circles //for attributes that depend on the data, they are set on the entire
        //selection (including updating elements), after having created the 
        //newly entered circles.
    .attr("cx", function(d) { return xScale( parseDate(d.week) ); })
    .attr("cy", function(d) { return yScale( d.revenue ); });

Live version with the rest of your code: http://jsfiddle.net/GVmVk/3/

You'll need to adjust the domain of your x-scale so that the first data points aren't cut off, and you'll need to decide how you want to use your movie title property, but that should get you going.

Yes indeed, nested selection are the way to go for the circles, although you don't need them for the paths:

svg.selectAll("g.circle")
            .data(dataset2)
           .enter()
            .append("g")
            .attr("class", "circle")
            .selectAll("circle")
        .data(function(d) { return d.results; })
            .enter()
            .append("circle")
            .attr("cx", function(d) {
    //      console.log(d[0]);
            console.log(parseDate(d.week));
            return xScale(parseDate(d.week));
        })
        .attr("cy", function (d) {
            return yScale(d.revenue);
        })
        .attr("r", 3);    



        //create line
        var lineGraph = svg.selectAll("path.line")
            .data(dataset2).enter().append("path")
            .attr("d", function(d) { return lineFunction(d.results); })
            .attr("class", "line");

Complete example here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM