Public
Edited
Jul 26, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Changed
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
Added
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