更新D3.js多线图时遇到问题

时间:2016-01-29 17:02:18

标签: javascript d3.js

我对D3很新,我遇到了问题。

我创造了一个简单的例子,说明了我想要实现的目标。

首先,我有一个包含数据的CSV文件。在这个例子中,它包括2个商店几个月的一些流行手机的电话销售数据。数据如下所示:

Store,Product,Month,Sold
London,iPhone,0,5
London,iPhone,1,4
London,iPhone,2,3
London,iPhone,3,5
London,iPhone,4,6
London,iPhone,5,7
London,Android Phone,0,3
London,Android Phone,1,4
London,Android Phone,2,5
London,Android Phone,3,7
London,Android Phone,4,8
London,Android Phone,5,9
London,Windows Phone,0,1
London,Windows Phone,1,2
London,Windows Phone,2,6
London,Windows Phone,3,7
London,Windows Phone,4,8
London,Windows Phone,5,5
Glasgow,iPhone,0,3
Glasgow,iPhone,1,4
Glasgow,iPhone,2,5
Glasgow,iPhone,3,2
Glasgow,iPhone,4,1
Glasgow,iPhone,5,3
Glasgow,Android Phone,0,4
Glasgow,Android Phone,1,3
Glasgow,Android Phone,2,7
Glasgow,Android Phone,3,4
Glasgow,Android Phone,4,3
Glasgow,Android Phone,5,6
Glasgow,Windows Phone,0,3
Glasgow,Windows Phone,1,6
Glasgow,Windows Phone,2,7
Glasgow,Windows Phone,3,5
Glasgow,Windows Phone,4,3
Glasgow,Windows Phone,5,4

我在JS / D3.js中编写了以下代码:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    svg {
      font: 10px sans-serif;
    }

    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }

    .x.axis path {
      fill:none;
      stroke:#000;
      shape-rendering: crispEdges;
    }

    .line {
      fill: none;
      stroke-width: 1.5px;
    }

</style>
<body>
    <p id="menu"><b>Test</b>
    <br>Select Store: 
    <select>
        <option value="0">London</option>
        <option value="1">Glasgow</option>
   </select>
   </p>
<script src="http://d3js.org/d3.v3.js"></script>
<script>


var margin = {top: 20, right: 80, bottom: 30, left: 50},
    width = 900 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

// construct a linear scale for x axis
var x = d3.scale.linear()
    .range([0,width]);

// construct a linear scale for y axis
var y = d3.scale.linear()
    .range([height,0]);

// use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
var color = d3.scale.category10();

// create the x axis and orient of ticks and labels at the bottom
var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

// create the y axis and orient of ticks and labels on the left
var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

// line generator function
var line = d3.svg.line()
    //.interpolate("basis")
    .x(function(d) { return x(d.Month); })
    .y(function(d) { return y(d.Sold); });

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    d3.csv("sampleData.csv", function(error, data) {


       color.domain(d3.keys(data[0]).filter(function(key) { return key == "Product"; }));

    // first we need to corerce the data into the right formats
        // map the data from the CSV file
      data = data.map( function (d) { 
        return { 
          Store: d.Store,
          Product: d.Product,
          Month: +d.Month,
          Sold: +d.Sold }; 
    });   


    // nest the data by regime and then CI
    var salesDataByStoreProduct = d3.nest()
          .key(function(d) { return d.Store; })
          .key(function(d) { return d.Product; })         
          .entries(data);

    // get the first regime's nest
    var salesDataForLondon;
    salesDataForLondon = salesDataByStoreProduct[0].values;

    console.log(salesDataForLondon);

    x.domain([d3.min(salesDataForLondon, function(d) { return d3.min(d.values, function (d) { return d.Month; }); }),
             d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Month; }); })]);
    y.domain([0, d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Sold; }); })]);


      svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);

      svg.append("g")
          .attr("class", "y axis")
          .call(yAxis);


      var Products = svg.selectAll(".Product")
          .data(salesDataForLondon, function(d) { return d.key; })
        .enter().append("g")
          .attr("class", "Product");

      Products.append("path")
          .attr("class", "line")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) { return color(d.key); });

    function redraw()
    {
        var salesDataByStoreProduct = d3.nest()
          .key(function(d) { return d.Store; })
          .key(function(d) { return d.Product; })         
          .entries(data);

        var salesDataForGlasgow;
        salesDataForGlasgow = salesDataByStoreProduct[1].values;

        console.log(salesDataForGlasgow);

        x.domain([d3.min(salesDataForGlasgow, function(d) { return d3.min(d.values, function (d) { return d.Product; }); }),
                 d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Product; }); })]);
        y.domain([0, d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Sales; }); })]);


          svg.select("g")
              .call(xAxis);

          svg.select("g")
              .call(yAxis);


          var Products = svg.selectAll(".Product")
              .data(salesDataForGlasgow, function(d) { return d.key; })
            .enter().select("g")
              .attr("class", "Product");

          Products.select("path")
              .attr("d", function(d) { return line(d.values); })
              .style("stroke", function(d) { return color(d.key); });

    }

    /******************************************************/
 var menu = d3.select("#menu select")
    .on("change", change);   


function change() 
{
    clearTimeout(timeout);
    d3.transition()
    .duration(altKey ? 7500 : 1500);
    redraw();
}
var timeout = setTimeout(function() {
  menu.property("value", "ENEUSE").node().focus();
  change();
}, 7000);

var altKey;
d3.select(window)
    .on("keydown", function() { altKey = d3.event.altKey; })
    .on("keyup", function() { altKey = false; });

/******************************************************/

});                    

</script>
</body>
</html>

我已在CSV文件中读取,然后使用D3嵌套创建层次结构,如下所示:

Store-&GT;制品 - &GT;月 - &gt;销售

我希望图表能够按月显示伦敦每个产品的销售数据,然后如果选择更改,则按月显示格拉斯哥的销售数据。

然而,尽管正在呈现伦敦数据,但当我选择格拉斯哥时,图表并未更新。

为了排除任何太明显的事情,我已经为每个商店硬编码了数组索引。

我还添加了console.log,可以看到正在使用的正确数据,但在调用redraw()时只是没有在图表中呈现。

我感谢任何有关问题原因的建议,我怀疑它与以下代码有关:

var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");

Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });

非常感谢有关改进或简化代码的任何其他建议。

2 个答案:

答案 0 :(得分:1)

正如您所怀疑的那样,问题确实存在于以下两个陈述中:

      var Products = svg.selectAll(".Product")
          .data(salesDataForGlasgow, function(d) { return d.key; })
        .enter().select("g")
          .attr("class", "Product");

      Products.select("path")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) { return color(d.key); });

Products源自.enter()选择。这包含每个数据项的一个元素,该元素未连接到DOM中的现有元素。更改图表以显示格拉斯哥数据时,没有要添加的新元素(伦敦数据有三个产品和格拉斯哥数据一样),因此.enter()选项为空。

相反,您需要从.Product重新启动选择。将两个语句中的第二个更改为以下内容:

      svg.selectAll(".Product")
          .select("path")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) { return color(d.key); });

我在您的代码中发现了一些其他问题。首先,设置x.domain()y.domain()的三行在结尾处使用了错误的属性名称。这会导致各种NaN出现在xy比例的范围内,因为D3会尝试将产品名称或undefined转换为数字。在这三行的末尾,将d.Product替换为d.Month,将d.Sales替换为d.Sold,以便它们与设置{{1}范围的行一致}}和x衡量伦敦销售数据。

最后,您需要调整重置轴的方式。目前您正在使用以下代码:

y

结束调用所有 svg.select("g") .call(xAxis); svg.select("g") .call(yAxis); 元素上的xAxis然后调用yAxis函数,包括两个轴,所有轴刻度线和三个图形线,因此图形看起来有点混乱。您已将X轴的g设置为class,但由于类名称中没有空格,因此实际上给了轴{{1} }和x axis。类似的事情发生在y轴上。

您需要做的是在拨打xaxis之前,使用您已分配给他们的课程分别选择轴。用以下内容替换上面的行:

xAxis

在您完成所有这些更改后,图表应该可以满足您的需求。

答案 1 :(得分:0)

虽然这不是代码审查网站,但您的代码可能需要进行大量重构。首先,您没有正确处理输入,更新,退出模式。其次,一旦正确处理模式,就不需要单独的redraw函数。只需一个函数来处理创建和更新。

这是一个快速重构:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  svg {
    font: 10px sans-serif;
  }

  .axis path,
  .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }

  .x.axis path {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }

  .line {
    fill: none;
    stroke-width: 1.5px;
  }
</style>

<body>
  <p id="menu"><b>Test</b>
    <br>Select Store:
    <select>
      <option value="London">London</option>
      <option value="Glasgow">Glasgow</option>
    </select>
  </p>
  <script src="http://d3js.org/d3.v3.js"></script>
  <script>
    var margin = {
        top: 20,
        right: 80,
        bottom: 30,
        left: 50
      },
      width = 900 - margin.left - margin.right,
      height = 500 - margin.top - margin.bottom;

    // construct a linear scale for x axis
    var x = d3.scale.linear()
      .range([0, width]);

    // construct a linear scale for y axis
    var y = d3.scale.linear()
      .range([height, 0]);

    // use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
    var color = d3.scale.category10();

    // create the x axis and orient of ticks and labels at the bottom
    var xAxis = d3.svg.axis()
      .scale(x)
      .orient("bottom");

    // create the y axis and orient of ticks and labels on the left
    var yAxis = d3.svg.axis()
      .scale(y)
      .orient("left");

    // line generator function
    var line = d3.svg.line()
      //.interpolate("basis")
      .x(function(d) {
        return x(d.Month);
      })
      .y(function(d) {
        return y(d.Sold);
      });

    var svg = d3.select("body").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // create gs for axis but don't add yet
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")");

    svg.append("g")
      .attr("class", "y axis");

    var salesDataByStoreProduct = null;
    d3.csv("data.csv", function(error, data) {

      color.domain(d3.keys(data[0]).filter(function(key) {
        return key == "Product";
      }));

      // first we need to corerce the data into the right formats
      // map the data from the CSV file
      data = data.map(function(d) {
        return {
          Store: d.Store,
          Product: d.Product,
          Month: +d.Month,
          Sold: +d.Sold
        };
      });

      // nest the data by regime and then CI
      salesDataByStoreProduct = d3.nest()
        .key(function(d) {
          return d.Store;
        })
        .key(function(d) {
          return d.Product;
        })
        .entries(data);

      draw("London");

    });

    function draw(which) {

      // get the first regime's nest
      var salesData = null;
      salesDataByStoreProduct.forEach(function(d) {
        if (d.key === which) {
          salesData = d.values;
        }
      });

      // set domains
      x.domain([d3.min(salesData, function(d) {
          return d3.min(d.values, function(d) {
            return d.Month;
          });
        }),
        d3.max(salesData, function(d) {
          return d3.max(d.values, function(d) {
            return d.Month;
          });
        })
      ]);
      y.domain([0, d3.max(salesData, function(d) {
        return d3.max(d.values, function(d) {
          return d.Sold;
        });
      })]);

      // draw axis
      svg.select(".x.axis").call(xAxis);    
      svg.select(".y.axis").call(yAxis);

      // this is the update selection
      var Products = svg.selectAll(".Product")
        .data(salesData, function(d) {
          return d.key;
        });

      // this is the enter selection
      Products
        .enter().append("g")
        .attr("class", "Product")
        .append("path");

      // now do update
      Products.selectAll("path")
        .attr("class", "line")
        .attr("d", function(d) {
          return line(d.values);
        })
        .style("stroke", function(d) {
          return color(d.key);
        });
    }

    var menu = d3.select("#menu select")
      .on("change", change);

    function change() {
      draw(this.options[this.selectedIndex].value);
    }
  </script>
</body>

</html>

正在运行代码here