Published
Edited
Jun 23, 2022
Fork of Simple D3
2 forks
2 stars
Insert cell
Insert cell
chart1 = RealtimeChart({size: [width, height], xDomain: [behindTenSeconds(), forwardTwoSeconds()], yDomain: [10,21]});
Insert cell
Insert cell
{
let frame;
let loop = 0;
let data = [];
let circleData = [];

// Seed initial data array
for(let i = 11; i >= 1; i--) {
const now = currentDateTime();
now.setSeconds(now.getSeconds() - i);
data.push({time: now, value: (((Math.random() * 11) + 10)).toFixed(2)});
}

// This simulates if we were getting real time data every second from an api
(function tick() {
loop++;

if (data.length % 12 === 0) {
circleData = data.slice(data.length-12);
}
if (loop != 0 && loop % 60 === 0) {
const now = currentDateTime();
const realTimeDataPoint = {time: now, value: (((Math.random() * 11) + 10)).toFixed(2)};

data.push(realTimeDataPoint);
circleData.push(realTimeDataPoint);
const props = {
size: [width, height],
xDomain: [behindTenSeconds(), forwardTwoSeconds()],
yDomain: [10,21],
data: data,
data2: circleData
};
chart1.update(props);
}
frame = requestAnimationFrame(tick);
})();
}


Insert cell
Insert cell
RealtimeChart = (props) => {
const [width, height] = props.size;
const xDomain = props.xDomain;
const margin = {left: 25, right: 50, bottom: 100, top: 30};

const xScale = d3.scaleTime()
.domain(xDomain)
.range([margin.left, width - margin.right])
.nice();

const yScale = d3.scaleLinear()
.domain(props.yDomain)
.range([height-margin.bottom, margin.top]);

const onUpdateXScale = xScale.copy();
const xAxis = (g, scale = xScale) => {
const drawAxis =
d3.axisBottom(scale)
.ticks(d3.timeSecond.every(1))
.tickSize(height-margin.bottom)
.tickFormat((domainValue) => {
const formatTime = d3.timeFormat('%I:%M:%S');
return formatTime(domainValue);
});

g.call(drawAxis)
}

const yAxis = (g, scale = yScale) => {
const drawAxis = d3.axisRight(scale)
.tickSize(width-margin.right);
g
.call(drawAxis)
.call(g => g.select(".domain").remove());
}

const lineFunc = d3.line()
.x(d => onUpdateXScale(d.time))
.y(d => yScale(d.value))
.curve(d3.curveCardinal);
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("font-size", "0.625rem")
.attr("font-family", "sans-serif")
.attr("font-weight", "bold");

const xAxisG = svg
.append("g")
.attr("class", "xAxis")
.attr("transform", d => `translate(0, ${margin.top})`)
.call(xAxis, onUpdateXScale);
const yAxisG = svg
.append("g")
.attr("class", "yAxis")
.attr("transform", d => `translate(${margin.left}, 0)`)
.call(yAxis, yScale);

xAxisG
.selectAll("g.tick")
.select("line")
.attr("stroke", "white");

yAxisG
.selectAll("g.tick")
.select("line")
.attr("stroke", "#bdbdbd");
const renderData = (props) => {
const { data, data2 } = props;

xAxisG
.selectAll("g.tick")
.select("line")
.attr("stroke", "white");

yAxisG
.selectAll("g.tick")
.select("line")
.attr("stroke", "#bdbdbd");
svg
.selectAll("path.line")
.data([props.data])
.enter()
.append("path")
.attr("class", "line")
.attr("d", lineFunc)
.style("stroke", "steelblue")
.attr("fill", "none")
.attr("stroke-width", "6px")
.attr("stroke-dasharray", "0,1")
.each(function(d) {
this._mySave = `0,0`;
});

svg
.selectAll("path.line")
.transition()
.duration(750)
.attr("d", lineFunc)
.attrTween("stroke-dasharray", function () {
const length = this.getTotalLength();
const lastStartingValue = this._mySave.split(',')[1];
const int = d3.interpolate(`${lastStartingValue},${length}`, `${length},${length}`);

return (t) => {
this._mySave = int(t);
return this._mySave;
}
});
svg
.selectAll("circle.pricePoint")
.data(data2, d => d.time)
.enter()
.append("circle")
.transition()
.duration(750)
.attr("cx", d => onUpdateXScale(d.time))
.attr("cy", d => yScale(d.value))
.attr("class", "pricePoint")
.attr("r", 0)
.attr("fill", "white")
.style("opacity", 0)
svg
.selectAll("circle.pricePoint")
.transition()
.duration(750)
.attr("cx", d => onUpdateXScale(d.time))
.attr("r", d => 5)
.attr("stroke", "steelblue")
.attr("stroke-width", "3px")
.attr("fill", "white")
.style("opacity", 1);

svg
.selectAll("circle.pricePoint")
.data(data, d => d.time)
.exit()
.remove();
}
return Object.assign(svg.node(), {
update(props) {
onUpdateXScale
.domain(props.xDomain);
xAxisG
.transition()
.duration(750)
.call(xAxis, onUpdateXScale);

xAxisG.select(".domain").remove();

renderData(props);
}
});
}
Insert cell
currentDateTime = () => {
const currentDate = new Date();
currentDate.setMilliseconds(0);
return currentDate;
}
Insert cell
behindTenSeconds = () => {
const tenSeconds = new Date();
tenSeconds.setMilliseconds(0);
tenSeconds.setSeconds(tenSeconds.getSeconds() - 10);
return tenSeconds;
}
Insert cell
forwardTwoSeconds = () => {
const elapsedTwoSeconds = new Date();
elapsedTwoSeconds.setMilliseconds(0);
elapsedTwoSeconds.setSeconds(elapsedTwoSeconds.getSeconds() + 2);
return elapsedTwoSeconds;
}

Insert cell
height = 500
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more