The real time chart is a resuable Javascript component that accepts real time data. The purpose is to show the arrival time of real time events (or lack thereof), piped to the browser (for example via Websockets). Various attributes of each event can be articulated using size, color and opacity of the object generated for the event.
The component allows multiple asynchronous data streams to be viewed, each in a horizontal band across the SVG. New data streams can be added dynamically (as they are discovered by the caller over time), simply by calling the yDomain
method with the new array of data series names. The chart will automatically fit the new data series into the available space in the SVG.
The chart's time domain is moving with the passage of time. That means that any data placed in the chart eventually will age out and leave the chart. Currently, the chart history is capped at five minutes (but can be changed by modifying the component).
In addition to the main chart, the component also manages a navigation window with a viewport (using d3.brush) that can moved and sized to view an arbitrary portion of the time series data.
A nice future capability is to allow the caller to dynamically specify the type of object to be created for each data item. The infrastructure is in place to dynamically create different svg objects on the fly (using the document.createElementNS() function). However, given the current data binding mechanism (lacking a "key function"), the data is just "passing through" already created elements (not necessarily of the same type as what the is specified by the data).
Another nice capability would be to use d3 transitions to create smooth horizontal scrolling, as opposed to the current 200ms leftward jump. Any idea how to implement this is appreciated.
View the component in action at bl.ocks.org here, Repos: GitHub Gist here, GitHub here
The component is a derivative of D3 Based Real Time Chart.
The component adheres to the pattern described in Towards Reusable Chart.
Use the component like so:
// create the real time chart
var chart = realTimeChartMulti()
.width(900) // width in pixels of chart; mandatory
.height(350) // height in pixels of chart; mandatory
.yDomain(["Category1"]) // initial categories/data streams (note array), mandatory
.title("Chart Title") // optional
.yTitle("Categories") // optional
.xTitle("Time") // optional
.border(true); // optional
// invoke the chart
var chartDiv = d3.select("#viewDiv").append("div")
.attr("id", "chartDiv")
.call(chart);
// create data item
var obj = {
time: new Date().getTime(), // mandatory
category: "Category1", // mandatory
type: "rect", // optional (defaults to circle)
color: "red", // optional (defaults to black)
opacity: 0.8, // optional (defaults to 1)
size: 5, // optional (defaults to 6)
};
// send the data item to the chart
chart.datum(obj);
// to dynamically add a data series, just call yDomain with a new array of data series names
// (of course, all data passed to the chart will need to reference one of these categories)
// the data series can dynamically be (re-)sorted in arbitrary order; the chart will update accordingly
chart.yDomain(["Category1", "Category2"]);
xxxxxxxxxx
<html lang="en">
<head>
<meta charset="utf-8">
<!-- Author: Bo Ericsson, https://www.linkedin.com/in/boeric00/-->
<title>Real Time Chart Multi</title>
<link rel=stylesheet type=text/css href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.min.css" media="all">
<style>
.axis text {
font: 10px sans-serif;
}
.chartTitle {
font-size: 12px;
font-weight: bold;
text-anchor: middle;
}
.axis .title {
font-weight: bold;
text-anchor: middle;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.nav .area {
fill: lightgrey;
stroke-width: 0px;
}
.nav .line {
fill: none;
stroke: darkgrey;
stroke-width: 1px;
}
.viewport {
stroke: grey;
fill: black;
fill-opacity: 0.3;
}
.viewport .extent {
fill: green;
}
.well {
padding-top: 0px;
padding-bottom: 0px;
}
</style>
</head>
<body>
<div style="max-width: 900px; max-height: 400px; padding: 10px">
<div class="well">
<h4>D3 Based Real Time Chart with Multiple Data Streams</h4>
</div>
<input id="debug" type="checkbox" name="debug" value="debug" style="margin-bottom: 10px" /> Debug
<input id="halt" type="checkbox" name="halt" value="halt" style="margin-bottom: 10px" /> Halt
<div id="viewDiv"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="realTimeChartMulti.js"></script>
<script>
'use strict';
// Create the real time chart
var chart = realTimeChartMulti()
.title("Chart Title")
.yTitle("Categories")
.xTitle("Time")
.yDomain(["Category1"]) // initial y domain (note array)
.border(true)
.width(900)
.height(350);
// Invoke the chart
var chartDiv = d3.select("#viewDiv").append("div")
.attr("id", "chartDiv")
.call(chart);
// Alternative and equivalent invocation:
// chart(chartDiv);
// Event handler for debug checkbox
d3.select("#debug").on("change", function() {
var state = d3.select(this).property("checked")
chart.debug(state);
})
// Event handler for halt checkbox
d3.select("#halt").on("change", function() {
var state = d3.select(this).property("checked")
chart.halt(state);
})
// Configure the data generator
// Mean and deviation for generation of time intervals
var tX = 5; // time constant, multiple of one second
var meanMs = 1000 * tX; // milliseconds
var dev = 200 * tX; // std dev
// Define time scale
var timeScale = d3.scale.linear()
.domain([300 * tX, 1700 * tX])
.range([300 * tX, 1700 * tX])
.clamp(true);
// Define function that returns normally distributed random numbers
var normal = d3.random.normal(meanMs, dev);
// Define color scale
var color = d3.scale.category10();
// In a normal use case, real time data would arrive through the network or some other mechanism
var d = -1;
var shapes = ["rect", "circle"];
var timeout = 0;
// Define the data generator
function dataGenerator() {
setTimeout(function() {
// Add categories dynamically
d++;
switch (d) {
case 5:
chart.yDomain(["Category1", "Category2"]);
break;
case 10:
chart.yDomain(["Category1", "Category2", "Category3"]);
break;
default:
}
// Output a sample for each category, each interval (five seconds)
chart.yDomain().forEach(function(cat, i) {
// Create randomized timestamp for this category data item
var now = new Date(new Date().getTime() + i * (Math.random() - 0.5) * 1000);
// Create new data item
var obj;
var doSimple = false;
if (doSimple) {
obj = {
// Simple data item (simple black circle of constant size)
time: now,
color: "black",
opacity: 1,
category: "Category" + (i + 1),
type: "circle",
size: 5,
};
} else {
obj = {
// Complex data item; four attributes (type, color, opacity and size) are changing dynamically with each iteration (as an example)
time: now,
color: color(d % 10),
opacity: Math.max(Math.random(), 0.3),
category: "Category" + (i + 1),
// type: shapes[Math.round(Math.random() * (shapes.length - 1))], // the module currently doesn't support dynamically changed svg types (need to add key function to data, or method to dynamically replace svg object – tbd)
type: "circle",
size: Math.max(Math.round(Math.random() * 12), 4),
};
}
// Send the datum to the chart
chart.datum(obj);
});
// Drive data into the chart at average interval of five seconds
// here, set the timeout to roughly five seconds
timeout = Math.round(timeScale(normal()));
// Do forever
dataGenerator();
}, timeout);
}
// Start the data generator
dataGenerator();
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js