带有 Y 值跟踪的 D3.js 多系列图表 [英] D3.js Multi-Series Chart with Y value tracking

查看:20
本文介绍了带有 Y 值跟踪的 D3.js 多系列图表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

可行的解决方案:现在我正在设计样式并解决一些与我创建由多个数据系列和值跟踪组成的图表有关的问题.如果有人遇到与我相同或类似的问题,我会尽快尝试为您提供工作代码示例,可以将其作为基础工作.目前我使用的大部分技巧都在下面的评论中.

Working solution: Right now I'm working on styling and on solving some of the issues regarding my problem with creating chart consisting of multiple-data series with values tracking. I will try as soon as I can to give you a sample of working code soo if anybody will came across the same or similar problem as I did, could work on it as a base. For now most of the tips which I used are in the comments below.

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

This will be my first question on StackOverflow and I'm looking forward to seeing what answers you might have to my problem.

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

Recently I got project in which I have to write Javascript code for generating charts and in which I would be able to read Y values from every line of the chart at the same time. I very new to D3 framework and by now I'm able to read csv data, create multi-series chart and track and read Y value but only when I'm creating chart from a single data series. I was trying to create multiple similar functions that would track data from diferent series of data but it won't work and in console i see that the Y is showing as null from what I can understand. I was using examples from D3 website to try to learn it and for now code will be very similar to those examples.

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

Later on I would need to do some other things with it but i think that after solving that problem i will be able to keep going. There will be like:

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

现在我有类似的东西.对不起,如果它有点乱,但我仍在学习和尝试很多不同的东西.我还添加了屏幕截图以及我可以获得的一些控制台信息.我希望它能帮助你们看到我做错了什么以及我需要学习什么.此外,这不是我唯一的方法,要全部展示它们也太长了.

For now I have something like that. Sorry if it is a little bit messy but I'm still learning and trying a lot of different things. I have added also screenshot from what it looks like for me and some console information that i could get. I hope it will help you guys see what I'm doing wrong and what I would need to learn. Also this is not my only approach and it would be too long to show them all.

我正在尝试一些不同的方法.在页面底部,我会显示我现在所做的事情.

I'm trying a little bit different approach. On the bottom of the page i will show what I have done by now.

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

Sorry if i was't precise enough about my goal. What I'm trying to do with this is I want to be able to read all Y-axis values of drawn lines (it will be 4 of them) at the same time on one X-axis value. I have added screenshot of the second code in which I'm able to read only one Y-axis value and can't read the over one.

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

对我来说是这样的:生成图表CSV 数据文件如下所示:

It looks like for me like this: Generated chart CSV data file looks like this:

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

我在试图了解问题所在时检查的一些过度信息:

Some over information that I inspected while trying to understand what is wrong:

[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,但现在我并没有真正使用任何框架,学习很有趣,但是在试图弄清楚我在做什么时可能真的很令人沮丧错了.

I would really appreciate any help from you. I know some Object Oriented Programming, HTML, CSS, but for now I wasn't really working with any framework and it is fun to learn but on the over hand could be really frustrating while trying to figure out what the heck I'm doing wrong.

编辑

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

Now I'm trying drawing two lines separately. It is working great and it could make it easier for me to change lines style later on. Now i need to use mousemove function for each of those lines. Then it would be fairly easy to just pass readed values to some variables and show them in some box or something.

这是我第二次尝试的代码(抱歉帖子太长了):

This is the code for the my second try(sorry for post getting long):

第二个代码的屏幕截图称为 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>

推荐答案

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

You've got all the basic code there, you just need to get it all to run at the same time.

第一个问题是您在同一个元素上设置了两个不同的mousemove"事件处理程序.除非你使用命名空间来区分它们,否则第二个函数只是替换了第一个,所以你的第一个函数永远不会被调用.与创建具有不同命名空间的两个事件处理程序相比,将所有事件处理代码放在一个函数中要容易得多.

The first problem is that you're setting two different "mousemove" event handlers on the same elements. Unless you use namespaces to distinguish them, the second function just replaces the first, so your first function is never getting called. Rather than creating two event handlers with different namespaces, it's much easier to just put all your event-handling code into one function.

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

The second problem is that you only have one "focus" element, and so even if you did run both functions to set the two different tooltip contents and position, only the second version would be displayed, because it just replaces the first.

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

So to recap, you need to: create a tooltip/focus element for each path, and then have one event-handling function that sets all the values and positions according to the appropriate column of your data file.

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

To keep the code concise, and to allow you to quickly switch from two lines to four or more, I'm going to suggest that you create the focus elements as a data-joined selection, where the data is an array of column names:

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

现在,当您在鼠标悬停/鼠标移出事件中显示或隐藏焦点时,它将显示或隐藏所有工具提示:

Now, when you show or hide focus in the mouseover/mouseout events, it will show or hide all the tooltips:

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 方法的任何函数:

But what should you do in your mousemove function? The first part, figuring out the nearest x-value (date) is the same. But then you have to set the text and position of each focus tooltip according to the values in the correct column. You can do this because each focus element has a column name bound to it as a data object, and d3 will pass that data object as the first parameter to any function you pass in to a d3 method:

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

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

By the way, you can use this same structure -- use the column names as data, and then use that name in a function to grab the correct data value -- to create all your lines with the same code, without having to create separate data arrays. Let me know if you have difficulty implementing that.

这篇关于带有 Y 值跟踪的 D3.js 多系列图表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆