Public
Edited
Jan 13
1 fork
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
events = [
create_event("2023-02-24", "Table ronde sur l'anonymisation de données", "Etalab", "https://www.numerique.gouv.fr/agenda/table-ronde-sur-lanonymisation-de-donnees/"),
create_event("2023-03-10", "Masterclass Datascientest : Réseaux de neurone", "SSPHub", "https://framaforms.org/participation-aux-masterclass-datascientest-1675096179"),
create_event("2023-03-21", "Data Visualization Fundamentals and Best Practices : Interaction", "observablehq", "https://observablehq.com/@observablehq/datavizcourse?utm_medium=video&utm_campaign=datavizcourse&utm_source=videoembed"),
create_event("2023-03-16", "Data Visualization Fundamentals and Best Practices : Transformations", "observablehq", "https://observablehq.com/@observablehq/datavizcourse?utm_medium=video&utm_campaign=datavizcourse&utm_source=videoembed"),
create_event("2023-03-14", "Data Visualization Fundamentals and Best Practices : Comparisons", "observablehq", "https://observablehq.com/@observablehq/datavizcourse?utm_medium=video&utm_campaign=datavizcourse&utm_source=videoembed"),
create_event("2023-03-09", "Data Visualization Fundamentals and Best Practices : Data representation", "observablehq", "https://observablehq.com/@observablehq/datavizcourse?utm_medium=video&utm_campaign=datavizcourse&utm_source=videoembed"),
create_event("2023-03-07", "Data Visualization Fundamentals and Best Practices : Introduction", "observablehq", "https://observablehq.com/@observablehq/datavizcourse?utm_medium=video&utm_campaign=datavizcourse&utm_source=videoembed"),
create_event("2023-02-13", "Packages Opendata", "SSPHub", "https://ssphub.netlify.app/talk/presentation-des-packages-r-et-python-pour-acceder-a-lopen-data-de-linsee/"),
create_event("2023-02-14", "Journée lancement saison 2 du programme 10%", "Etalab", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-03-28", "Présentation du projet Meta Academy - Carpentries", "SSPHub", "https://ssphub.netlify.app/talk/presentation-du-projet-meta-academy-carpentries/"),
create_event("2023-03-29", "\"OCRisation: état de l'art et projets auxquels Teklia participe par Christopher Kermorvant \"", "SSPHub", "https://ssphub.netlify.app/talk/ocrisation-teklia"),
//create_event("2023-04-04", "Breizh Data Days", "LA FRENCH TECH SAINT-BRIEUC BAY / INNOZH ", "https://meetu.ps/e/LDrDT/JgYdy/i"),
create_event("2023-04-17", "Journée du réseau", "SSPHub", "TBA"),
create_event("2023-04-04", "Neuvième journée PERSPECTIVES ET DEFIS DE l’IA sur le thème de « IA et écologie »", "L’Association Française pour l’Intelligence Artificielle (AFIA)", "https://afia.asso.fr/pdia-2023/"),
create_event("2023-05-10", "JupyterCon", "Quantstack", "https://www.jupytercon.com/"),
create_event("2023-03-30", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-04-03", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-04-13", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-05-09", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-05-25", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-06-13", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-06-29", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-07-11", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-09-05", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-09-21", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-10-05", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
create_event("2023-10-19", "Atelier de travail du programme 10%", "Etalab, programme 10%", "https://www.10pourcent.etalab.gouv.fr/"),
]
Insert cell
parser_month = d3.timeParse("%Y-%m-%d")
Insert cell
parser_month_reverse = d3.timeParse("%d-%m-%Y")
Insert cell
function utc_to_string(d){
return d3.timeFormat("%Y-%m-%d")(d)
}
Insert cell
format_month = d3.timeFormat("%m-%Y")
Insert cell
date = "2018-01-16"
Insert cell
events.filter(d => parser_month(d.date) > firstDayOfMonth("2023-01-01"))
Insert cell
function lastDayOfMonth(date) {
var input = new Date(date)
return new Date(input.getFullYear(), input.getMonth()+1, 0);
}
Insert cell
function firstDayOfMonth(date) {
var input = new Date(date)
return new Date(input.getFullYear(), input.getMonth(), 1);
}
Insert cell
input_date = new Date(Date.now())
Insert cell
function addMonths(input_date, months) {
const date = new Date(input_date);
date.setMonth(date.getMonth() + months);

return date;
}
Insert cell
firstDayOfMonth(input_date)
Insert cell
html`<link rel="stylesheet" href="${await require.resolve(
`tippy.js/themes/${tippytheme}.css`
)}">`
Insert cell
html`<style>
.highlight { stroke: #444; stroke-width: 2px }
`
Insert cell
import {addTooltips} from "@mkfreeman/plot-tooltip"
Insert cell
import {mdPlus} from "@tmcw/bonus-markdown-flavor"
Insert cell
viewof start = Inputs.date({label: "Date de début", value: utc_to_string(firstDayOfMonth(input_date))})
Insert cell
viewof end = Inputs.date({label: "Date de fin", value: utc_to_string(lastDayOfMonth(addMonths(input_date, 3)))})
Insert cell
chart = addTooltips(
Calendar2(
events,
{
fill: d => ["SSPHub", "Etalab, programme 10%"].includes(d.organizer) ? d.organizer : "Autre",
dayFormat: d => d.toLocaleString("fr", { weekday: "short", timeZone: "UTC" }),
title: d => `${d.name} \nOrganisateur: ${d.organizer}`,
StartDate: start,
EndDate: end,
color : {
type: "categorical",
scheme: "set1"
}
},
{ fill: "gray", opacity: 0.5, "stroke-width": "3px", stroke: "red" }
)
)
Insert cell
// add tippy to the chart's dots
hover(d3.select(chart).selectAll("rect"), invalidation)
Insert cell
html`<link rel="stylesheet" href="${await require.resolve(
`tippy.js/themes/${tippytheme}.css`
)}">`
Insert cell
html`<style>
.highlight { stroke: #444; stroke-width: 2px }
`
Insert cell
tippytheme
Insert cell
import { viewof tippytheme, hover } with { tipcontent } from "@fil/hello-tippy"
Insert cell
function tipcontent(i) {
return html`<div>
<strong>${events[i].name}</strong>
<div style="max-height: 11em; overflow: auto">
<div><img src="${events[i].reference}featured.png" height=120 /></div>
<div href="${events[i].reference}">Link to event</div>
</div>
</div>`;
}
Insert cell
tipcontent(0)
Insert cell
//function tipcontent(i) {
// const d = events[i];
//
// return md`<div style="max-height: 14em; overflow: auto; width: 340px;">
// <strong>${d.name}</strong>
//<div>${d.organizer}</div></div>` ;
//}
Insert cell
function create_event(date, name, organizer, reference){
let d = {
"date": date, "name": name, "organizer": organizer, "reference": reference
} ;
return d;
}
Insert cell
Calendar2 = {
// default accessors
function valueAccessor(d) {
return typeof d === "object" && ("value" in d ? d.value : d[1]);
}

function dateAccessor(d) {
return typeof d === "object" && ("date" in d ? d.date : d[0]);
}

// https://github.com/d3/d3-time/blob/main/src/utcWeek.js#L4-L13
function utcWeekday(i) {
return d3.timeInterval(
(date) => {
date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 7 - i) % 7));
date.setUTCHours(0, 0, 0, 0);
},
(date, step) => date.setUTCDate(date.getUTCDate() + step * 7),
(start, end) => (end - start) / 604800000 // durationWeek;
);
}

return function (
data = [],
{
date = dateAccessor,
value = valueAccessor,
reduce = (d) => d[0],
width = 726,
gap = 0.15,
color,
fill = value || "steelblue",
textFill = "white",
title,
colors = {
base: "#eee",
today: "red"
},
StartDate = null,
EndDate = null,
weekStart = 0, // 1 for Monday-based weeks.
daysToShow = d3.range(7).map((d) => (d + +weekStart) % 7),
weekNumber,
locale = "en-US",
weekNumberFormat = +weekStart === 0 ? "%U" : "%W",
dayFormat = (d) =>
d.toLocaleString(locale, { weekday: "narrow", timeZone: "UTC" }),
monthFormat = (d) =>
d.toLocaleString(locale, { month: "short", timeZone: "UTC" }),
fy, // options for fy, e.g. reverse: false
} = {}
) {
// rollup the data into days
data = Array.from(data, (d) => (typeof d === "string" ? [d, ""] : d));
const dates = Plot.valueof(data, date);
const marked = d3.rollup(data, reduce, (d, i) =>
dates[i] instanceof Date ? dates[i] : d3.isoParse(dates[i])
);
const days = [...marked.keys()].filter((d) => !isNaN(d.getTime())); // filter out invalid dates
if (days.length === 0) days.push(new Date());
const e = d3.extent(days);

// responsive
const W = width < 726 ? "H" : "Y";

// sort all days, with days containing information put at the beginning of the
// array (so we can pass channels as arrays, e.g. fill: [1, 2, 3] for three events)
const fullExtent = [
d3.utcYear.floor(e[0]),
d3.utcYear.offset(d3.utcYear.floor(e[1]))
];
// filter out empty semesters
if (W === "H") {
if (e[0].getUTCMonth() >= 6)
fullExtent[0] = d3.utcMonth.offset(fullExtent[0], 6);
if (e[1].getUTCMonth() < 6)
fullExtent[1] = d3.utcMonth.offset(fullExtent[1], -6);
}
const alldays = new Set([...days, ...d3.utcDays(...fullExtent)]);

// copy the rolled-up data into the days array
data = Array.from(alldays, (date) => ({
date,
...(marked.has(date)
? { ...marked.get(date), date, foreground: true }
: { background: true })
}));



// weekStart and weekNumber
const utcWeek = utcWeekday((weekStart = +weekStart));
if (typeof weekNumberFormat === "string")
weekNumberFormat = d3.utcFormat(weekNumberFormat);
if (![0, 1].includes(weekStart))
throw new Error("unsupported weekStart value");

const weekX =
W === "H"
? (d) =>
+utcWeek.count(d3.utcYear(d), d) -
26.2 * (d.getUTCMonth() >= 6) +
gap * d.getUTCMonth()
: (d) => +utcWeek.count(d3.utcYear(d), d) + gap * +d.getUTCMonth();
const height =
(d3.utcMonths(...d3.extent(alldays)).length / 12) *
(daysToShow.length + 2) *
17 *
(W === "H" ? 2 : 1);

// We want the UTC date that corresponds to our local calendar date
const now = new Date();
const today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());

// formats
if (typeof dayFormat !== "function") dayFormat = d3.utcFormat(dayFormat);
if (typeof monthFormat !== "function")
monthFormat = d3.utcFormat(monthFormat);

// positions
const barOptions = {
x1: (d) => -0.45 + weekX(d.date),
x2: (d) => 0.5 + weekX(d.date),
y: (d) => d.date.getUTCDay(),
insetBottom: 1
};
const textOptions = {
x: (d) => weekX(d.date),
y: (d) => d.date.getUTCDay(),
text: (d) => d.date.getUTCDate(),
fontSize: 8,
pointerEvents: "none"
};

// default title
if (title === undefined) {
const values = Plot.valueof(data, value);
const format = d3.format("~f");
const formatValue = (d) => (typeof d === "number" ? format(d) : d);
title = Plot.valueof(data, (d, i) =>
d.foreground
? `${new Intl.DateTimeFormat(locale, { timeZone: "UTC" }).format(
d.date
)}: ${formatValue(values[i])}`
: undefined
);
}

if (StartDate != null){
data = data.filter(d => d.date > firstDayOfMonth(StartDate))
}
if (EndDate != null){
data = data.filter(d => d.date <= lastDayOfMonth(EndDate))
}

const p = Plot.plot({
width,
marginTop: 0,
marginBottom: 0,
marginLeft: W === "H" ? 70 : 40,
height,
facet: {
data,
y:
W === "H"
? (d) =>
`${d.date.getUTCFullYear()} H${
d.date.getUTCMonth() < 6 ? "1" : "2"
}`
: (d) => `${d.date.getUTCFullYear()}`
},
y: {
// -2/-1 is for the legend/week number, 0=Sun, 1=Mon… 6=Sat
domain: weekNumber ? [-2, -1, ...daysToShow] : [-1, ...daysToShow],
tickFormat: (day) =>
day < 0 ? "" : dayFormat(d3.isoParse(`2000-02-2${day}`)),
tickSize: 0
},
x: { axis: null },
fy: { reverse: true, axis: null, ...fy },
color,
marks: [
// cells
[
colors.base &&
Plot.barX(data, {
filter: "background",
...barOptions,
fill: colors.base
}),
Plot.barX(data, {
filter: "foreground",
...barOptions,
fill,
title
}),
colors.today &&
Plot.barX(data, {
filter: (d) => +d.date === +today,
...barOptions,
fill: "none",
stroke: colors.today
})
],

// labels
[
Plot.text(data, {
filter: "background",
...textOptions,
fill: "black"
}),
Plot.text(data, {
filter: "foreground",
...textOptions,
fill: textFill
})
],

// years and months
[
Plot.text(
data,
Plot.selectMinX({
filter: (d) => d.date.getUTCDay() === weekStart,
x: (d) => weekX(d.date),
y: weekNumber ? -2 : -1,
text: (d) => monthFormat(d.date),
z: (d) => d.date.getUTCMonth()
})
),
Plot.text(
data,
Plot.selectFirst({
sort: "date",
x: 0,
y: weekNumber ? -2 : -1,
text:
W === "H"
? (d) =>
d.date.getUTCFullYear() +
(d.date.getUTCMonth() < 6 ? " H1" : "H2")
: (d) => `${d.date.getUTCFullYear()}`,
textAnchor: "end",
fontWeight: "bold",
dx: -14
})
)
],

// week numbers
weekNumber
? Plot.text(
data,
Plot.selectFirst({
filter: (d) => d.date.getUTCDay() === (weekStart + 6) % 7,
x: (d) => weekX(d.date),
y: -1,
text: (d) => weekNumberFormat(d.date),
fontSize: 7,
fill: "grey",
z: (d) => weekNumberFormat(d.date)
})
)
: null
]
});

p.appendChild(html`<style>.plot text { pointer-events: none }`);
return p;
};
}
Insert cell
To use, import the function in a cell:
~~~js
import {Calendar2} from "@linogaliana/calendar"
~~~

then call it:
~~~js
Calendar2(data, options)
~~~

where **data** is an *iterable* of calendar items.

The **options** are:

* **date** — the date accessor; defaults to the first element of the item. The date is expected to be set in UTC—otherwise a viewer who is not in the same timezone as the author might see 1-day shifts. You can pass the date as an ISO string format (like "2022-03-01"), which will be converted via [d3.isoParse](https://github.com/d3/d3-time-format#isoParse)—this is the recommended (risk-free) approach.

* **value** — value accessor; defaults to the second element of the item.

* **reduce** — a function that decides what happens when multiple events happen on the same day. Defaults to “first”.

* **width** — the width of the calendar in pixels; defaults to 726; if smaller than 726, the calendar will be displayed by semester—allowing a responsive layout.

* **gap** — the gap between months, as a percentage of the cell’s width (defaults to 0.15).

* **color** — a color scale options object to pass to Plot.

* **fill** — controls the color of the marked dates; defaults to *value* if a value was specified, steelblue otherwise.

* **textFill** — text color for marked days; defaults to white.

* **title** — a title attribute.

* **colors** — an object with specific color constants: { base: "#eee", today: "red" }. colors.base will be used to display the background of unmarked days. colors.today is the outline of the current day.

* **weekStart** - 0 for Sunday-based weeks (default); 1 for Monday-based weeks.

* **daysToShow** — which days of the week to show, as an array of weekday numbers (defaults to [0, 1, 2, 3, 4, 5, 6], the whole week—[1, 2, 3, 4, 5, 6, 0] for Monday-based weeks).

* **weekNumber** — should we display the week number? Defaults to false.

* **locale** — the locale for date, days and months formatting; defaults to en-US.

* **dayFormat** - A formatter function for the days; if specified as a string, it is passed to d3.utcFormat (in the default English locale). Defaults to the initial of the day’s name.

* **monthFormat** - A formatter function for the months; if specified as a string, it is passed to d3.utcFormat (in the default English locale). Defaults to the short month name.

* **weekNumberFormat** - A formatter function to receive the date of the last day of the week; if specified as a string, it is passed to d3.utcFormat. %V formats the week as [ISO 8601 week of the year](https://en.wikipedia.org/wiki/ISO_week_date); defaults to [%U for Sunday-based, %W for Monday-based weeks](https://github.com/d3/d3-time-format). Week numbers are expressed as a decimal number [01, 53].

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more