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

时间:2018-04-23 13:42:42

标签: d3.js data-visualization

我几天前创建了this question关于当用户选择更改数据顺序时迷你图的排序。答案已经解决了问题,但仍保持红色圆圈的正确位置,突出显示用户放置鼠标的位置。

这是代码: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是迷你线),它附加一个圆圈,将其定位在{{1} },cx依赖于cyxSpark。 如果点 1 中提到的那个是真的(即ySparkxSpark是"固定"值),那么即使这段代码也没有必须在数据顺序更改时进行更改。

ySpark

(3)当数据顺序发生变化时,也不需要更改这段代码。

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));
    })
应该更新

(4) 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; }); ,但事实并非如此。在排序之前,data按照正确的顺序包含数据,在排序后,data尚未更改,它应该,不是吗?

data我认为不应该更改poscx,因为它们取决于cy / xSpark和{{1} }}

ySpark

结论

简而言之,我不明白应该修改代码的哪一点以及如何修改代码。 有谁知道如何帮助我?

编辑1

Mark的回答解决了用户将鼠标悬停在filemap矩形上的问题。

但是当用户将鼠标悬停在迷你图上时,红色圆圈未定位在正确的位置。 我希望这张图片可以澄清问题所在。

enter image description here

我徘徊在与意大利有关的迷你线上,并且圆圈不显示在线上但在上方。 此外,数据似乎混淆了。

编辑2

我测试了代码here(标记了更新后的代码)。 当用户单击行和列标签时,我修改了添加一些pos的代码:

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

错误示例:当用户点击德国,然后是2000年,然后是2002年,然后是2005年,然后是2003年,最后是意大利,这就是结果:

enter image description here

正如您所看到的那样,迷你图和热图不正确,因为与console.log(d)相关联的迷你线缺少数据,实际上它确实没有。

编辑3

我创建了这个gif,显示了问题所在:

enter image description here

最初,德国有两个未知值(与2000年和2001年有关)。相应的迷你线是正确的。

当您点击 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); }); 时,数据按降序排序,且迷你图仍然正确。

然后单击年份Italy,数据按降序排序,然后按正确的顺序重新定位行。迷你线是正确的。

然后点击Germany,数据排序,迷你图正确。

然后点击2002,一切都再次正确。

最后点击2005,图表不再正确。德国有两个缺失的数据,但从相应的火花线,这没有突出显示。相反,这两个缺失的数据是意大利的亮点。

1 个答案:

答案 0 :(得分:8)

这是一个新颖而简洁的可视化概念。如果我理解正确,我们需要在矩形和迷你图,鼠标悬停和工具提示之间进行完全对齐,而不管排序如何。对于我的回答,我将使用您首先链接的plunkr(不是Mark的版本)。如果我正确理解了这个问题,那么有五个问题需要解决:

  1. 垂直排序会删除鼠标悬停中使用的数据
  2. 首先,在垂直排序你的svg迷你图时,你正在分配新的基准 - 这些会覆盖路径的子基准面,这就是为什么一旦垂直排序,鼠标悬停在悬停在迷你图上时就不起作用了(圆圈拥抱)每个svg的顶部)。例如,请参见此简化示例:

    &#13;
    &#13;
    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; });
    &#13;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
    &#13;
    &#13;
    &#13;

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

    这是一个挑战,可以通过不使用forEach循环来附加svg来缓解这种情况,而是使用正确的输入/更新/退出循环来实现此目的并指定更多数据而不仅仅是数组对于线本身(具体来说,如果索引包含在数据中这将更容易)。

    话虽如此,在不修改代码的情况下执行此操作的最简单方法是替换此:

    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. 每个rect的鼠标悬停使用固定的列/ x值
    2. 在矩形鼠标悬停功能的迷你线上设置圆圈坐标时,使用固定值:

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

      idc不会改变,规模也不会改变。因此,无论矩形的重新排序如何,每个矩形将继续突出显示闪光图的相同部分。要解决这个问题,我们需要保留索引的水平排序。我使用了sorted变量,但为它提供了一个范围,以便鼠标悬停功能和排序功能都可以访问它:

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

      然后我可以设置矩形鼠标悬停以获得迷你图上的正确列:

       .attr('cx', xSpark(sorted.indexOf(+idc))) 
      

      确保在排序功能中删除sorted的定义。 因此,这还需要使用两个变量,一个用于水平,另一个用于垂直排序,因为我们需要按需处理水平排序,而不仅仅是排序功能

      1. 垂直排序时,您正在移动节点,但不会对其进行说明
      2. 通过垂直排序节点,您可以在水平排序时中断路径所需的顺序:

        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. 迷你图的基准&#39;即使路径发生变化,路径仍然保持不变。
        2. 更新路径时数据保持不变,但数据用于计算要突出显示的方块以及如何放置圆。每次修改时我们都需要更新火花线的基准面:

          d3.select(this).datum(result);
          
          1. 需要根据排序数据更新工具提示位置
          2. 为了使工具提示正确对齐,我们需要考虑我们已经完成的重新排序:

            而不是:

            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(或者g s)以及更具描述性的数据绑定将允许更容易的排序,更新,并且最有可能更容易解决问题。输入循环似乎适用于矩形,但我不确定为什么路径的方法不同。想到这个answer