Published
Edited
Jul 12, 2022
11 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function flower(appraisal, probeIndex, rotation, scale) {
const probe = appraisal.probes[probeIndex];
setupGradients(appraisal, probeIndex);
let g = "";
g += grid();
g += ripples(numberOfRipples, scale);
g += label(appraisal, probeIndex);
let a = "";
a += defs(appraisal);
a += "<!-- Petals -->";
probe.scores.forEach((score, i) => {
a += layersOfPetals(score.mean / appraisal.max, numberOfRipples, i);
if (showRadials)
a += `<line class="ripple" x1="0" y1="0" x2="100" y2="0" />`;
});
return `${g}<!-- Flower --><g class="petal-stroke" transform="rotate(${rotation}) scale(${scale})">${a}</g>`;

function head(appraisal, probeIndex) {
let h = "";
h += title(appraisal, probeIndex);
return h;

function title(appraisal, probeIndex) {}
}

// Define the global gradients and blurs.
function defs(appraisal) {
let gradients = "";
appraisal.appearance.forEach(segment => {
gradients += `${segment.gradient.value}`;
});
let filters = "";
appraisal.appearance.forEach(segment => {
filters += `${segment.blur.value}`;
});
return `<!-- Gradients & Blurs --><defs>${gradients}${filters}</defs>`;
}

// Draw rippleCount layered petals for a specific segment and in its appropriate segment color,
// as a function of the value.
function layersOfPetals(value, rippleCount, segment) {
let a = "";
a += `transform="rotate(${(segment * 360) / probe.scores.length})"`;
a += `fill="${appraisal.appearance[segment].gradient.id}"`;
if (!navigator.userAgent.match("HeadlessChrome"))
// kludge: avoid rendering blur on thumbnail machine
a += `filter="${appraisal.appearance[segment].blur.id}"`;
return `<g ${a}>${petals(rippleCount)}</g>`;

// Draw each petal at its approprate scale as a function of the number of ripples.
function petals(rippleCount) {
let a = "";
for (let i = rippleCount; i >= 0; i--) {
// rippleCount layers, smaller ones on top of larger ones
let scale =
(i + 1) / rippleCount < value ? (i + 1) / rippleCount : value;
a += `<path transform="scale(${scale})"`;
a += `d="M 0 0 C ${petalLength} ${petalWidth} ${petalLength} ${-petalWidth} 0 0" `; // cubic Bézier curve
a += "/>";
}
return a;
}
}

function setupGradients(appraisal, probeIndex) {
appraisal.probes[probeIndex].scores.forEach((score, i) => {
// loop colors if appraisal.length > appearance.length
const appearance = appraisal.appearance[i % appraisal.appearance.length];
const guid = DOM.uid("gradient");
appearance.gradient = {
id: guid,
value:
`<radialGradient id="${guid.id}" cx="0%" r="100%">` +
`<stop class='heart-color' offset="${heart * 100}%" />` +
`<stop stop-color="${appearance.color}" offset="${edge * 100}%" />` +
`</radialGradient>`
};
const buid = DOM.uid("blur");
appearance.blur = {
id: buid,
value:
`<filter id="${buid.id}">` +
`<feGaussianBlur in="SourceGraphic" stdDeviation="${score.sigma *
2}" />` +
`</filter>`
};
});
return appraisal;
}

function label(appraisal, probeIndex) {
const probe = appraisal.probes[probeIndex];
const center = [0, 0];
let a = "";

probe.scores.forEach((b, i) => {
const dotColor = appraisal.appearance[i].color;
const alpha = 2 * Math.PI * (i / probe.scores.length + rotation / 360);
const bend = dotOnCircle(labelCenterDistance, alpha);
const side = bend[0] > 0 ? +1 : -1;
const edgePoint = [side * 100, bend[1]];
const textAnchor = [edgePoint[0] + side * 5, edgePoint[1]];
const statAnchor = [textAnchor[0], textAnchor[1] + 5];
const stats = `avg = ${b.mean.toFixed(1)} / σ = ${b.sigma.toFixed(1)}`;

a += polyline([center, bend, edgePoint]);
a += text("label", textAnchor, side, appraisal.appearance[i].category);
if (showStatistics) a += text("stats", statAnchor, side, stats);
a += dot(bend, dotColor);
a += dot(center, "darkred");
});

return `<!-- Labels --><g>${a}</g>`;

function polyline(l, kind = "ray") {
let a = "";
const start = l.shift();
if (start) a += `M${start[0]} ${start[1]}`;
l.forEach(p => {
a += ` L${p[0]} ${p[1]}`;
});
return `<path class="${kind}" d="${a}" />`;
}

function text(kind, [x, y], anchor, t) {
const a = anchor == 1 ? "start" : "end";
return `<text class="${kind}" text-anchor="${a}" x="${x}" y="${y}" dy="0mm"> ${t}</text>`;
}
function dotOnCircle(radius, angle) {
return [radius * Math.cos(angle), radius * Math.sin(angle)];
}
}

function ripples(numberOfRipples, scale) {
if (showRipples) {
const ripple = numberOfRipples => {
let a = "";
for (let r = numberOfRipples; r > 0; r--)
a += `<circle class="ripple" r="${(100 * r) / numberOfRipples}" />`;
return a;
};
return `<!-- Ripples --><g transform="scale(${scale})">${ripple(
numberOfRipples
)}</g>`;
}
}

function dot([x, y], color) {
const a = x.toFixed(1);
const b = y.toFixed(1);
let z = "";
if (showHandles)
z += `<!-- dot --><circle class="handle" cx=${a} cy=${b} r="3" fill="${color}" />`;
if (showCoordinates)
z += `<text dy="-1mm" class="point" x=${a} y=${b}>(${a}, ${b})</text>`;
return z;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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