chart = {
var svg = d3.create('svg').attr('viewBox', [0, 0, width, height]);
var visitText = svg
.append('text')
.attr('x', width / 2)
.attr('y', margin.top)
.attr('text-anchor', 'center')
.text(`${visits[0]} (n=${ids.length})`);
svg
.append("g")
.classed('axis axis--x', true)
.call(xAxis);
svg
.append("g")
.classed('axis axis--y', true)
.call(yAxis);
svg
.append("text")
.attr('transform', `rotate(-90 10 ${(height - margin.top) / 2})`)
.attr("x", 10)
.attr("y", height / 2)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.text("Result");
var g = svg.append('g').classed('canvas', true);
var lines = g
.append('g')
.classed('lines canvas__lines', true)
.selectAll('line')
.data(ids, d => d[0])
.join('line')
.attr('x1', d => x(d[1][0].ADY))
.attr('y1', d => y(d[1][0].AVAL))
.attr('stroke', d => colorScaleLine(Math.abs(d[1][0].AVAL - median) * 2))
.attr('stroke-opacity', 0);
var circles = g
.append('g')
.classed('circles canvas__circles', true)
.selectAll('circle')
.data(ids, d => d[0])
.join('circle')
.attr('cx', d => x(d[1][0].ADY))
.attr('cy', d => y(d[1][0].AVAL))
.attr('r', 5)
.attr('fill', 'steelblue')
.attr('fill-opacity', .75)
.attr('stroke', 'steelblue')
.attr('stroke-opacity', 1);
let visitIndex = 1;
d3.interval(() => {
// reset visit index
if (visitIndex >= visits.length) visitIndex = 0;
var visit1 = visits[visitIndex - 1];
var visit2 = visits[visitIndex];
// subset on visit
var subset = measure.filter(d => d.AVISIT === visit2);
// update visit text
visitText
.transition()
.delay(500)
.text(`${visit2} (n=${subset.length})`);
// Circles should move from the previous visit to the next visit.
circles.each(function(data) {
const d = data[1].find(di => di.AVISIT === visit2);
const circle = d3
.select(this)
.transition()
.ease(d3.easeQuad)
.duration(1000)
.delay(500);
if (d)
circle
.attr('cx', x(d.ADY))
.attr('cy', y(d.AVAL))
.attr('fill-opacity', .75);
else circle.attr('fill-opacity', 0.25).attr('stroke-opacity', 0.5);
});
// Lines should move from the previous visit to the next visit and be colored by the size of the change.
lines.each(function(data) {
const d1 = data[1].find(di => di.AVISIT === visit1);
const d2 = data[1].find(di => di.AVISIT === visit2);
const line = d3.select(this);
if (d1 && d2)
line
.attr('x1', x(d1.ADY))
.attr('y1', y(d1.AVAL))
.attr('x2', x(d1.ADY))
.attr('y2', y(d1.AVAL))
.attr('stroke', colorScaleLine(Math.abs(d2.AVAL - median) * 2))
.attr('stroke-opacity', .5)
.transition()
.ease(d3.easeQuad)
.duration(1000)
.attr('x2', x(d2.ADY))
.attr('y2', y(d2.AVAL));
else
line
.transition()
.duration(1000)
.attr('stroke-opacity', 0);
});
// increment visit
visitIndex++;
}, 2000);
return svg.node();
}