|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
body { |
|
font-family: 'Catamaran', sans-serif; |
|
margin: 20px; |
|
top: 20px; |
|
right: 20px; |
|
bottom: 20px; |
|
left: 20px; |
|
} |
|
|
|
.domain, |
|
.x-axis { |
|
stroke: grey; |
|
stroke-width: 1px; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
line.major-tick { |
|
stroke: grey; |
|
} |
|
|
|
text.major-tick { |
|
fill: grey; |
|
font-size: 10px; |
|
} |
|
|
|
.major-tick { |
|
text-anchor: middle; |
|
} |
|
|
|
.minor-tick { |
|
opacity: 0 |
|
} |
|
|
|
.tick line { |
|
stroke: grey; |
|
} |
|
|
|
.actual-line { |
|
fill: none; |
|
stroke: #000; |
|
stroke-width: 3px; |
|
} |
|
|
|
.historical-line line { |
|
fill: none; |
|
stroke: red; |
|
stroke-width: 1.5px; |
|
stroke-dasharray: 2, 2; |
|
} |
|
|
|
.historical-line text { |
|
fill: red; |
|
} |
|
|
|
.current-line line { |
|
fill: none; |
|
stroke: orange; |
|
stroke-width: 1.5px; |
|
stroke-dasharray: 2, 2; |
|
} |
|
|
|
.current-line text { |
|
fill: orange; |
|
} |
|
|
|
circle { |
|
stroke: white; |
|
} |
|
|
|
</style> |
|
<svg width="960" height="500"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/d3-scale-chromatic.v0.3.min.js"></script> |
|
<link href="https://fonts.googleapis.com/css?family=Catamaran" rel="stylesheet"> |
|
<script> |
|
|
|
var n = 40; |
|
var random = d3.randomNormal(0, .2); |
|
let id = 0; |
|
var durationLength = 200; |
|
var data = []; |
|
|
|
for (i = 0; i < n; i++) { |
|
data[i] = newDatum(); |
|
}; |
|
|
|
var extent = d3.extent(data, function (d) { return d.value; }); |
|
var historicalMin = extent[0]; |
|
var historicalMax = extent[1]; |
|
var currentMin = extent[0]; |
|
var currentMax = extent[1]; |
|
|
|
var svg = d3.select("svg"), |
|
margin = { top: 20, right: 20, bottom: 20, left: 40 }, |
|
width = +svg.attr("width") - margin.left - margin.right, |
|
height = +svg.attr("height") - margin.top - margin.bottom, |
|
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
var x = d3.scaleLinear() |
|
.domain([0, n - 1]) |
|
.range([0, width]); |
|
|
|
var y = d3.scaleLinear() |
|
.domain([-1, 1]) |
|
.range([height, 0]); |
|
|
|
var line = d3.line() |
|
.x(function (d, i) { return x(i); }) |
|
.y(function (d, i) { return y(d.value); }); |
|
|
|
g.append("line") |
|
.attr("class", "x-axis") |
|
.attr("x1", 0) |
|
.attr("y1", y(0) + 0.5) |
|
.attr("x2", width) |
|
.attr("y2", y(0) + 0.5) |
|
|
|
g.append("defs").append("clipPath") |
|
.attr("id", "clip") |
|
.append("rect") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
let yAxis = g.append("g") |
|
.attr("class", "axis axis--y") |
|
.call(d3.axisLeft(y)); |
|
|
|
yAxis.selectAll("path, line") |
|
.style("stroke", "grey") |
|
.style("stroke-width", "1px") |
|
|
|
yAxis.selectAll("text") |
|
.style("font-family", "Catamaran") |
|
|
|
let historicalMaxLine = g.append("g") |
|
.attr("class", "historical-line") |
|
.attr("id", "historical-max") |
|
.attr("transform", "translate(0, " + y(historicalMax) + ")"); |
|
|
|
historicalMaxLine.append("line") |
|
.attr("x2", width) |
|
|
|
historicalMaxLine.append("text") |
|
.text("Historical max: " + round(historicalMax)) |
|
.attr("x", width) |
|
.attr("y", -3) |
|
.style("text-anchor", "end") |
|
|
|
let historicalMinLine = g.append("g") |
|
.attr("class", "historical-line") |
|
.attr("id", "historical-min") |
|
.attr("transform", "translate(0, " + y(historicalMin) + ")"); |
|
|
|
historicalMinLine.append("line") |
|
.attr("x2", width) |
|
|
|
historicalMinLine.append("text") |
|
.text("Historical min: " + round(historicalMin)) |
|
.attr("x", width) |
|
.attr("y", 14) |
|
.style("text-anchor", "end") |
|
|
|
let currentMaxLine = g.append("g") |
|
.attr("class", "current-line") |
|
.attr("id", "current-max") |
|
.attr("transform", "translate(0, " + y(currentMax) + ")"); |
|
|
|
currentMaxLine.append("line") |
|
.attr("x2", width) |
|
|
|
currentMaxLine.append("text") |
|
.text("Current max: " + round(currentMax)) |
|
.attr("x", width) |
|
.attr("y", 14) |
|
.style("text-anchor", "end") |
|
|
|
let currentMinLine = g.append("g") |
|
.attr("class", "current-line") |
|
.attr("id", "current-max") |
|
.attr("transform", "translate(0, " + y(currentMin) + ")"); |
|
|
|
currentMinLine.append("line") |
|
.attr("x2", width) |
|
|
|
currentMinLine.append("text") |
|
.text("Current min: " + round(currentMin)) |
|
.attr("x", width) |
|
.attr("y", -3) |
|
.style("text-anchor", "end") |
|
|
|
g.append("line") |
|
.attr("class", "current-line") |
|
.attr("id", "current-min") |
|
.attr("x1", 0) |
|
.attr("y1", y(currentMin)) |
|
.attr("x2", width) |
|
.attr("y2", y(currentMin)); |
|
|
|
g.append("g") |
|
.attr("clip-path", "url(#clip)") |
|
.append("path") |
|
.datum(data) |
|
.attr("class", "actual-line") |
|
.transition() |
|
.duration(durationLength) |
|
.ease(d3.easeLinear) |
|
.on("start", tick); |
|
|
|
let circlesG = g.append("g").classed("circles", true) |
|
.attr("clip-path", "url(#clip)") |
|
|
|
let circles = circlesG.selectAll("circle") |
|
.data(data) |
|
.enter() |
|
.append("g") |
|
.attr("transform", function (d, i) { return "translate(" + x(i) + ",0)"; }) |
|
|
|
circles.transition() |
|
.ease(d3.easeLinear) |
|
.duration(function (d, i) { return durationLength * i; }) |
|
.attr("transform", "translate(0,0)") |
|
.on("end", function (d) { |
|
d3.select(this).remove() |
|
}); |
|
|
|
circles.call(appendCircle); |
|
circles.call(appendTick); |
|
|
|
|
|
function tick() { |
|
|
|
// Push a new data point onto the back. |
|
var dataPoint = newDatum(); |
|
|
|
historicalMin = historicalMin > dataPoint.value ? dataPoint.value : historicalMin; |
|
historicalMax = historicalMax < dataPoint.value ? dataPoint.value : historicalMax; |
|
|
|
data.push(dataPoint); |
|
let newExtent = d3.extent(data, function (d) { return d.value; }); |
|
currentMin = newExtent[0] |
|
currentMax = newExtent[1] |
|
|
|
// Redraw the line. |
|
d3.select(this) |
|
.attr("d", line) |
|
.attr("transform", null); |
|
|
|
var newCircle = circlesG.append("g") |
|
.datum(dataPoint) |
|
.attr("transform", "translate(" + x(n) + ",0)") |
|
|
|
newCircle.transition() |
|
.ease(d3.easeLinear) |
|
.duration(durationLength * n) |
|
.attr("transform", "translate(0,0)") |
|
.on("end", function (d) { |
|
d3.select(this).remove() |
|
}); |
|
|
|
newCircle.call(appendCircle) |
|
|
|
newCircle.call(appendTick) |
|
|
|
circlesG.selectAll("circle").style("fill", function (d) { |
|
if (d.value == currentMax) { |
|
return "orange"; |
|
} else if (d.value == currentMin) { |
|
return "orange"; |
|
} else { |
|
return "black"; |
|
}; |
|
}); |
|
|
|
|
|
// Slide it to the left. |
|
d3.active(this) |
|
.attr("transform", "translate(" + x(-1) + ",0)") |
|
.transition() |
|
.on("start", tick); |
|
|
|
//update min and max lines |
|
historicalMinLine.transition() |
|
.attr("transform", "translate(0, " + y(historicalMin) + ")"); |
|
|
|
historicalMinLine.select("text") |
|
.text("Historical min: " + round(historicalMin)) |
|
|
|
historicalMaxLine.transition() |
|
.attr("transform", "translate(0, " + y(historicalMax) + ")"); |
|
|
|
historicalMaxLine.select("text") |
|
.text("Historical max: " + round(historicalMax)) |
|
|
|
currentMinLine.transition() |
|
.attr("transform", "translate(0, " + y(currentMin) + ")"); |
|
|
|
currentMinLine.select("text") |
|
.text("Current min: " + round(currentMin)) |
|
|
|
currentMaxLine.transition() |
|
.attr("transform", "translate(0, " + y(currentMax) + ")"); |
|
|
|
currentMaxLine.select("text") |
|
.text("Current max: " + round(currentMax)) |
|
|
|
// Pop the old data point off the front. |
|
data.shift(); |
|
|
|
} |
|
|
|
function newDatum() { |
|
var nd = {}; |
|
nd.id = id; |
|
id = id + 1; |
|
nd.value = random() |
|
return nd; |
|
}; |
|
|
|
function round(n) { |
|
return n.toFixed(2); |
|
}; |
|
|
|
function appendTick(selection) { |
|
|
|
selection.append("line") |
|
.attr("class", function (d) { |
|
return (d.id % 5) == 0 ? "major-tick" : "minor-tick" |
|
}) |
|
.attr("y1", y(0)) |
|
.attr("y2", y(-0.02)); |
|
|
|
selection.append("text") |
|
.attr("class", function (d) { |
|
return (d.id % 5) == 0 ? "major-tick" : "minor-tick" |
|
}) |
|
.attr("y", y(0) + 16) |
|
.text(function (d) { return d.id; }) |
|
}; |
|
|
|
function appendCircle(selection) { |
|
selection.append("circle") |
|
.attr("cy", function(d) {return y(d.value)}) |
|
.attr("r", 5) |
|
}; |
|
|
|
|
|
</script> |