消除D3基线图转换中的突然添加/删除

时间:2016-09-24 11:58:42

标签: javascript d3.js transition interpolation data-visualization

我有完全相似的问题

Eliminate sudden additions/deletions in D3 line chart transition

一个不同之处在于,我将线插入为"基础"

var line = d3.svg.line().interpolate('basis')

正好挣扎了13个小时:/请帮忙



<html>
    <head>
        <title>Chart</title>
        <style>
            path {
                stroke: #f00;
            }
            .line {
                stroke: #0f0;
                fill: none;
                stroke-width: 2px;
            }
            .rule {
                stroke: #ccc;
                stroke-width: 1px;
            }
        </style>
    </head>
    <body>
        <p>I want to get the chart below to transition such that
        the points on the lines appear to move up and down, not
        side to side.
        </p>
        <p>When transitioning to the smaller data-set especially,
        I'd like to not have a white gap appear before the lines
        take shape.
        </p>
        <p>Also, the grid-lines should slide into and out of
        existence, rather than appearing or disappearing.  Ideas?
        </p>
        <script src="http://d3js.org/d3.v2.min.js"></script>
        <script>
        var data = [
            [1,8,8,8,8,8,8,8,8,8,8,8,8,8,8],
            [8,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
        ];
        var data3 = [
            [1,1,1],
            [8,8,8],
        ];
        
        var w = 500,
            h = 100;

        var chart = d3.select('body').append('div')
            .attr('class', 'chart')
            .append('svg:svg')
            .attr('width', w)
            .attr('height', h);

        var color = d3.scale.category10();

        // Add path interpolator to d3
        d3.interpolators.push(function(a, b) {
          debugger;
          var isPath, isArea, interpolator, ac, bc, an, bn, d;
        
          // Create a new array of a given length and fill it with the given value
          function fill(value, length) {
            return d3.range(length)
              .map(function() {
                return value;
              });
          }
        
          // Extract an array of coordinates from the path string
          function extractCoordinates(path) {
            return path.substr(1, path.length - (isArea ? 2 : 1)).split('L');
          }
        
          // Create a path from an array of coordinates
          function makePath(coordinates) {
            return 'M' + coordinates.join('L') + (isArea ? 'Z' : '');
          }
        
          // Buffer the smaller path with coordinates at the same position
          function bufferPath(p1, p2) {
            var d = p2.length - p1.length;
        
            if (isArea) {
              return fill(p1[0], d/2).concat(p1, fill(p1[p1.length - 1], d/2));
            } else {
              return fill(p1[0], d).concat(p1);
            }
          }
        
          isPath = /M-?\d*\.?\d*,-?\d*\.?\d*(L-?\d*\.?\d*,-?\d*\.?\d*)*Z?/;
        
          if (isPath.test(a) && isPath.test(b)) {
            isArea = a[a.length - 1] === 'Z';
            ac = extractCoordinates(a);
            bc = extractCoordinates(b);
            an = ac.length;
            bn = bc.length;
        
            if (an > bn) {
              bc = bufferPath(bc, ac);
            }
        
            if (bn > an) {
              ac = bufferPath(ac, bc);
            }
        
            // Create an interpolater with the buffered paths (if both paths are of the same length,
            // the function will end up being the default string interpolator)
            interpolator = d3.interpolateString(bn > an ? makePath(ac) : a, an > bn ? makePath(bc) : b);
        
            // If the ending value changed, make sure the final interpolated value is correct
            return bn > an ? interpolator : function(t) {
              return t === 1 ? b : interpolator(t);
            };
          }
        });
        
        function drawdata(data, chart) {
            var num = data[0].length-1;
            var x = d3.scale.linear().domain([0, num]).range([0,w]);
            var y = d3.scale.linear().domain([0, 10]).range([h, 0]);
            
            var line = d3.svg.line().interpolate('basis')
                .x(function(d, i) { return x(i); })
                .y(function(d) { return y(d); });
            
            var flat = d3.svg.line()
                .x(function(d, i) { return x(i); })
                .y(y(-1));
            
            var lines = chart.selectAll('.line')
                .data(data);
            
            lines.enter().append('path')
                    .attr('class', 'line')
                    .style('stroke', function(d,i) { return color(i); })
                    .attr('d', line);
            
            lines.transition()
                .ease('linear')
                .duration(500)
                .attr('d', line);
            
            lines.exit().remove();
            
            // legend
            var ticks = chart.selectAll('line')
                .data(x.ticks(num));
            
            ticks.enter().append('line')
                    .attr('x1', x)
                    .attr('x2', x)
                    .attr('y1', 0)
                    .attr('y2', h)
                    .attr('class', 'rule');
            ticks.transition()
                .ease('linear')
                .duration(500)
                .attr('x1', x)
                .attr('x2', x)
                .attr('y1', 0)
                .attr('y2', h);
            ticks.exit().remove();
        }
        var dats = [data, data3];
        function next() {
            var it = dats.shift();
            dats.push(it);
            drawdata(it, chart);
        }
        setInterval(next, 2000);
        next();
        </script>
    </body>
</html
&#13;
&#13;
&#13;

Codepen

3 个答案:

答案 0 :(得分:3)

部分答案:对于网格线,使用大于width的值设置“输入”和“退出”选择:

<html>
    <head>
        <title>Chart</title>
        <style>
            path {
                stroke: #f00;
            }
            .line {
                stroke: #0f0;
                fill: none;
                stroke-width: 2px;
            }
            .rule {
                stroke: #ccc;
                stroke-width: 1px;
            }
        </style>
    </head>
    <body>
        <script src="https://d3js.org/d3.v3.min.js"></script>
        <script>
        var data = [
            [0,2,3,2,8],
            [2,4,1,5,3],
        ];
        var data2 = [
            [0,1,2,3,4,5],
            [9,8,7,6,5,6],
        ];
        var data3 = [
            [1,3,2],
            [0,8,5],
        ];
        
        var w = 300,
            h = 100;

        var chart = d3.select('body').append('div')
            .attr('class', 'chart')
            .append('svg:svg')
            .attr('width', w)
            .attr('height', h);

        var color = d3.scale.category10();
        
        function drawdata(data, chart) {
            var num = data[0].length-1;
            var x = d3.scale.linear().domain([0, num]).range([0,w]);
            var y = d3.scale.linear().domain([0, 10]).range([h, 0]);
            
            var line = d3.svg.line().interpolate('basis')
                .x(function(d, i) { return x(i); })
                .y(function(d) { return y(d); });
            
            var flat = d3.svg.line()
                .x(function(d, i) { return x(i); })
                .y(y(-1));
            
            var lines = chart.selectAll('.line')
                .data(data);
            
            lines.enter().append('path')
                    .attr('class', 'line')
                    .style('stroke', function(d,i) { return color(i); })
                    .attr('d', line);
            
            lines.transition()
                .ease('linear')
                .duration(500)
                .attr('d', line);
            
            lines.exit().remove();
            
            // legend
            var ticks = chart.selectAll('line')
                .data(x.ticks(num));
            
            ticks.enter().append('line')
            .attr('x1', w+10)
                    .attr('x2', w+10)
                    .attr('y1', 0)
                    .attr('y2', h)
                    .attr('class', 'rule')
                    .transition()
                    .duration(500)
                    .attr('x1', x)
                    .attr('x2', x)
                    .attr('y1', 0)
                    .attr('y2', h);
                    
          
            ticks.transition()
                .ease('linear')
                .duration(500)
                .attr('x1', x)
                .attr('x2', x)
                .attr('y1', 0)
                .attr('y2', h);
            
          ticks.exit().transition()
          .duration(500)
          .attr('x1', w+10)
          .attr('x2', w+10)
          .attr('y1', 0)
          .attr('y2', h)
          .remove();
        }
        var dats = [data, data2, data3];
        function next() {
            var it = dats.shift();
            dats.push(it);
            drawdata(it, chart);
        }
        setInterval(next, 2000);
        next();
        </script>
    </body>
</html>

答案 1 :(得分:1)

我相信d3有更清洁的解决方案。请参阅var points0 = [ [1,8,8,8,8,8,8,8,8,8,8,8,8,8,8], [8,1,1,1,1,1,1,1,1,1,1,1,1,1,1], ]; var points1 = [ [1,1,1], [8,8,8], ]; var w = 500, h = 100; var chart = d3.select('body').append('div') .attr('class', 'chart') .append('svg') .attr('width', w) .attr('height', h) var color = d3.scale.category10(); function drawdata(data, svg) { var num = data[0].length-1; var x = d3.scale.linear().domain([0, num]).range([0,w]); var y = d3.scale.linear().domain([0, 10]).range([h, 0]); var line = d3.svg.line().interpolate('basis') .x(function(d, i) { return x(i); }) .y(function(d) { return y(d); }); var flat = d3.svg.line() .x(function(d, i) { return x(i); }) .y(y(-1)); var lines = chart.selectAll('path.line') .data(data); lines .enter() .append('path') .attr('class', 'line') .style('stroke', (d,i) => color(i)) lines // .attr('d', line) .transition() .ease('linear') .duration(500) .attrTween("d", function(d) { return pathTween(line(d), 4, this)}) lines .exit() .remove(); // legend var ticks = chart.selectAll('line') .data(x.ticks(num)); ticks.enter() .append('line') .attr('x1', w+10) // HACK .attr('x2', w+10) // HACK .attr('y1', 0) .attr('y2', h) .attr('class', 'rule') ticks.transition() .ease('linear') .duration(500) .attr('x1', x) .attr('x2', x) .attr('y1', 0) .attr('y2', h) ticks.exit().remove(); } const data = [points0, points1] setInterval(() => { const point = data[0] data.reverse() drawdata(point, chart) }, 1e3); function pathTween(d1, precision, path0) { var path1 = path0.cloneNode(), n0 = path0.getTotalLength(), n1 = (path1.setAttribute("d", d1), path1).getTotalLength(); // Uniform sampling of distance based on specified precision. var distances = [0], i = 0, dt = precision / Math.max(n0, n1); while ((i += dt) < 1) { distances.push(i); } distances.push(1); // Compute point-interpolators at each distance. var points = distances.map(function(t) { var p0 = path0.getPointAtLength(t * n0), p1 = path1.getPointAtLength(t * n1); return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]); }); return function(t) { return "M" + points.map(function(p) { return p(t); }).join("L"); }; }

&#13;
&#13;
path {
    stroke: #f00;
}
.line {
    stroke: #0f0;
    fill: none;
    stroke-width: 2px;
}
.rule {
    stroke: #ccc;
    stroke-width: 1px;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
&#13;
{{1}}
&#13;
&#13;
&#13;

答案 2 :(得分:1)

使用Peter Beshai的d3-line-chunked!它完全符合您的需求:&#34;创建指示缺口数据缺失的行或不同样式的块/线段。&#34;

enter image description here

另见博客文章,他解释了同一问题的各种方法:how to display missing data