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

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