Published unlisted
Edited
Apr 5, 2021
Insert cell
chart = {
const el = DOM.svg(width + margin.left + margin.right, height + margin.top + margin.bottom)
const svg = d3.select(el)

const clip = DOM.uid("clip");

svg.append("defs")
.append("clipPath")
.attr("id", clip.id)
.append("rect")
.attr("width", width)
.attr("height", height);
const focus = svg.append("g")
.attr("class", "focus")
.attr("transform", `translate(${margin.left},${margin.top})`);

const context = svg.append("g")
.attr("class", "context")
.attr("transform", `translate(${margin2.left},${margin2.top})`);

focus.append("path")
.datum(data)
.attr("clip-path", clip)
.attr("class", "area")
.attr("d", area)
.style("fill", "black");

focus.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0,${height})`)
.call(xAxis);

focus.append("g")
.attr("class", "y axis")
.call(yAxis);

context.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area2);

context.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0,${height2})`)
.call(xAxis2);

context.append("g")
.attr("class", "x brush")

//add tooltip
const tooltip = focus.append("g");

focus.on("touchmove mousemove", function(event) {
const {date, value} = bisect(d3.pointer(event, this)[0]);

tooltip
.attr("transform", `translate(${x(parseDate(date))},${y(value)})`)
.call(callout, `${(value)}
${parseDate(date)}`);
});

focus.on("touchend mouseleave", () => tooltip.call(callout, null));
return el;

}

Insert cell
brush = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("display", "block");
const brush = d3.brushX(x2)
.extent([[0, -10],[width - margin.right, height2]])
.on('brush', brushed)
.on("end", brushended)
d3.select(chart)
.select('g.brush')
.call(brush)

const defaultSelection = [x(d3.utcHour.offset(x.domain()[0], -1)), x.range()[1]];

svg.append("g")
.call(xAxis, x, height);
svg.append("path")
.datum(data)
.attr("fill", "yellow")
.attr("d", area);

const gb = svg.append("g")
.call(brush)
.call(brush.move, defaultSelection);

function brushed() {
let extent = d3.event.selection.map(d => x2.invert(d))
x.domain( extent )
d3.select(chart).select(".area").attr("d", area);
d3.select(chart).select(".x.axis").call(xAxis);
}
function brushended() {
if (!d3.event.selection) {
gb.call(brush.move, defaultSelection);
}
}
return svg.brush

}
Insert cell
area2 = d3.area()
.x(d => x2(parseDate(d.date)))
.y0(y2(0))
.y1(d => y2(d.count))
.curve(d3.curveStepAfter)

Insert cell
area = d3.area()
.x(d => x(parseDate(d.date)))
.y0(y(0))
.y1(d => y(d.count))
.curve(d3.curveStepAfter)
Insert cell
yAxis = d3.axisLeft(y)
Insert cell
xAxis2 = d3.axisBottom(x2)
Insert cell
xAxis = d3.axisBottom(x)
Insert cell
width = 960 - margin.left - margin.right
Insert cell
margin2 = ({ top: 430, right: 10, bottom: 20, left: 40 })
Insert cell
margin = ({top: 10, right: 10, bottom: 100, left: 40})

Insert cell
height = 500 - margin.top - margin.bottom

Insert cell
height2 = 500 - margin2.top - margin2.bottom
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data.map(d => d.count))])
.range([height, 0])
Insert cell
y2 = d3.scaleLinear()
.domain([0, d3.max(data.map(d => d.count))])
.range([height2,0])
Insert cell
x = {
const minDate = d3.min(data.map(d => parseDate(d.date)))
const maxDate = d3.max(data.map(d => parseDate(d.date)))
return d3.scaleTime()
.domain([minDate, maxDate])
.range([0, width])
}

Insert cell
x2 = {
const minDate = d3.min(data.map(d => parseDate(d.date)))
const maxDate = d3.max(data.map(d => parseDate(d.date)))
return d3.scaleTime()
.domain(x.domain())
.range([0, width])
}
Insert cell
timeParse = d3.timeParse("%Y-%m-%d %H:%M:%S")
Insert cell
parseDate = d3.timeParse("%Y-%m-%d %H:%M:%S")
Insert cell
formatDate = d3.timeFormat("%Y-%m-%d %H:%M:%S")
Insert cell
data = {
const earliestStartDate = d3.min(candidates.map(d => parseDate(d.start)))
const lastDate = d3.max(candidates.map(d => parseDate(d.end)))
let dates = d3.scaleTime()
.domain([d3.timeHour.offset(earliestStartDate, -1), lastDate])
.ticks(d3.timeHour.every(1))
.map(d => formatDate(d))
const datesAndCounts = dates.map(d => {
let c = 0
for (let i = 0; i < candidates.length; i++) {
const cStart = candidates[i].start
const cEnd = candidates[i].end
if ((parseDate(cStart) <= parseDate(d)) && (parseDate(cEnd) > parseDate(d) || cEnd === "")) {
c++
}
}
return {date: d, count: c}
})
return datesAndCounts
}
Insert cell
candidates = [
{name: "nome1", start: "2019-09-01 09:00:12", end: "2019-09-02 15:20:13"},
{name: "nome2", start: "2019-09-01 05:10:33", end: "2019-09-01 12:40:15"},
{name: "nome3", start: "2019-09-01 02:08:11", end: "2019-09-01 13:15:23"}
]
Insert cell
d3 = require("d3@5")
Insert cell
callout = (g, value) => {
if (!value) return g.style("display", "none");

g
.style("display", null)
.style("pointer-events", "none")
.style("font", "10px sans-serif");

const path = g.selectAll("path")
.data([null])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");

const text = g.selectAll("text")
.data([null])
.join("text")
.call(text => text
.selectAll("tspan")
.data((value + "").split(/\n/))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i) => `${i * 1.1}em`)
.style("font-weight", (_, i) => i ? null : "bold")
.text(d => d));

const {x, y, width: w, height: h} = text.node().getBBox();

text.attr("transform", `translate(${-w / 2},${15 - y})`);
path.attr("d", `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
}
Insert cell
bisect = {
const bisect = d3.bisector(d => parseDate(d.date)).left;
return mx => {
const date = x.invert(mx);
const index = bisect(data, date, 1);
const a = data[index - 1];
const b = data[index];
return b && (date - a.date > b.date - date) ? b : a;
};
}
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