简体   繁体   中英

d3 draw animated line graph basic example

I've seen an example of how to draw a line chart here . I've tried to replicate their example from the "Fixing for arbitrary datasets" section, but I can't get it to work.

Hopefully someone can explain to me what I have done wrong.

Here is the code I used:

<!DOCTYPE html>

<head>
    <meta charset="utf-8">

    <script src="js/d3.min.js" charset="utf-8"></script>
    <script src="js/jquery-1.11.2.min.js" charset="utf-8"></script>

    <style>

        .axis text {
          font: 10px sans-serif;
        }

        .axis path,
        .axis line {
          fill: none;
          stroke: #000;
          shape-rendering: crispEdges;
        }

        .line {
            fill: none;
            stroke: steelblue;
            stroke-width: 1.5px;
        }

    </style>

</head>

<body>

    <table class="small-chart">
        <tr>
            <td id="chart_str"></td>
        </tr>
    </table>

</body>

<script>

    var margin = {top: 20, right: 30, left: 30, bottom: 20}
        height = 300 - margin.top - margin.bottom,
        width = 300 - margin.left - margin.right;

    var data = [{"x":100, "y":0}, {"x":110, "y":10}, {"x":120, "y":20}, {"x":130, "y":30}]

    var x = d3.scale.linear()
        .domain(d3.extent(data, function (d) { return d.x; }))
        .range([0, width]);

    var y = d3.scale.linear()
        .domain(d3.extent(data, function (d) { return d.y; }))
        .range([height, 0]);

    var lineFunction = d3.svg.line()
      .x(x, function (d) { return d.x; })
      .y(y, function (d) { return d.y; });

    var svg = d3.select('#chart_str').append('svg')

    svg.append('path')
        .attr('class', 'line')
        .transition()
        .duration(3000)
        .attrTween('d', getSmoothInterpolation(data));


    function getSmoothInterpolation(iData) {
        return function (d, i, a) {
            var interpolate = d3.scale.linear()
                .domain([0, 1])
                .range([1, iData.length + 1]);

            return function(t) {
                var flooredX = Math.floor(interpolate(t));
                var weight = interpolate(t) - flooredX;
                var interpolatedLine = iData.slice(0, flooredX);

                    if(flooredX > 0 && flooredX < 31) {
                        var weightedLineAverage = iData[flooredX].y * weight + iData[flooredX-1].y * (1-weight);
                        interpolatedLine.push({"x":interpolate(t)-1, "y":weightedLineAverage});
                    }

                return lineFunction(interpolatedLine);
            }
        }
    }

</script>

I keep getting the below error in the console:

Error: Problem parsing d="MNaN,NaNLNaN,NaN" test_line.html:1

Error: Problem parsing d="MNaN,NaNLNaN,NaNLNaN,NaN" test_line.html:1

Error: Problem parsing d="MNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaN" test_line.html:1

Uncaught TypeError: Cannot read property 'y' of undefined test_line.html:91

Here's a fiddle: http://jsfiddle.net/henbox/t3fam08j/1/ . Note that I've added a few extra data points to make it easier to see what's going on.

As per @user1614080's comment, start by getting the basic line chart to draw, before moving on to transition\\ interpolation bits.

For that, you need to make sure you apply the x and y scales to the points in lineFunction , so change:

var lineFunction = d3.svg.line()
  .x(x, function (d) { return d.x; })
  .y(y, function (d) { return d.y; });

to

var lineFunction = d3.svg.line()
  .x(function (d) { return x(d.x); })
  .y(function (d) { return y(d.y); });

Also define the width and height of your svg, to ensure that the line will fit:

.append('svg')
    .attr("width", width)
    .attr("height", height)
    ...

The error: "Uncaught TypeError: Cannot read property 'y' of undefined" comes from the fact that flooredX can be greater than the number of elements in iData + 1, so iData[flooredX] does not exist and iData[flooredX].y is undefined .

The interpolation function has this weird " if (flooredX > 0 && flooredX < 31) " line, where 31 is hardcoded (I can't say why exactly, I think the data in the exanple has exactly 30 points). If you replace that 31 with the iData.length you'll handle the case:

if (flooredX > 0 && flooredX < iData.length) {
    ...
}

A final change you'll need to make is when you push points to the interpolatedLine array. In your current code you have:

interpolatedLine.push({
    "x": interpolate(t) - 1,
    ...

This will push x values between 0 and iData.length (ie the count of x/y value pairs in your data), rather than values you actually want to pass ( x between 100 and 160)

The assumption, I think, for the example you link to is that x values in the raw data will just start at 0 and run to 30: Not very helpful for your example. A hacky way to get round this would be to hard-code the transform you need to go from x values 1, 2, 3... to 110, 120, 130... , so change:

"x": interpolate(t) - 1,

to

"x": 10* (interpolate(t) - 1) + 100,

This is not a nice way of doing things because it hard-codes an assumption about the x-values. I'm sure there's a better way

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM