Public
Edited
Jul 12, 2022
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
appendTrendBrackets = function(selection, x, x1, x2, y, bracketHeight) {
// Create brackets for the trend intervals
const g = selection.append('g').attr('class', 'trend-bracket-g');

// Calculate the path
const path = d3.path();
path.moveTo(x1, y + bracketHeight);
path.lineTo(x1 + 2, y + bracketHeight/2);
path.lineTo(d3.max([x - bracketHeight/2, x1]), y + bracketHeight/2);
path.lineTo(x, y);
path.lineTo(d3.min([x + bracketHeight/2, x2]), y + bracketHeight/2);
path.lineTo(x2 - 2, y + bracketHeight/2);
path.lineTo(x2, y + bracketHeight);
g.append('path')
.attr('class', 'trend-bracket')
.attr('d', path)
.attr('fill', 'none')
.attr('stroke', 'grey');
}
Insert cell
Insert cell
appendTrendInfo = function(selection, tScale, category, y) {
const fontsize = 10;
const nrEvents = getNrEvents();
// Calculate the x-positions of the intervals
const xLeft = tScale(shiftHours(dateBase, - temporalWindowSizeUpdated * 24 + 12));
const xRight = tScale(shiftHours(dateBase, + 12));
const xMid = (xLeft + xRight) / 2;
// Append Trend text
selection.append('text')
.attr('class', category + '-trend-text annotation-text')
.attr('x', xMid - 3 * fontsize)
.attr('y', y)
.text('Trend before ' + category + ':')
.attr('fill', color[category])
.style('text-anchor', 'end')
.style('font-size', 12);
// Append trend counts and brackets
const intervals = ['A', 'B'];
intervals.forEach((interval, i) => {
// Append trend counts
//const x = (xLeft + xMid) / 2 + i * (xRight - xLeft) / 2;
const x = xMid - 2 * fontsize + i * 4 * fontsize;
selection.append('text')
.attr('class', category + '-trend-count-interval-' + interval + ' annotation-text')
.attr('x', x)
.attr('y', y)
.attr('fill', color['dependent'])
.text(nrEvents[category]['pre'+interval])
.style('text-anchor', 'middle')
.style('font-size', 16);
// Append brackets
const x1 = i == 0 ? xLeft : xMid + 3;
const x2 = i == 0 ? xMid - 3 : xRight;
const bracketHeight = 9;
appendTrendBrackets(selection, x, x1, x2, y + 2, bracketHeight)
})

// Append arrow representing trend
// Length of the arrow
const arrowSize = fontsize;
// Append a refernce to the arrrow head to the svg
const arrowHeadSize = arrowSize / 3;
defineArrowHead(selection, arrowHeadSize, category)
// x-coordinate of the arrow's center of rotation
const xr = xMid;
const yr = y - fontsize/2;

// A place holder for what the maximum trend could theoretically be
const assumedMaxAbsTrend = nrDependentTreatmentPre;

// Define a scale to calculate the arrow rotation
const rotationScale = d3.scaleLinear()
.domain([-assumedMaxAbsTrend, assumedMaxAbsTrend])
.range([Math.PI * 0.5, Math.PI * 1.5]);

// Calculate the sin and cos first
const rotationAngle = rotationScale(nrEvents[category]['preA'] - nrEvents[category]['preB'])
const xOffset = arrowSize * Math.cos(rotationAngle)
const yOffset = arrowSize * Math.sin(rotationAngle)
selection.append('line')
.attr('class', category + '-trend-arrow-line')
.attr('x1', xr + xOffset)
.attr('x2', xr - xOffset)
.attr('y1', yr + yOffset)
.attr('y2', yr - yOffset)
//.attr('stroke', color[category])
.attr('stroke', color['dependent'])
.attr('stroke', 'grey')
.attr('stroke-width', 2)
.attr('marker-end', `url(#${category}-arrow)`);

}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
createTimelineSharp = function(selection, xGrid, height, category, histHeight, yPosLine) {
const rectHeight = 20;
//const histHeight = 20;
const rectWidth = 10;
//const yPosLine = yGrid(0.5 - paddingY + (category === 'control' ? 0.5 : 0));
const stripeWidth = 3;

// Determine the first and list dates in the data set
//var monthExtent = d3.extent(data, function(d) { return d.created_date; });

// Create one bin per day (do i need to use an offset to include the first and last day?)
const dayThresholds = d3.timeDay.every(1).range(dateStart, dateEnd)

const dependentDates = ['pre', 'post']
.map(step => eventData[category].stepData[step].data.filter(d => d.type === category ? false : true))
.flat().map(d => d.date);

console.log(dependentDates.map(d => dateToString(d)))
const binData = d3.histogram()
.domain([dateStart, dateEnd])
.thresholds(dayThresholds)
.value(d => d)
(dependentDates);
// Shift the beginning and the end of the scale to be able to centralize the bins
const tScale = d3.scaleTime()
.domain([shiftHours(dateStart, -12), shiftHours(dateEnd, 12)])
.range([xGrid('pre'), xGrid('post') + xGrid.bandwidth()]);
// Offset to centralize the bars around their date
const binWidth = (tScale.range()[1] - tScale.range()[0]) / (2 * temporalWindowSize);

// Create an array that contains all the information for the stripes
const stripeData = [];
binData.forEach((binArray, binNr) => {
// Calculate the actual x-position where the bin will start -> shifted to the left by half the bin width
const binStartX = tScale(binArray.x0) - binWidth/2;
const nrBinEvents = binArray.length;
if (nrBinEvents > 0) {
// How large the step is between each stripe
let step;
if (binNr == (temporalWindowSize - 1)) {
// Treat intervention bin differently: Distribute dependent event stripes only across first half of the bin, in front of the intervention event
step = (binWidth - stripeWidth) / (nrBinEvents + 1);
}
else {
// Distribute dependent event stripes across entire bin
step = binWidth / (nrBinEvents + 1);
}
// Push the x-position for each stripe to the data array
binArray.forEach((d, j) => stripeData.push({
// Move the first stripe in by one step
x: binStartX + j * step + step
}));
};
});
const g = selection.append('g').attr('class', 'timeline-g');
const gStripeDep = selection.append('g').attr('class', 'dependent-stripe-g');
const gStripeInt = selection.append('g').attr('class', 'intervention-stripe-g');
gStripeDep.selectAll('.dependent-stripe')
.data(stripeData)
.join('line')
.attr('class', 'dependent-stripe')
.attr('x1', d => d.x)
.attr('x2', d => d.x)
.attr('y1', yPosLine - histHeight)
.attr('y2', yPosLine)
.attr('stroke', d => {
// If the stripe lies outside of the updated temporal window, color it light grey
if(d.x >= tScale(shiftHours(dateBase, - temporalWindowSizeUpdated * 24 + 12)) &&
d.x <= tScale(shiftHours(dateBase, temporalWindowSizeUpdated * 24 + 12))) {
return fillScale('dependent');
}
else return 'lightgrey';
})
.attr('stroke-width', stripeWidth);






// Intervention event (placed first in actual code)
gStripeInt.selectAll('.intervention-stripe-' + category)
.data([{x: tScale(dateBase) + binWidth/2 - stripeWidth/2}])
.join('line')
.attr('class', 'intervention-stripe-' + category)
.attr('x1', d => d.x)
.attr('x2', d => d.x)
.attr('y1', yPosLine - histHeight) // * 1.3)
.attr('y2', yPosLine)
.attr('stroke', fillScale(category))
.attr('stroke-width', stripeWidth);

// Create ticks for the beginning/end of each day
const gTick = g.append('g').attr('class', 'tick-g');
const tickHeight = 3;
// Get all days that should be ticked
const dayTickData = [...dayThresholds, dateEnd, shiftHours(dateEnd, 24)];
gTick.selectAll('.day-tick')
.data(dayTickData)
.join('line')
.attr('class', 'day-tick')
.attr('x1', d => tScale(d) - binWidth/2)
.attr('x2', d => tScale(d) - binWidth/2)
.attr('y1', yPosLine - tickHeight)
.attr('y2', yPosLine)
.attr('stroke', 'grey')
.attr('stroke-width', 1);

// Create brackets for the labelled day bins
const gBracket = g.append('g').attr('class', 'bracket-g');
const bracketHeight = 5;
gBracket.selectAll('.bracket')
.data([dateStart, dateBase, dateEnd])
.join('path')
.attr('class', 'bracket')
.attr('d', d => {
const cx = tScale(d);
const path = d3.path();
path.moveTo(cx - binWidth/2, yPosLine + tickHeight)
path.lineTo(cx - binWidth/2, yPosLine + tickHeight + bracketHeight)
path.lineTo(cx - binWidth/8, yPosLine + tickHeight + bracketHeight)
path.lineTo(cx, yPosLine + tickHeight + bracketHeight + tickHeight)
path.lineTo(cx + binWidth/8, yPosLine + tickHeight + bracketHeight)
path.lineTo(cx + binWidth/2, yPosLine + tickHeight + bracketHeight)
path.lineTo(cx + binWidth/2, yPosLine + tickHeight)
return path;
})
.attr('fill', 'none')
.attr('stroke', 'grey');
// Create x-axis function
const xAxis = d3.axisBottom().scale(tScale)
.tickValues([dateStart, dateBase, dateEnd])
.tickSize(0)
.tickFormat(d3.timeFormat("%d %b %Y"));

g.selectAll(`#${category}-x-axis-g`)
.data([0])
.join(
enter => enter.append('g')
.attr('class', `${category}-x-axis-g`)
.call(xAxis)
.attr('transform', `translate(0, ${yPosLine})`)
)
.selectAll("text")
.attr("dy", "2em");

//appendAxisTicks(g, tScale, category);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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