Published
Edited
Jun 5, 2020
27 stars
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
Insert cell
Insert cell
dateRange = d3.timeDay.range(new Date(start_date), d3.timeDay.offset(new Date(end_date),1)).map(d3.timeFormat("%Y-%m-%d"))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
pivotNodes = function(pivot){
let rolledup = d3.rollup(nodes,
// we return the list of nodes as a Map
v => v.length || 0,
// first we group by the date as a string
d => numericToCalendar(d.data.node_attrs.num_date.value),
// then we group by the division
d => d.data.node_attrs[pivot].value
)
let keys = pivotKeys(pivot)
// this is me not being super familiar with d3.stack and using maps
// I'm going to create empty entries for missing divisions/date combinations
dateRange.forEach(d => {
let m = rolledup.get(d)
if(!m) {
m = new Map()
}
keys.forEach(key => {
if(!m.get(key)) {
m.set(key, 0)
}
})
rolledup.set(d, m)
})
return rolledup
}
Insert cell
// gives us an array with one row for each division, each of those rows is an array of 85 dates
pivotSeries = function(pivot, stackOrder) {
let keys = pivotKeys(pivot)
let pivotedNodes = pivotNodes(pivot)
// create an intermediate datastructure that we can pass into d3.stack
let entries = Array.from(pivotedNodes)
.sort((a,b) => a[0].localeCompare(b[0])) // order by date ascending
.map(d => ({ date: d[0], map: d[1] })) // store each element as an object instead of array
let stacked = d3.stack()
.keys(keys)
.value((group, key) => {
return group.map.get(key)
})
if(stackOrder) {
stacked = stacked.offset(stackOrder)
}
stacked = stacked(entries)
// post-processing the stack so that each datapoint (what will become a rectangle or a piece of the stream)
// has all it's associated metadata in the .data property,
// this way we don't need to look it up by index later
// I wonder if there is a way to do this with d3.stack
keys.forEach((key,i) => {
let stack = stacked[i];
stack.forEach(s => {
let d = s.data
s.data = {
date: d.date,
key: key,
group: regionsBy[pivot].get(key),
value: d.map.get(key),
color: colorScales[pivot](key)
}
})
})
return stacked
}
Insert cell
Insert cell
Insert cell
stackedHisto = function(data, height) {
let div = html`
<style>
.axis {
pointer-events: none;
}
</style>
<div clas="root">
<svg></svg>
</div>
`
let className = "sh_tooltip"
let tooltipDiv = makeTooltip(className)
div.appendChild(tooltipDiv)
const tooltip = d3.select(tooltipDiv).select("." + className)
// let points = pointsByDate(data)
// const svg = d3.create("svg")
const svg = d3.select(div).select("svg")
.attr("viewBox", [0, 0, width, height]);
/*
Setup interactive rectangles in the background to trigger highlight and tooltip
*/
svg.append("g")
.selectAll("rect.bg")
.data(dateRange)
.join("rect")
.classed("bg", true)
.attr("x", d => x(d))
.attr("y", d => margin.top)
.attr("width", x.bandwidth() )
.attr("height", d => height - margin.bottom)
.style("fill", "#fff")
.on("mouseover", function(d) {
d3.select(this).style("fill", "#dfdfdf")
tooltipDiv.update(this, d, data)
// tooltipDiv.update(this, d, points[d].points, points[d].total)
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "#fff")
let ee = d3.event.toElement
console.log(tooltip.node().contains(ee))
// TODO: simplify this and move it to tooltip
if(!tooltip.node().contains(ee) &&
//ee != tooltip.node() &&
!d3.select(ee).classed("bg")) {
tooltip.style("display", "none")
}
})
let ys = d3.scaleLinear()
.domain([0, d3.max(data, d => d3.max(d, d => d[1]))]).nice()
.range([height - margin.bottom, margin.top])

svg.append("g")
.selectAll("g")
.data(data)
.join("g")
.attr("fill", (d) => {
return d[0].data.color.color
})
.style("pointer-events", "none")
.call((g,div) => {
return g.selectAll("rect")
.data(d => { return d})
.join("rect")
.attr("x", (d) => {
return x(d.data.date)
})
.attr("y", (d,i) => ys(d[1]))
.attr("width", x.bandwidth())
.attr("height", (d,i) => {return ys(d[0]) - ys(d[1])})
});

/* Axis and grid lines */
svg.append("g").classed("axis", true)
.attr("transform", `translate(${+x.bandwidth()/2 - .5},${height - margin.bottom})`)
.call(xAxisLines(dateRange, height, margin, "#dfdfdf"));
svg.append("g").classed("axis", true)
.attr("transform", `translate(${-x.bandwidth()/2 - .5},${height - margin.bottom})`)
.call(xAxisLines(xTicks, height, margin, "#999"));
svg.append("g").classed("axis", true)
.attr("transform", `translate(${-x.bandwidth()/2 - .5},${height - margin.bottom})`)
.call(xAxis);
svg.append("line").classed("axis", true)
.attr("x1", x(start_date))
.attr("x2", x(end_date) + x.bandwidth())
.attr("y1", height - margin.bottom)
.attr("y2", height - margin.bottom)
.style("stroke", "#dfdfdf")

svg.append("line").classed("axis", true)
.attr("x1", x(start_date))
.attr("x2", x(end_date) + x.bandwidth())
.attr("y1", height - margin.bottom)
.attr("y2", height - margin.bottom)
.style("stroke", "#dfdfdf")

// svg.append("g")
// .call(yAxis);

// return svg.node();
return div
}
Insert cell
stackedHisto(divisionSeries, height2)
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more