使用d3.js的多个圆圈/线条

时间:2014-02-12 15:15:46

标签: javascript json d3.js

我有一个d3.js问题,并且已经挣扎了一段时间,似乎无法解决它。我相信这很简单,但我遗漏了一些非常基本的东西。

具体来说,我有以下代码,它为JSON中的第一个条目生成一行和两个圆圈 - 我已为第一个条目“硬编码”。

我现在想将JSON文件的第2和第3个条目添加到图形中,并控制线条和圆形颜色,然后概括代码。

从阅读文档和StackOverflow,似乎正确的方法是使用嵌套,但我似乎无法使其工作?

代码位于以下网址的jsfiddle,javascript位于以下位置。

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");

2 个答案:

答案 0 :(得分:4)

“嵌套”一词出现在d3中的两个上下文中 - 使用d3.nest创建嵌套数据数组,并使用嵌套数据创建嵌套选择。

您的数据已经采用了嵌套选择的正确格式 - 一个对象数组,每个对象都有一个单独数据点的子数组。因此,您无需担心操纵数据,只需要在嵌套的d3选择中直接将数据加入元素中:

我将尽快带您通过,但以下教程将成为未来的好参考:

在您的示例中:您有一个顶级数据结构,它是一个电影对象数组,每个数组都包含一个每周收入值的子数组。您需要确定的第一件事是您希望与每个数据级别关联的元素类型。您正在为子数组中的数据绘制一条线和一组圆,但当前没有为顶级数组对象(电影)添加任何内容。您需要为它们添加某些内容才能使嵌套选择生效,并且它需要包含您的直线和圆。在SVG中,这几乎总是一个<g>(分组)元素。

为数据数组中的每个对象有效地创建一个<g>元素 - 并将数据对象附加到元素以供将来参考 - 您创建一个选择,加入您的数据,然后使用数据连接选择的enter()方法为每个与元素不匹配的数据对象添加元素。在这种情况下,由于我们没有任何要启动的元素,因此所有数据对象都将在enter()选择中。但是,更新某些数据时也可以使用相同的模式。

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 ); });

包含其余代码的实时版本:http://jsfiddle.net/GVmVk/3/

您需要调整x-scale的域名,以便不会切断第一个数据点,并且您需要决定如何使用您的电影标题属性,但这应该可以帮到您去。

答案 1 :(得分:3)

确实如此,nested selection是圈子的方式,虽然你不需要它们用于路径:

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");

完整示例here