Public
Edited
Nov 23, 2023
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sales = FileAttachment("sales.csv").csv({typed: true})
Insert cell
Insert cell
Plot.rect(
sales,
marimekko({ x: "market", y: "segment", value: "value", fill: "segment" })
).plot()
Insert cell
Insert cell
Plot.rect(
sales,
marimekko({ sequence: "yx", x: "market", y: "segment", value: "value", fill: "segment" })
).plot()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
((sales) =>
Plot.plot({
x: { percent: true },
y: { insetTop: 20, percent: true },
width,
height: 540,
facet: { data: sales, x: "year" },
marks: [
Plot.rect(
sales,
marimekko({
sequence,
reverseX,
reverseY,
x: "market",
y: "segment",
value: "value",
fill: "segment",
fillOpacity: 0.2
})
),
Plot.ruleY([1]),
Plot.text(
sales,
marimekko({
sequence,
reverseX,
reverseY,
anchor: "top-left",
x: "market",
y: "segment",
value: "value",
text: "value",
fontWeight: "bold",
dx: 4,
textAnchor: "start",
dy: 10
})
),
Plot.text(
sales,
Plot.selectMinX(
marimekko({
sequence,
reverseX,
reverseY,
anchor: "top-left",
x: "market",
y: "segment",
value: "value",
text: "segment",
z: "segment",
dx: 4,
textAnchor: "start",
dy: 22
})
)
),
Plot.text(
sales,
Plot.selectMaxY(
marimekko({
sequence,
reverseX,
reverseY,
anchor: "top-left",
x: "market",
y: "segment",
value: "value",
text: "market",
z: "market",
dx: 4,
textAnchor: "start",
dy: -10
})
)
),
Plot.frame()
]
}))(fakeFacets)
Insert cell
fakeFacets = {
const random = d3.randomLcg(42);
return sales.flatMap((d) =>
[2012, 2020].map((year) => ({
...d,
value: (d.value + 500 * random()) | 0,
year
}))
);
}
Insert cell
Insert cell
function marimekko({
sequence = "xy",
anchor = "",
reverseX,
reverseY,
...options
}) {
const [first, second] = maybeAnchor("" + anchor);

switch (sequence) {
case "xy":
return marimekkoXY(
Plot[["stackX", "stackX1", "stackX2"][first]],
Plot[["stackY", "stackY1", "stackY2"][second]],
{ reverseX, reverseY, ...options }
);
case "yx":
return marimekkoYX(
Plot[["stackY", "stackY1", "stackY2"][first]],
Plot[["stackX", "stackX1", "stackX2"][second]],
{ reverseX: reverseY, reverseY: reverseX, ...options }
);

default:
throw new Error(`Unkown marimekko sequence: ${sequence}`);
}

function maybeAnchor(anchor) {
let sx = 0;
let sy = 0; // middle
if (anchor.match(/^bottom\b/i)) sy = 1;
if (anchor.match(/^top\b/i)) sy = 2;
if (anchor.match(/\bleft$/i)) sx = 1;
if (anchor.match(/\bright$/i)) sx = 2;
return [sx, sy];
}
}
Insert cell
Insert cell
function marimekkoYX(sx, sy, { x: xx, y: yy, ...options }) {
const { x, x1, x2, y, y1, y2, ...rest } = marimekkoXY(sy, sx, {
x: yy,
y: xx,
...options
});
return { y: x, y1: x1, y2: x2, x: y, x1: y1, x2: y2, ...rest };
}
Insert cell
function marimekkoXY(
stackX,
stackY,
{ x, y, value, inset = 0.5, reverseX, reverseY, ...options }
) {
const [Xz, setXz] = Plot.column(value);

const { y1, y2, y: yy, transform: stackYt } = stackY({
offset: "expand",
x,
y: value,
reverse: reverseY
});
const { x1, x2, x: xx, transform: stackXt } = stackX({
offset: "expand",
y,
x: Xz,
reverse: reverseX
});
return {
x1,
x2,
y1,
y2,
x: xx,
y: yy,
transform: (data, facets) => {
const I = d3.range(data.length);
const X = Plot.valueof(data, x);
const Z = Plot.valueof(data, value);
const sum = d3.rollup(
I,
(I) => d3.sum(I, (i) => Z[i]),
(i) => X[i]
);
setXz(I.map((i) => sum.get(X[i])));
stackXt(data, facets);
stackYt(data, facets);
return { data, facets };
},
inset,
...options
};
}
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