Public
Edited
Jul 26, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
timePlot = Plot.plot({
marks: [
Plot.axisX({
render: (index, scales, values, dimensions, context, next) => {
const a = next(index, scales, values, dimensions, context);
mutable debug = Array.from(d3.select(a).selectAll("text"), (d) =>
Array.from(
d.children.length ? d.children : [d],
(d) => d.textContent
).join("\n")
);
return a;
}
}),
Plot.ruleY([0]),
Plot.lineY(aapl, { x: "Date", y: (d) => d.Close * 1000 })
],
width: w,
height,
marginLeft: 50
})
Insert cell
mutable debug = null
Insert cell
function getTicks(direction, { type, domain, range }) {
if (type === "band") return domain;
else if ((type === "linear") | (type === "utc")) {
const scaleType = type === "linear" ? "scaleLinear" : "scaleUtc";
const scale = d3[scaleType]().domain(domain).range(range);
const axis = direction === "x" ? d3.axisBottom(scale) : d3.axisLeft(scale);
// https://github.com/observablehq/plot/blob/203bd4c9f7f41270ad2bc31d24efffb3787bd0e5/src/marks/axis.js#L547
const tickSpacing = direction === "x" ? 80 : 35;
const tickCount = inferTickCount(scale, tickSpacing);
// Couldn't get the time formatter working: so close!
const allTicks = axis.scale().ticks(tickCount);
const formatter =
type === "utc" ? inferTimeFormat(type, allTicks) : scale.tickFormat();
return axis
.scale()
.ticks(tickCount)
.map((d, i) => formatter(d, i, allTicks));
} else {
return "error";
}
}
Insert cell
// From https://github.com/observablehq/plot/blob/203bd4c9f7f41270ad2bc31d24efffb3787bd0e5/src/marks/axis.js#L639C1-L642C2
// tickSpacing = k === "x" ? 80 : 35
function inferTickCount(scale, tickSpacing) {
const [min, max] = d3.extent(scale.range());
return (max - min) / tickSpacing;
}
Insert cell
Insert cell
// https://github.com/observablehq/plot/blob/203bd4c9f7f41270ad2bc31d24efffb3787bd0e5/src/time.js#L269
function inferTimeFormat(type, dates, anchor = "bottom") {
const step = d3.max(d3.pairs(dates, (a, b) => Math.abs(b - a))); // maybe undefined!
if (step < 1000) return formatTimeInterval("millisecond", "utc", anchor);
for (const [name, interval, intervalType, maxStep] of getFormatIntervals(
type
)) {
if (step > maxStep) break; // e.g., 52 weeks
if (name === "hour" && !step) break; // e.g., domain with a single date
if (dates.every((d) => interval.floor(d) >= d))
return formatTimeInterval(name, intervalType, anchor);
}
}
Insert cell
function formatTimeInterval(name, type, anchor) {
const format = type === "time" ? d3.timeFormat : d3.utcFormat;
// For tips and legends, use a format that doesn’t require context.
if (anchor == null) {
return format(
name === "year"
? "%Y"
: name === "month"
? "%Y-%m"
: name === "day"
? "%Y-%m-%d"
: name === "hour" || name === "minute"
? "%Y-%m-%dT%H:%M"
: name === "second"
? "%Y-%m-%dT%H:%M:%S"
: "%Y-%m-%dT%H:%M:%S.%L"
);
}
// Otherwise, assume that this is for axis ticks.
const template = getTimeTemplate(anchor);
console.log({ template });
switch (name) {
case "millisecond":
return formatConditional(format(".%L"), format(":%M:%S"), template);
case "second":
return formatConditional(format(":%S"), format("%-I:%M"), template);
case "minute":
return formatConditional(format("%-I:%M"), format("%p"), template);
case "hour":
return formatConditional(format("%-I %p"), format("%b %-d"), template);
case "day":
return formatConditional(format("%-d"), format("%b"), template);
case "month":
return formatConditional(format("%b"), format("%Y"), template);
case "year":
return format("%Y");
}
throw new Error("unable to format time ticks");
}
Insert cell
function getTimeTemplate(anchor) {
return anchor === "left" || anchor === "right"
? (f1, f2) => `\n${f1}\n${f2}` // extra newline to keep f1 centered
: anchor === "top"
? (f1, f2) => `${f2}\n${f1}`
: (f1, f2) => `${f1}\n${f2}`;
}
Insert cell
function formatConditional(format1, format2, template) {
return (x, i, X) => {
const f1 = format1(x, i); // always shown
const f2 = format2(x, i); // only shown if different
const j = i - orderof(X); // detect reversed domains
// Why is X undefined....?
console.log({ x, i, X, j });
return i !== j && X[j] !== undefined && f2 === format2(X[j], j)
? f1
: template(f1, f2);
// return template(f1, f2);
};
}
Insert cell
function getFormatIntervals(type) {
return type === "time"
? d3.timeFormatIntervals
: type === "utc"
? utcFormatIntervals
: d3.formatIntervals;
}
Insert cell
function orderof(values) {
if (values == null) return;
const first = values[0];
const last = values[values.length - 1];
return d3.descending(first, last);
}
Insert cell
utcFormatIntervals = [
["year", d3.utcYear, "utc"],
["month", d3.utcMonth, "utc"],
["day", d3.unixDay, "utc", 6 * durationMonth],
["hour", d3.utcHour, "utc", 3 * durationDay],
["minute", d3.utcMinute, "utc", 6 * durationHour],
["second", d3.utcSecond, "utc", 30 * durationMinute]
]
Insert cell
durationSecond = 1000
Insert cell
durationMinute = durationSecond * 60;
Insert cell
durationHour = durationMinute * 60
Insert cell
durationDay = durationHour * 24;

Insert cell
durationWeek = durationDay * 7
Insert cell
durationMonth = durationDay * 30
Insert cell
durationYear = durationDay * 365
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