【问题标题】:D3.js Multi-Series Chart with Y value tracking带 Y 值跟踪的 D3.js 多系列图表
【发布时间】:2014-03-30 14:53:51
【问题描述】:

工作解决方案:现在我正在设计样式并解决一些与我创建由具有值跟踪的多数据系列组成的图表有关的问题。如果有人遇到与我相同或相似的问题,我会尽快给你一个工作代码示例,可以以此为基础。目前我使用的大部分技巧都在下面的 cmets 中。


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

最近我有一个项目,我必须编写 Javascript 代码来生成图表,并且我可以同时从图表的每一行读取 Y 值。我对 D3 框架非常陌生,现在我能够读取 csv 数据、创建多系列图表以及跟踪和读取 Y 值,但只有当我从单个数据系列创建图表时。我试图创建多个类似的函数来跟踪来自不同系列数据的数据,但它不起作用,并且在控制台中我看到 Y 显示为空值。我正在使用 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 函数。然后将读取的值传递给一些变量并在某个框或其他东西中显示它们会相当容易。

这是我第二次尝试的代码(对不起,帖子变长了):

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>

【问题讨论】:

  • 我发布了一种不同的方法,可以帮助我解决这个问题。如果有人对下一步做什么有建议,我将不胜感激。
  • 我不太确定你的问题是什么?您是否需要一个工具提示来显示与鼠标具有相同 x 值的所有行的 y 值?您应该能够使用您拥有的 mousemove 功能来做到这一点。还是您只想要鼠标下特定行的值?在这种情况下,您将 (a) 需要将数据数组绑定到 &lt;path&gt; 元素或其父 &lt;g&gt; 元素(您的第二个代码示例执行此操作),并且 (b) 接受该数据数组作为 mousemove 的参数函数:`function mousemove(pathData){... var d0=pathData[i - 1]; ...}。
  • 是的。在一个 X 轴值上读取所有 Y 轴值是我想要做的。对此感到抱歉。我试图将 b) 解决方案应用于我的第一个代码,但我无法使其工作,第二个代码仅读取一个 Y 轴值。我会尝试找出它有什么问题,但我会很感激更多的建议。我花了大约 5 天的时间来解决这个问题,此时我觉得我真的陷入了困境。

标签: javascript csv svg charts d3.js


【解决方案1】:

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

第一个问题是您在相同的元素上设置了两个不同的“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 事件中显示或隐藏焦点时,它将显示或隐藏所有工具提示:

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

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

【讨论】:

  • 非常感谢您的回答。它肯定会派上用场,我会尽可能尝试。昨天,当我在努力寻找解决方案时,我在 Stack Overflow 上发表了这篇文章。 linklink。我很高兴终于可以开始工作了。如果我在实施您的解决方案时遇到任何问题,我会通知您。我想知道。我可以将自定义颜色应用于例如第一版代码中使用的颜色函数吗?
  • 要创建自定义色标,您只需创建一个 [d3 ordinal scale] 并自己设置域和范围。 var color = d3.scale.ordinal().domain(["max", "min", "mean"]).range(["red", "blue", "green"])。域值应与您用于数据的列名匹配,然后您可以将色阶作为.attr('stroke', color) 之类的函数传递。
  • 我还有一个问题。现在我正在尝试在图表之外添加字段,我将在悬停时看到每条线的值。我希望它看起来像画圆圈一样。 link(文本文件图表)。它只向我显示最后一个值,当我尝试添加时。 enter() 什么也没发生。稍后我将有另一个问题要解决。我将不得不用(第 1 周,第 2 周...)之类的东西替换 X 轴标签和刻度,而不会改变太多,因为我可能需要在工具提示日期中显示(2014-01-01 00:00)。
  • 您在函数中添加文本以修改圆圈,因此每次该函数运行时,都会选择修改相同的文本元素以获取当前圆圈的数据。如果您需要多个文本元素,则需要一次对所有数据元素的数组进行数据连接。对于您的轴,请查看 formatting options for datestickFormat function。对于任何其他问题,请创建一个新的 SO 问题,以便其他人可以提供帮助。
猜你喜欢
  • 2022-09-23
  • 1970-01-01
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-21
  • 2016-04-15
  • 1970-01-01
相关资源
最近更新 更多