2016-01-29 44 views
0

我对D3很新,并且遇到问题。在更新D3.js多行图表时遇到问题

我创建了一个我试图实现的简单示例。

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

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> 

其中I在CSV文件已经阅读并然后用于D3巢创建如下图所示的层级:

Store->产品 - >月 - >销售

我希望图表按月伦敦,然后如果选择改变时,显示本单位产品的销售数据周一的销售数据为格拉斯哥。

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

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

我也添加了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); }); 

改善或简化了代码任何其他意见将非常感激地赞赏。

回答

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 s出现在xy比例范围内,因为D3试图将产品名称或undefined转换为数字。在这三行结尾处,将d.Product替换为d.Monthd.Sales以及d.Sold,以便它们与设置伦敦销售数据的xy范围的范围的行一致。

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

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

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

这结束要求所有g元素,包括两个轴,所有轴蜱和三个曲线图线xAxis然后yAxis功能,使图形看起来有点困惑。您已将X轴的class设置为x axis,但由于类名称中没有空格,因此实际上已将该轴指定为类xaxis。类似的事情发生在Y轴上。

在调用xAxisyAxis之前,您需要做的是单独选择轴,使用分配给它们的类。你所做的所有这些改变你想要的图形应该有希望做后

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

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

:用以下内容替换线以上。

+0

非常感谢卢克。结合Mark的代码,我已经能够解决这个问题。我将重新发布工作代码。 –

0

尽管这不是代码评论网站,但您的代码可能会进行重大重构。首先,您没有正确处理输入,更新和退出模式。其次,一旦你正确地处理模式,你不需要单独的功能。只有一个功能来处理创建和更新。

这里有一个快速重构:

<!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

+0

谢谢马克。虽然乍一看,它看起来像你的代码工作,我不认为它是因为它呈现相同的数据,但重新调整轴。我试图确定为什么 –

+0

我通过更改以下代码解决了问题: Products.selectAll(“path”) .attr(“class”,“line”) .attr(“d”,function (d){ return line(d.keyues); }); } .style(“stroke”,function(d){ return color(d.key); }); 到: svg.selectAll( “产品 ”) 。选择(“ 路径 ”) \t \t .attr(“ 类”, “线”) .attr( “d”,函数(d){返回线(d.values);}) .style( “中风” 函数(d){ \t \t \t返回颜色(d.key); \t \t \t}); 似乎解决了这个问题。再次感谢示例代码。 –

+0

@JamesSaunders,我的道歉。它意味着输入'Products.select(“path”)。attr(“d”,function(d)...'。注意,这是一个'select'而不是'selectAll'。参见[here](http:/ /bost.ocks.org/mike/nest/#conclusion)了解为什么我把它搞砸了!有时候回答问题太快了......更正了这里的掠夺者:http://plnkr.co/edit/MGjzEiGOQi7sUeC6nUuP?p=预习 – Mark