D3.js具有Y值跟踪的多系列图表

时间:2014-02-27 09:22:58

标签: javascript csv svg charts d3.js

工作解决方案:现在我正在设计样式并解决有关我的问题的一些问题,创建包含值跟踪的多数据系列的图表。我会尽快给你一个工作代码的样本,如果有人遇到与我相同或类似的问题,可以作为基础工作。目前,我使用的大多数提示都在下面的评论中。


这将是我关于StackOverflow的第一个问题,我期待看到你可能对我的问题有什么答案。

最近我得到了一个项目,我必须编写用于生成图表的Javascript代码,并且我可以在其中同时从图表的每一行读取Y值。我是D3框架的新手,现在我能够读取csv数据,创建多系列图表并跟踪和读取Y值,但仅限于我从单个数据系列创建图表时。我试图创建多个类似的函数来跟踪来自不同系列数据的数据,但它不起作用,在控制台中我看到Y从我能理解的内容中显示为null。我使用D3网站上的示例来尝试学习它,现在代码将与这些示例非常相似。

稍后我会用它做一些其他的事情,但我认为在解决了这个问题之后我将能继续前进。会有:

  • 通过代码减少来自csv的数据,因为我需要删除标题信息
  • 更改图表的视觉样式并编辑轴缩放

现在我有类似的东西。对不起,如果它有点乱,但我还在学习并尝试很多不同的东西。我还添加了截图,看起来对我来说和我可以得到的一些控制台信息。我希望它能帮助你们看到我做错了什么以及我需要学习什么。这也不是我唯一的方法,而且要显示所有内容的时间太长了。

编辑:我正在尝试一些不同的方法。在页面底部,我将展示我现在所做的事情。

EDIT2:很抱歉,如果我对目标不够准确。我想要做的是我希望能够在一个X轴值上同时读取绘制线的所有Y轴值(它们将是4个)。我添加了第二个代码的屏幕截图,其中我只能读取一个Y轴值而无法读取超过一个。

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

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

.x.axis path {
  display: none;
}

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

.overlay {
  fill: none;
  pointer-events: all;
}

.focus circle {
  fill: none;
  stroke: steelblue;
}

</style>
<body>
<script src="d3.min.js"></script>
<script>

var margin = {top: 20, right: 80, bottom: 30, left: 200},
    //-margin.left
    width = 960 - margin.right,
    height = 750 - margin.top - margin.bottom;

var parseDate = d3.time.format("%Y-%M-%d %H:%M").parse,
    //dodane do sledzenia myszy i rysowania kuleczek
    bisectDate = d3.bisector(function(d) { return d.date; }).left,
    formatValue = d3.format(",.2f"),
    formatCurrency = function(d) { return "$" + formatValue(d); };

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

var color = d3.scale.category10();

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

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .interpolate("basis")
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.transfers); });

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("data2.csv", function(error, data) {
  color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));

  data.forEach(function(d) {
    d.date = parseDate(d.date);
  });

  var bitrates = color.domain().map(function(name) {
    return {
      name: name,
      values: data.map(function(d) {
        return {date: d.date, transfers: +d[name]};
      })
    };
  });

  console.log(bitrates);

  //data.sort(function(a, b) {
    //return a.date - b.date;
  //});

  x.domain(d3.extent(data, function(d) { return d.date; }));
  y.domain([
    d3.min(bitrates, function(c) { return d3.min(c.values, function(v) { return v.transfers; }); }),
    d3.max(bitrates, function(c) { return d3.max(c.values, function(v) { return v.transfers; }); })
  ]);

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

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Transfers");

  var chart = svg.selectAll(".chart")
      .data(bitrates)
    .enter().append("g")
      .attr("class", "chart");

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

  chart.append("text")
      .datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
      .attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.transfers) + ")"; })
      .attr("x", 3)
      .attr("dy", ".35em");
      //.text(function(d) { return d.name; });

  //sledzenie myszy i rysowanie kuleczek
  var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

  focus.append("circle")
      .attr("r", 4.5);

  focus.append("text")
      .attr("x", 9)
      .attr("dy", ".35em");

  svg.append("g").append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove);

  function mousemove() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
    focus.select("text").text(formatCurrency(d.value));
  }
});

</script>

对我来说这样: Generated chart CSV数据文件如下所示:

date,Średni wych.:,Średni wch.:,Maks. wych.:,Maks. wch.:
2014-02-14 15:40,86518717581,101989990772,88304882317,108036052338
2014-02-14 16:00,85739038102,98312113056,87060053514,107154908503

我在尝试理解错误时检查过的一些过度信息:

[Object, Object, Object, Object]
0: Object
name: "Średni wych.:"
values: Array[504]
__proto__: Object
1: Object
2: Object
name: "Maks. wych.:"
values: Array[504]
[0 … 99]
[100 … 199]
100: Object
date: Thu Jan 16 2014 01:00:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 49305177944
__proto__: Object
101: Object
date: Thu Jan 16 2014 01:20:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 42169641572
__proto__: Object
102: Object
date: Thu Jan 16 2014 01:40:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 39400112189
__proto__: Object
103: Object
104: Object
105: Object
106: Object
107: Object
108: Object
109: Object
110: Object

我真的很感谢你的任何帮助。我知道一些面向对象的程序设计,HTML,CSS,但是现在我并没有真正使用任何框架,学习起来很有趣但是在试图找出我正在做什么的时候可能会非常令人沮丧错。

修改

现在我正在尝试分别绘制两条线。它工作得很好,它可以让我以后更容易改变线条样式。现在我需要为每一行使用mousemove函数。然后,将readed值传递给某些变量并在某些框中显示它们会相当容易。

这是我第二次尝试的代码(抱歉发布了很长时间):

Screenshot for the second code is called Chart2.jpg

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

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

.x.axis path {
  display: none;
}

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

.overlay {
  fill: none;
  pointer-events: all;
}

.focus circle {
  fill: none;
  stroke: steelblue;
}

</style>
<body>
<script src="d3.js"></script>
<script>

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

var parseDate = d3.time.format("%d-%b-%y").parse,
    bisectDate = d3.bisector(function(d) { return d.date; }).left,
    formatValue = d3.format(",.2f"),
    formatCurrency = function(d) { return "$" + formatValue(d); };

var x = d3.time.scale()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

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

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });

var valueline2 = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.open); });

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("data.csv", function(error, data) {
  data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
    d.open = +d['open data'];
  });

  data.sort(function(a, b) {
    return a.date - b.date;
  });

  x.domain([data[0].date, data[data.length - 1].date]);
  y.domain([0, d3.max(data, function(d) { return Math.max(d.close, d.open); })]);

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

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Price ($)");

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .style("stroke", "red")
      .attr("d", valueline2);

  var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

  focus.append("circle")
      .attr("r", 4.5);

  focus.append("text")
      .attr("x", 9)
      .attr("dy", ".35em");

  svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove1)
      .on("mousemove", mousemove2);

  function mousemove1() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
    focus.select("text").text(formatCurrency(d.close));
    }

  function mousemove2() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectDate(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.open) + ")");
    focus.select("text").text(formatCurrency(d.open));
    }
});

</script>

1 个答案:

答案 0 :(得分:2)

你已经拥有了所有的基本代码,你只需要同时运行它。

第一个问题是你在同一个元素上设置了两个不同的“mousemove”事件处理程序。 Unless you use namespaces to distinguish them, the second function just replaces the first,所以你的第一个功能永远不会被调用。不是创建具有不同命名空间的两个事件处理程序,而是将所有事件处理代码放入一个函数中要容易得多。

第二个问题是你只有一个“焦点”元素,所以即使你确实运行了两个函数来设置两个不同的工具提示内容和位置,只会显示第二个版本,因为它只是替换第一个

所以回顾一下,你需要:为每个路径创建一个工具提示/焦点元素,然后有一个事件处理函数根据你的相应列设置所有值和位置数据文件。

为了保持代码简洁,并允许您快速从两行切换到四行或更多,我建议您将焦点元素创建为数据连接选择,其中数据是一个数组列名:

var columnNames = d3.keys( data[0] ) //grab the key values from your first data row
                                     //these are the same as your column names
                  .slice(1); //remove the first column name (`date`);

var focus = svg.selectAll("g")
    .data(columnNames)
  .enter().append("g") //create one <g> for each columnName
    .attr("class", "focus")
    .style("display", "none");

focus.append("circle") //add a circle to each
    .attr("r", 4.5);

focus.append("text")  //add a text field to each
    .attr("x", 9)
    .attr("dy", ".35em");

现在,当你在mouseover / mouseout事件中显示或隐藏焦点时,它会显示或隐藏 all 工具提示:

svg.append("rect")
  .attr("class", "overlay")
  .attr("width", width)
  .attr("height", height)
  .on("mouseover", function() { focus.style("display", null); })
  .on("mouseout", function() { focus.style("display", "none"); })
  .on("mousemove", mousemove);

但是你应该在你的mousemove功能中做些什么?第一部分,计算出最接近的x值(日期)是相同的。但是,您必须根据正确列中的值设置每个焦点工具提示的文本和位置。您可以这样做,因为每个焦点元素都有一个绑定到它的列名作为数据对象,并且d3会将该数据对象作为第一个参数传递给传递给d3方法的任何函数:

function mousemove() {
  var x0 = x.invert(d3.mouse(this)[0]),
    i = bisectDate(data, x0, 1),
    d0 = data[i - 1],
    d1 = data[i],
    d = x0 - d0.date > d1.date - x0 ? d1 : d0; 
       //d is now the data row for the date closest to the mouse position

   focus.attr("transform", function(columnName){
         return "translate(" + x( d.date ) + "," + y( d[ columnName ] ) + ")";
   });
   focus.select("text").text(function(columnName){
         //because you didn't explictly set any data on the <text>
         //elements, each one inherits the data from the focus <g>

         return formatCurrency(d[ columnName ]);
   });
}

顺便说一句,您可以使用相同的结构 - 使用列名作为数据,然后在函数中使用该名称来获取正确的数据值 - 使用相同的代码创建所有行,而无需创建单独的数据数组。如果你难以实现,请告诉我。