当用户更改数据的排序时,在火花线上放置一个圆圈

12

几天前我创建了这个问题,关于当用户选择更改数据顺序时,火花线的排序。答案已解决了这个问题,但仍然需要正确定位突出显示用户放置鼠标的红色圆圈。

这是代码:PLUNKER

我思考了如何更改代码以在数据排序更改时重新定位火花线圆圈。但我不知道在哪里以及如何更改代码。接下来,我将根据与火花线相关的代码点来说明我的推理。

(1) 这两行代码定义了火花线的域和范围。在数据排序更改时,这些代码似乎不应该被更改。

// domain and range for sparkline lines
var xSpark = d3.scaleLinear().domain([0, numYears-1]).range([0, sparkLength]); 
var ySpark = d3.scaleLinear().domain([minYvalue, maxYvalue]).range([itemSize-2, 2]);

(2) 这段代码选择了元素#data-svg-i(其中i是火花线),它追加了一个圆圈,将其定位在cxcy上,这取决于xSparkySpark。 如果1中提到的条件成立(即xSparkySpark是“固定”的值),那么即使数据顺序发生变化,这段代码也不必更改。

var cells = svg.selectAll('.cell')
    .data(data)
    .enter()
    .append('g')
    .append('rect')
    .on('mouseover', function(d, i) { // on mouseover rect
        // get row, column and value of this rect
        var idr = d3.select(this).attr('data-r'); // row
        var idc = d3.select(this).attr('data-c'); // column
        var value = d3.select(this).attr('data-value');
        // highlight this rect
        d3.select(this).style('stroke', 'red');
        // add red dot to sparkline
        d3.select('#data-svg-' + idr)
            .append('circle')
            .attr('r', 3) // radius
            .style('stroke', 'red')
            .style('fill', 'red')
            .attr('cx', xSpark(idc))
            .attr('cy', ySpark(value));
    })

(3) 当数据顺序发生变化时,此代码块也无需更改。

line = d3.line()
    .x(function(d, i) { 
        return xSpark(i); 
    })
    .y(function(d) { 
        return ySpark(d); 
    })
    .defined(function(d) { // for missing (0) data
        return d !== 0;
    });

(4) 数据应该被更新,但实际上没有更新。 在排序之前,data 包含数据以正确的顺序显示,排序后,data 没有发生变化,但是它应该更改,不是吗?

pos 我认为不应该改变,cxcy 也一样,因为它们取决于 xSpark/ySparkpos

var sparkSvg = d3.select('#sparkline')
    .append('svg')
    .on('mousemove', function() { // on mousemove svg sparkline canvas
        var mouse = d3.mouse(this); // mouse position [x, y]
        var r = d3.select(this).attr('data-r'); // number of line
        var data = d3.select(this).select('path').data(); // array containing all the data values of that line
        
        var element = document.getElementById('data-path-' + r); // get the right path
        var pos = get_data_on_line(data, mouse);
        d3.selectAll('.data-svg').selectAll('circle').remove(); // remove old circles

        // add new circle
        d3.select('#data-svg-' + r)
            .append('circle')
            .attr('r', 3)
            .style('stroke', 'red')
            .attr('fill', 'red')
            .attr('cx', xSpark(pos[1]))
            .attr('cy', ySpark(pos[0]));
    })

结论

简言之,我不明白代码应该修改哪一点以及如何修改。 有人能帮助我吗?

编辑1

马克的答案解决了当用户悬停在文件地图矩形上时的问题。

但是当用户悬停在火花线上时,红色圆圈没有定位到正确的位置。 我希望这张图片可以说明问题。

enter image description here

我悬停在与意大利相关的火花线上,圆圈显示在线的上方而不是在线上。 此外,数据似乎混乱了。

编辑2

我测试了这里的代码(马克更新后的代码)。 我添加了一些console.log(d),当用户点击行和列标签时:

  var rowLabels = svg.append('g')
    .attr('class', 'rowLabels')
    .selectAll('.rowLabels')
    .data(regionsName)
    .enter().append('text')
    .text(function(d) {
      return d;
    })
    .attr('x', 0)
    .attr('y', function(d, i) {
      return i * cellSize;
    })
    .attr('transform', function(d, i) {
      return 'translate(-3, 11)';
    })
    .attr('class', 'rowLabel mono')
    .attr('id', function(d) {
      return 'rowLabel_' + regionsName.indexOf(d);
    })
    .attr('label-r', function(d) {
      return regionsName.indexOf(d);
    })
    .attr('font-weight', 'normal')
    .style('text-anchor', 'end')
    .on('click', function(d, i) {
      console.log(d); // <-- ADDDED
      rowSortOrder = !rowSortOrder;
      sortByValues('r', i, rowSortOrder);
    });

  // year labels
  var colLabels = svg.append('g')
    .attr('class', 'colLabels')
    .selectAll('.colLabels')
    .data(yearsName)
    .enter().append('text')
    .text(function(d) {
      return d;
    })
    .attr('transform', function(d, i) {
      return 'translate(' + (i * cellSize) + ', 2) rotate(-65)';
    })
    .attr('class', 'colLabel mono')
    .attr('id', function(d) {
      return 'colLabel_' + yearsName.indexOf(d);
    })
    .attr('label-c', function(d) {
      return yearsName.indexOf(d);
    })
    .attr('font-weight', 'normal')
    .style('text-anchor', 'left')
    .attr('dx', '.8em')
    .attr('dy', '.5em')
    .on('click', function(d, i) {
      console.log(d); // <-- ADDDED
      colSortOrder = !colSortOrder;
      sortByValues('c', i, colSortOrder);
    });

错误示例:当用户点击德国,然后是2000年,接着是2002年、2005年、2003年,最后是意大利时,结果如下:

enter image description here

可以看到,由于与Italy相关的小图表缺失了数据,所以迷你统计图和热力图不正确。

编辑3

我制作了这个 GIF,展示了问题所在:

enter image description here

一开始,德国有两个未知值(分别对应于2000年和2001年)。对应的迷你统计图是正确的。

当你单击Germany时,数据按降序排列,迷你统计图仍然正确。

然后单击年份2002,数据按降序排列,行重新排序,迷你统计图也是正确的。

然后单击2005,数据被排序,迷你统计图正确。

然后单击2003,一切都再次正确。

最后单击Italy,图表不再正确。德国有两个缺失的数据,但是在对应的迷你统计图上,这并没有突出显示。相反,这两个缺失的数据出现在了意大利的迷你统计图上。


我没有看到你的错误,还是我只是不理解它?在你的EDIT 1问题上,我可以修复它,但我没有看到像示例错误图片中的不匹配错误,即使我按照你的坐标点击了图表。 - KEKUATAN
@KEKUATAN 感谢您的帮助。我已经用一个 gif 修改了主要信息,希望能更好地阐明问题。 - user7558372
1个回答

7
这是一个新颖而整洁的可视化概念。如果我理解正确,我们需要在矩形和火花线之间获得完全对齐,无论排序如何,都需要进行鼠标悬停和工具提示。对于我的回答,我将使用你提供的plunkr(而不是Mark的版本)。如果我理解问题正确,那么就有五个问题需要解决:
1. 垂直排序会擦除鼠标悬停时使用的数据
首先,在垂直排序svg火花线时,你正在分配新的数据 - 这些正在覆盖路径的子数据,这就是为什么一旦垂直排序,当悬停在火花线上时鼠标悬停不起作用(圆圈紧贴每个SVG顶部)。例如,看看这个简化的示例:

var div = d3.select("body").append("div");
div.append("p").datum("hello").text(function(d) { return d; });
div.datum("new datum").select("p").text(function(d) { return d; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

这破坏了从路径数据中获取信息的代码部分,使用get_data_on_line()时,你现在只有路径的索引而不是所有点的数组。

这有些具有挑战性,可以通过不使用forEach循环来附加svg,而是使用适当的enter/update/exit循环并指定比线本身更多的数据(特别是,如果索引包含在数据中,这将更容易)来缓解这种情况。

话虽如此,最简单的方法是在不大修改代码的情况下替换以下内容:

var sortedSVG = d3.selectAll(".data-svg")
   .datum(function(d){
      return sorted.indexOf(+this.dataset.r)})
   .sort(function(a,b){
     return d3.ascending(a,b)
});

使用:

  sortedVertical.forEach(function(d) {
    d3.select("#data-svg-"+d).raise();
  })

这将按照所需的顺序排列SVG,并不会改变SVG或路径的数据。

我已将垂直和水平顺序拆分为两个单独的变量,有关详细信息,请参见第二条

  1. 每个矩形的mouseover使用固定的列/ x值

在矩形mouseover函数中设置sparkline上圆的坐标时,您使用固定值:

 .attr('cx', xSpark(idc))

idc 不变,尺度也不变。因此,无论矩形的重新排序如何,每个矩形都将继续突出显示sparkgraph的相同部分。为了解决这个问题,我们需要保留索引的水平排序。我使用了 sorted 变量,但给它一个范围,使得mouseover函数和排序函数都可以访问它:

var sorted = [0,1,2,3,4,5]; // base ordering

然后我可以设置矩形鼠标悬停以获取Sparklines上的正确列:
 .attr('cx', xSpark(sorted.indexOf(+idc))) 

请确保在排序函数中删除sorted的定义。因此,这也需要使用两个变量,一个用于水平排序,另一个用于垂直排序顺序,因为我们需要在鼠标悬停时按需获取水平排序顺序,而不仅仅是在排序函数中。

  1. 在垂直排序时,您正在移动节点,但没有考虑到它

通过对节点进行垂直排序,您会破坏排序水平方向上所需的路径顺序:

d3.selectAll('.sparkline-path')

这将按照DOM中出现的顺序选择元素,但是由于用户可能通过排序修改了此顺序,因此在使用上述选择的索引选择矩形时,索引不再指向正确的数据:

d3.selectAll('.sparkline-path')
 .attr('d', function(d, k) {
    var arr = [];     
    var sel = d3.selectAll('rect[data-r=\'' + (index) + '\']') 
 ....

相反,让我们将其改为:

.attr('d', function(d, k) {
    var index = d3.select(this).attr("id").split("-")[2]; // get the original index    
    var arr = [];       
    var sel = d3.selectAll('rect[data-r=\'' + index + '\']')  

索引来自SVG的id属性,这使我们可以使用正确的数据 - 如果索引来自排序后的SVG顺序,我们将遇到问题。

  1. 尽管路径改变了,火花线的路径中的数据保持不变。

在更新路径时,数据保持不变,但是数据用于计算要突出显示哪些正方形以及如何放置圆圈。每次修改火花线时,我们需要更新其数据。

d3.select(this).datum(result);
  1. 需要根据排序后的数据更新提示框位置

为了使工具提示正确对齐,我们需要考虑我们所做的重新排序:

而不是:

if(rect_r == r && rect_c == pos[1] ) {

确保在考虑顺序后比较每个相对位置:

if(rect_r == r && sorted.indexOf(+rect_c) == pos[1] ) {  

总的来说,根据我的计算,这会在大约11个地方更改代码(包括几个仅传递上述更改的更改)。我已经创建了一个更新后的plunkr:http://plnkr.co/edit/Bgbp7swTs98CMDFkSss3?p=preview

每个更改都清楚地标记出旧代码的注释和新代码的位置。


我想再强调一下,使用进入循环输入路径和SVG(或替代的


哈哈,猜猜看!我正在修复同样的问题。无论如何,干得好!+1 - Shashank
1
@Shashank Ha,上一次你确实赢了我,我相信下一次你也会赢。但是,我今晚已经结束了,所以不用担心我会打字什么 - 虽然看到其他答案正在被输入会很好,我已经错过了几秒钟。 - Andrew Reid
@AndrewReid 非常好的回答。我是回答OP关于垂直/水平排序的先前问题的那个人,我承认我没有将他们的代码重构为符合惯用的D3版本……事实上,在SO这里我有一个个人规则:如果回答需要花费我超过5分钟的时间(超过了这个时间,应该付费咨询!),我通常不会写出���案。这就是为什么我沿着代码的思路回答了之前的问题,也就是说,没有将其重构为符合惯用的D3,但已经知道在未来事情会变得更加复杂。这正是为什么你的回答如此优秀的原因。 - Gerardo Furtado
感谢@AndrewReid和@GerardoFurtado的帮助。我正在学习使用D3,有时会遇到困难而无法前进。但我必须承认我正在学到很多东西。谢谢! - user7558372

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接