Public
Edited
Apr 11, 2024
1 star
Also listed in…
SVG
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const grid = Grid({divisions: 3, aspect: {tall: 2, wide: 16}, opacity: .2}).sketch();
const {pad, plot, M} = grid;
const area = pad
.append("g")
.style("font", font.short)
;
const textBox = area
.append("g")
.attr("transform", `translate(${M(3)}, ${M(1)})`)
;
textBox
.append("text")
.text(text)
;
WebFont.load({
google: {families: [`${font.fontFamily}:wght@100..1000&swap`]},
loading: set(viewof status, `loading ${font.fontFamily}…`),
// https://github.com/typekit/webfontloader#events
// active gets called as soon as the loaded font is ready to use
//
// also exploits https://observablehq.com/@observablehq/synchronized-inputs
// to expose the proper measures externally
active: () => {
set(viewof bb, box(grid, area, textBox, bbox(font, text)));
set(viewof status, `${font.fontFamily} loaded.`)
},
// omit the () => like so:
// active: set(viewof bb, box(grid, area, textBox, bbox(font, text))),
// and the values are almost always off in the beginning
// because bbox() is called *before* the font is completely loaded
});

return plot();
}
Insert cell
viewof status = Inputs.input()
Insert cell
// bb (synchronized input) is set as soon as font is ready to use
bb
Insert cell
// ref https://talk.observablehq.com/t/getbbox/1222
bbox = (font, text) => {
const svg = d3.create('svg');
const tmp = svg.append("text").text(text).style("font", font.short);
document.body.appendChild(svg.node());
const bbox = tmp.node().getBBox();
document.body.removeChild(svg.node());
tmp.remove();
return exposeProps(bbox);
}
Insert cell
box = (grid, area, textBox, bbox) => {
const {q} = grid;
const {x, y, width, height} = bbox;
textBox
.append("rect")
.style("stroke", "red")
.style("stroke-dasharray", 5)
.style("fill", "transparent")
.attr("width", width)
.attr("height", height)
.attr("x", x)
.attr("y", y)
;
measures(grid, area, [
{label: "x", value: x},
{label: "y", value: y},
{label: "width", value: width},
{label: "height", value: height},
{label: "wide", value: width/q()},
{label: "tall", value: height/q()},
]);
return bbox;
}
Insert cell
measures = ({µ}, area, metrics) => {
const texts = area
.append("g")
.attr("transform", `translate(${µ(0)}, ${µ(5)})`)
.style("font", "15px normal var(--monospace)")
.style("fill", "grey")
.attr("text-anchor", "end")
.selectAll("text")
.data(metrics)
;
const linespacing = (_, i) => µ(4 * i - 1)
texts
.join("text")
.text(d => `${d.label}:`)
.attr("dx", µ(12))
.attr("dy", linespacing)
;
texts
.join("text")
.text(d => d.value.toFixed(1))
.attr("dx", µ(24))
.attr("dy", linespacing)
;
}
Insert cell
font = ({
short: `${fontWeight} ${fontSize}px ${fontFamily}`,
fontFamily,
fontWeight,
fontSize,
})
Insert cell
fonts = [
"Roboto Condensed",
"Roboto Slab",
// "Roboto Flex",
"Roboto",
"PT Sans Narrow",
"Courier Prime",
"Tangerine",
"IBM Plex Mono",
"Noto Sans Mono",
"Noto Sans",
"Tilt Neon",
// Non-Google fonts:
// "Neutraface Text Book",
// "Neutraface Condensed Thin",
// "Taz SemiBold",
]
Insert cell
// see https://observablehq.com/@observablehq/synchronized-inputs
viewof bb = Inputs.input()
Insert cell
// see https://observablehq.com/@observablehq/synchronized-inputs
function set(input, value) {
input.value = value;
input.dispatchEvent(new Event("input", {bubbles: true}));
}
Insert cell
// convert hidden Object (e.g. FontFace) to plain Object
// kudos to Fabian Iwand who drew my attention to it:
// ref https://talk.observablehq.com/t/getbbox-shows-previous-bbox/9128
exposeProps = f => Object.fromEntries(Object.keys(f.constructor.prototype).map(k => [k, f[k]]))
Insert cell
// convert list of FontFaces to list of plain Objects
// Inputs.table([...document.fonts].map(exposeProps))
Insert cell
WebFont = require("https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js")
Insert cell
Insert cell
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