Question

I have a d3 chart displaying three lines, where each line has its own y-axis. It also has a x-axis for displaying time. All axes has zoom / pan on them. enter image description here This works fine for displaying historic data, but I also need a button to start displaying real time data.

I have a button triggering SignalR, which again gives me new data that I can push onto my data arrays.

My question is how I can update these three lines and move them in a horizontal direction every time the arrays are updated with the new data values. I've tried following this and this guide, but all I really end up doing is redrawing the entire line on top of the old one. (I'm only trying to update one of them at the moment)

Before displaying my chart code, I should mention that my array is updated every second with new values (This happens in a separate js file, included in the same view):

speed.push(entry.Speed);
redraw();

<script>
    /* d3 vars */
    var graph;
    var m = [];
    var w;
    var h;

    /* d3 scales */
    var x;
    var y1;
    var y2;
    var y3;

    /* d3 axes */
    var xAxis;
    var yAxisLeft;
    var yAxisLeftLeft;
    var yAxisRight;

    /* d3 lines */
    var line1;
    var line2;
    var line3;

    /* d3 zoom */
    var zoom;
    var zoomLeftLeft;
    var zoomRight;

    /* Data */
    var speed = [];
    var depth = [];
    var weight = [];
    var timestamp = [];

    var url = '@Url.Action("DataBlob", "Trend", new {id = Model.Unit.UnitId, runId = Request.Params["runId"]})';

    var data = $.getJSON(url, null, function(data) {
        var list = JSON.parse(data);
        var format = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
        if ($("#IsLiveEnabled").val() != "true") {
            list.forEach(function(d) {
                speed.push(d.Speed);
                depth.push(d.Depth);
                weight.push(d.Weight);
                var date = format(d.Time);
                d.Time = date;
                timestamp.push(d.Time);
            });
        }

        $('#processing').hide();

        m = [20, 85, 30, 140]; // margins: top, right, bottom, left
        w = ($("#trendcontainer").width() - 35) - m[1] - m[3]; // width
        h = 600 - m[0] - m[2]; // height

        x = d3.time.scale().domain(d3.extent(timestamp, function(d) {
            return d;
        })).range([0, w]);

        y1 = d3.scale.linear().domain([0, d3.max(speed)]).range([h, 0]);
        y2 = d3.scale.linear().domain([0, d3.max(depth)]).range([h, 0]);
        y3 = d3.scale.linear().domain([0, d3.max(weight)]).range([h, 0]);

        line1 = d3.svg.line()
            .interpolate("basis")
            .x(function(d, i) {
                return x(timestamp[i]);
            })
            .y(function(d) {
                return y1(d);
            });

        line2 = d3.svg.line()
            .interpolate("basis")
            .x(function(d, i) {
                return x(timestamp[i]);
            })
            .y(function(d) {
                return y2(d);
            });

        line3 = d3.svg.line()
            .interpolate("basis")
            .x(function(d, i) {
                return x(timestamp[i]);
            })
            .y(function(d) {
                return y3(d);
            });

        zoom = d3.behavior.zoom()
            .x(x)
            .y(y1)
            .scaleExtent([1, 35])
            .on("zoom", zoomed);

        zoomLeftLeft = d3.behavior.zoom()
            .x(x)
            .y(y3)
            .scaleExtent([1, 35])
            .on("zoom", zoomed);

        zoomRight = d3.behavior.zoom()
            .x(x)
            .y(y2)
            .scaleExtent([1, 35])
            .on("zoom", zoomed);

        // Add an SVG element with the desired dimensions and margin.
        graph = d3.select(".panel-body").append("svg:svg")
            .attr("width", w + m[1] + m[3])
            .attr("height", h + m[0] + m[2])
            .call(zoom)
            .append("svg:g")
            .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

        graph.append("defs").append("clipPath")
            .attr("id", "clip")
            .append("rect")
            .attr("width", w)
            .attr("height", h);

        // create xAxis
        xAxis = d3.svg.axis().scale(x).tickSize(-h).tickSubdivide(false);
        // Add the x-axis.
        graph.append("svg:g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + h + ")")
            .call(xAxis);

        // create left yAxis
        yAxisLeft = d3.svg.axis().scale(y1).ticks(10).orient("left");
        // Add the y-axis to the left
        graph.append("svg:g")
            .attr("class", "y axis axisLeft")
            .attr("transform", "translate(-25,0)")
            .call(yAxisLeft)
            .append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 5)
            .attr("dy", ".71em")
            .style("text-anchor", "end")
            .text("Speed (m/min)");

        // create leftleft yAxis
        yAxisLeftLeft = d3.svg.axis().scale(y3).ticks(10).orient("left");
        // Add the y-axis to the left
        graph.append("svg:g")
            .attr("class", "y axis axisLeftLeft")
            .attr("transform", "translate(-85,0)")
            .call(yAxisLeftLeft)
            .append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 5)
            .attr("dy", ".71em")
            .style("text-anchor", "end")
            .text("Weight (kg)");

        // create right yAxis
        yAxisRight = d3.svg.axis().scale(y2).ticks(10).orient("right");
        // Add the y-axis to the right
        graph.append("svg:g")
            .attr("class", "y axis axisRight")
            .attr("transform", "translate(" + (w + 25) + ",0)")
            .call(yAxisRight)
            .append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", -15)
            .attr("dy", ".71em")
            .style("text-anchor", "end")
            .text("Depth (m)");

        graph.append("svg:path").attr("d", line1(speed)).attr("class", "y1").attr("clip-path", "url(#clip)");
        graph.append("svg:path").attr("d", line2(depth)).attr("class", "y2").attr("clip-path", "url(#clip)");
        graph.append("svg:path").attr("d", line3(weight)).attr("class", "y3").attr("clip-path", "url(#clip)");
    });

    function redraw() {
        graph.append("svg:path")
            .attr("d", line1(speed))
            .attr("class", "y1")
            .attr("clip-path", "url(#clip)");
    }

    function zoomed() {
        zoomRight.scale(zoom.scale()).translate(zoom.translate());
        zoomLeftLeft.scale(zoom.scale()).translate(zoom.translate());

        graph.select(".x.axis").call(xAxis);
        graph.select(".y.axisLeft").call(yAxisLeft);
        graph.select(".y.axisLeftLeft").call(yAxisLeftLeft);
        graph.select(".y.axisRight").call(yAxisRight);
        graph.select(".x.grid")
            .call(make_x_axis()
                .tickFormat(""));
        graph.select(".y.axis")
            .call(make_y_axis()
                .tickSize(5, 0, 0));
        graph.selectAll(".y1")
            .attr("d", line1(speed));
        graph.selectAll(".y2")
            .attr("d", line2(depth));
        graph.selectAll(".y3")
            .attr("d", line3(weight));
    };

    var make_x_axis = function() {
        return d3.svg.axis()
            .scale(x)
            .orient("bottom")
            .ticks(10);
    };

    var make_y_axis = function() {
        return d3.svg.axis()
            .scale(y1)
            .orient("left")
            .ticks(10);
    };

</script>

I do realise that it's not coming out right, but I can't seem to figure out how to append the new values to the end of the drawn line and move it.

Was it helpful?

Solution

Here I made a very basic graph with a button that does what you want. It is a little choppy as there are some dates missing. I hope it can help you, it is based on one of the simple samples at http://bl.ocks.org/mbostock

Here is the relevant part:

    //Add new post to data
data.push({
    "Date": new Date(new Date().setDate(new Date().getDate() + skip)),
        "Close": price
});

//assign the new data to the graph
svg.select(".area")
    .datum(data)
    .attr("d", area)
    .attr("transform", null);

//Make sure the X axis is updated (not updating the Y axis in the example, but same principle)
x.domain(d3.extent(data.map(function (d) {
    return d.Date;
})));
svg.select(".x.axis").transition().call(xAxis);

data.shift();

var skip = 0;
var oldPrice = 11;

var data = [{
    "Date": "2015-08-20",
        "Close": 7
}, {
    "Date": "2015-08-23",
        "Close": 8
}, {
    "Date": "2015-08-24",
        "Close": 9
}, {
    "Date": "2015-08-25",
        "Close": 6
}, {
    "Date": "2015-08-26",
        "Close": 5
}, {
    "Date": "2015-08-27",
        "Close": 7
}, {
    "Date": "2015-08-30",
        "Close": 5
}, {
    "Date": "2015-08-31",
        "Close": 9
}, {
    "Date": "2015-09-01",
        "Close": 8
}, {
    "Date": "2015-09-02",
        "Close": 10
}, {
    "Date": "2015-09-03",
        "Close": 11
}, {
    "Date": "2015-09-07",
        "Close": 12
}, {
    "Date": "2015-09-08",
        "Close": 11
}, {
    "Date": "2015-09-09",
        "Close": 12
}, {
    "Date": "2015-09-10",
        "Close": 13
}, {
    "Date": "2015-09-13",
        "Close": 14
}, {
    "Date": "2015-09-14",
        "Close": 15
}, {
    "Date": "2015-09-15",
        "Close": 13
}, {
    "Date": "2015-09-16",
        "Close": 11
}, {
    "Date": "2015-09-17",
        "Close": 7
}, {
    "Date": "2015-09-20",
        "Close": 6
}, {
    "Date": "2015-09-21",
        "Close": 5
}, {
    "Date": "2015-09-22",
        "Close": 6
}, {
    "Date": "2015-09-23",
        "Close": 7
}, {
    "Date": "2015-09-24",
        "Close": 8
}, {
    "Date": "2015-09-27",
        "Close": 10
}, {
    "Date": "2015-09-28",
        "Close": 9
}, {
    "Date": "2015-09-29",
        "Close": 10
}, {
    "Date": "2015-09-30",
        "Close": 11
}];


var margin = {
    top: 10,
    right: 10,
    bottom: 100,
    left: 40
};
var width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;


var parseDate = d3.time.format("%Y-%m-%d").parse;

var x = d3.time.scale().range([0, width]),

    y = d3.scale.linear().range([height, 0]);

var xAxis = d3.svg.axis().scale(x).orient("bottom"),
    yAxis = d3.svg.axis().scale(y).orient("left");

var area = d3.svg.area()
    .interpolate("monotone")
    .x(function (d) {
    return x(d.Date);
})
    .y0(height)
    .y1(function (d) {
    return y(d.Close);
});


var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

svg.append("defs").append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("width", width)
    .attr("height", height);

var focus = svg.append("g")
    .attr("class", "focus")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


data.forEach(function (d) {
    d.Date = parseDate(d.Date);
    d.Close = +d.Close;

});

x.domain(d3.extent(data.map(function (d) {
    return d.Date;
})));
y.domain([0, 200]);

focus.append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

focus.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

focus.append("g")
    .attr("class", "y axis")
    .call(yAxis);

function update() {

    //Generate a new price
    var price = Math.floor(Math.random() * 5) + oldPrice

    //Add new post to data
    data.push({
        "Date": new Date(new Date().setDate(new Date().getDate() + skip)),
            "Close": price
    });

    //assign the new data to the graph
    svg.select(".area")
        .datum(data)
        .attr("d", area)
        .attr("transform", null);

    //Make sure the X axis is updated (not updating the Y axis in the example, but same principle)
    x.domain(d3.extent(data.map(function (d) {
        return d.Date;
    })));
    svg.select(".x.axis").transition().call(xAxis);

    data.shift();

    skip++;
    oldPrice = price;
}


$(document).ready(function () {

    $('#update').click(function () {
        update();

    });

});
svg {
    font: 10px sans-serif;
}
.area {
    fill: steelblue;
    clip-path: url(#clip);
}
.axis path, .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
}
.brush .extent {
    stroke: #fff;
    fill-opacity: .125;
    shape-rendering: crispEdges;
}
<html>
<head>  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
</head>
<body>
<input type="button" id="update" value="update">
</body>
</html>

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top