nicola
February 10, 2023, 9:47am
1
I am using this calendar version for one of my scripts and I took the small challenge of re-writing it in Observable Plot
Plot.plot({
height: 150,
width,
x: {
axis: null,
padding: 0,
},
y: {
padding: 0,
tickFormat: d => Plot.formatWeekday("en", "narrow")(-((d + 6) % 7)),
tickSize: 0,
ticks: 7
},
fy: {
// reverse: true
},
marks: [
Plot.rect(log, {
x1: d => d3.timeMonday.count(d3.utcYear(d[0]), d[0]),
x2: d => d3.timeMonday.count(d3.utcYear(d[0]), d[0]) +1,
// i => (i + 6) % 7
y1: d => -((d[0].getUTCDay() + 6) %7 + d[3]/d[2] - 0.5),
y2: d => -((d[0].getUTCDay() + 6) % 7 + 1/[d[2]] + d[3]/d[2] - 0.5),
fy: d => d[0].getUTCFullYear(),
fill: (d, i) => d[1],
title: (d, i) => `${i} ${d[0]} ${d[1]} ${d3.timeMonday.count(d3.utcYear(d[0]), d[0])}`,
inset: 0.5
}),
Plot.text(d3.utcMonths(log[0][0], log[log.length -1][0]), {
x: d => d3.timeMonday.count(d3.utcYear(d), d) + 0.5,
y: d => 0,
dy: -15,
text: d3.utcFormat('%b')
}),
]
})
This works (might be improved sure), but I now wanted to add the path that separates the months and I believe I am reaching the limit of what is possible with Observable Plot.
Any idea on how to do that?
Cobus
February 10, 2023, 3:08pm
2
You may want to look at this one for inspiration. I think that part is quite tricky.
Based on Observable Plot, this function displays a calendar, highlighting some dates passed as data: To use, import the function in a cell: then call it: where data is an iterable of calendar items. The options are: date — the date accessor; defaults...
Here’s my take:
This notebook demonstrates a D3-style calendar using Plot. This implementation has a custom transform (see `calendar` below) for default x (week number), y (weekday number), and fy (year) options derived from the date; it also uses a custom mark...
I did this by implementing a custom mark:
class MonthLine extends Plot.Mark {
static defaults = {stroke: "white", strokeWidth: 3};
constructor(data, options = {}) {
const {x, y} = options;
super(data, {x: {value: x, scale: "x"}, y: {value: y, scale: "y"}}, options, MonthFrame.defaults);
}
render(index, {x, y}, {x: X, y: Y}, dimensions) {
const {marginTop, marginBottom, height} = dimensions;
const dx = x.bandwidth(), dy = y.bandwidth();
return htl.svg`<g fill=none stroke=${this.stroke} stroke-width=${this.strokeWidth}>
${Array.from(index, (i) => htl.svg`<path d=${
Y[i] > marginTop + dy
? `M${X[i] + dx},${marginTop + dy}V${Y[i]}h${-dx}`
: `M${X[i]},${marginTop + dy}`}V${height - marginBottom}>`)}
</g>`;
}
}
We haven’t documented how to implement custom marks yet, but as the code above hints, it’s possible! In this case we’re using the x and y scales provided by plot to render an SVG path element.
1 Like
nicola
February 12, 2023, 9:10pm
5
brilliant, this is exactly what I wished it was possible!
Thank you!
1 Like
nicola
February 13, 2023, 8:50am
6
Weird, I am trying @mbostock solution, but apparently x.bandwidth
not defined. It looks like in your example the type of the x is band
and in my example is linear
. I will look into this.
Yes, my code assumes that x and y are band scales; if you use linear scales you’ll need to make some adjustments.