Public
Edited
Jan 10, 2024
1 fork
1 star
Insert cell
Insert cell
Insert cell
cardWidth = 600*2
Insert cell
cardHeight = 300
Insert cell
Insert cell
Insert cell
Insert cell
viewof fontSize = Inputs.range([50, 400], { value: 160, step: 1 })
Insert cell
Insert cell
textPathSvgString = makerjs.exporter.toSVG(textModel) // { fill: "black" }
Insert cell
textPathSvgEl = svg`${textPathSvgString}`
Insert cell
textWidth = Number(textPathSvgEl.getAttribute("width"))
Insert cell
textHeight = Number(textPathSvgEl.getAttribute("height"))
Insert cell
textPathElement = textPathSvgEl.querySelector("path")
Insert cell
textPath = textPathElement.getAttribute("d")
Insert cell
textPathSvg = svg`<svg width="${width}">
<path d="${textPath}" stroke="black" />
</svg>`
Insert cell
viewof expandWidth = Inputs.range([0, 24], {
value: 14,
step: 1,
label: "Expand width"
})
Insert cell
expanded = makerjs.model.outline(textModel, expandWidth)
Insert cell
expandSvg = {
const svg = html`${makerjs.exporter.toSVG(expanded, {
viewbox: false
// origin: [0, 0]
})}`;
return svg;
}
Insert cell
secondExpandWidth = 3.5
Insert cell
secondExpansion = makerjs.model.outline(expanded, secondExpandWidth)
Insert cell
secondExpandSvg = {
const svg = html`${makerjs.exporter.toSVG(secondExpansion, {
viewbox: false
})}`;
return svg;
}
Insert cell
secondExpandPath = secondExpandSvg.querySelector("path").getAttribute("d")
Insert cell
secondExpandPoints = polygonSampledFromPath(secondExpandPath, 200).map(
roundPoint
)
Insert cell
expandX = cardWidth / 2 - textWidth / 2 - totalExpansion
Insert cell
expandY = cardHeight / 2 - textHeight / 2 - totalExpansion
Insert cell
roundPoint = ([x, y]) => [
Math.round((x + Number.EPSILON) * 100) / 100,
Math.round((y + Number.EPSILON) * 100) / 100
]
Insert cell
// to center the expanded text outline polygon
expandedTextPolygon = secondExpandPoints
// .map(([x, y]) => [x + expandX, y + expandY])
.map(roundPoint)
Insert cell
svg`<svg width="${width}" height="${textHeight * 1.5}">
<polygon points="${secondExpandPoints.join(" ")}" fill="none" stroke="black"/>
</svg>`
Insert cell
totalExpansion = expandWidth * 3
Insert cell
googleFonts = fetch(
`https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyAOES8EmKhuJEnsn9kS1XKBpxxp-TgN8Jc`
)
.then((res) => res.json())
.then((r) => r.items)
Insert cell
font = googleFonts.filter((d) => d.family === selectedFont)?.[0]
Insert cell
fontUrl = font.files[fontVariant].replace("http", "https")
Insert cell
openTypeFont = new Promise((res, rej) =>
openType.load(fontUrl, (err, font) => {
if (err) {
console.error(err);
rej(err);
}
res(font);
})
)
Insert cell
openType
Insert cell
Insert cell
padding = 1
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// colorscheme = d3.schemeYlGnBu[8]
colorscheme = d3.schemeGreys[6]
Insert cell
c = colorscheme
.map((d) => d3.cubehelix(d))
.sort((a, b) => d3.ascending(a.h, b.h))
.map((d) => d.formatHsl())
Insert cell
pack = ({ inPolygon, outPolygon, circles }) => {
const [[x1, y1], [x2, y2]] = geometric.polygonBounds(inPolygon),
width = x2 - x1,
height = y2 - y1;

let r, x, y;

let safe = false,
iters = 0;

while (!safe) {
let intersection = false;
r = 1 + Math.random() * (size / 10);
x = x1 + r + Math.random() * (width - r * 2);
y = y1 + r + Math.random() * (height - r * 2);

const p = [x, y];

if (
geometric.pointInPolygon(p, inPolygon) &&
!geometric.pointInPolygon(p, outPolygon) &&
// && circleInPolygon({x, y, r, polygon: inPolygon})
circleInPolygon({ x, y, r, polygon: outPolygon })
) {
for (let i = 0, l = circles.length; i < l; i++) {
const c = circles[i];
const l = geometric.lineLength([p, [c.x, c.y]]);
if (l < r + c.r + 1) {
intersection = true;
break;
}
}
safe = !intersection;
} else {
safe = false;
}

iters++;
}

if (safe) {
circles.push({ x, y, r, iters, index: circles.length });
}
}
Insert cell
// https://observablehq.com/@mbostock/closest-point-on-line
function pointToLine(point, line) {
const [[x1, y1], [x2, y2]] = line;
const [x3, y3] = point;
const x21 = x2 - x1;
const y21 = y2 - y1;
const x31 = x3 - x1;
const y31 = y3 - y1;
const t = Math.max(
0,
Math.min(1, (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21))
);

return [x1 + (x2 - x1) * t, y1 + (y2 - y1) * t];
}
Insert cell
function circleInPolygon({x, y, r, polygon}) {
let inside = true;
for (let i = 0, l = polygon.length; i < l; i++) {
const line = [polygon[i], polygon[i + 1] || polygon[0]];
const point = [x, y];
if (geometric.lineLength([point, pointToLine(point, line)]) < r) {
inside = false;
break;
}
}
return inside;
}
Insert cell
circles = {
const circles = [];
for (let i = 0; i < N; i++) {
pack({ inPolygon: square, outPolygon: expandedTextPolygon, circles });
}
return circles;
}
Insert cell
size = Math.min(width, 300)
Insert cell
// https://observablehq.com/@mayagans/path-to-polygon
polygonSampledFromPath = (path, samples) => {
path = svg`<path d="${path}">`;
var poly = document.createElementNS("http://www.w3.org/2000/svg", "polygon");

var points = [];
var len = path.getTotalLength();
var step = (step = len / samples);

for (var i = 0; i <= len; i += step) {
var p = path.getPointAtLength(i);
points.push([p.x, p.y]);
}

return points;
}
Insert cell
geometric = require("geometric@2")
Insert cell
makerjs = require("https://bundle.run/makerjs@0.17.0")
Insert cell
openType = require("opentype.js")
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