Published
Edited
Jan 22, 2021
Insert cell
Insert cell
Insert cell
viewof afm = drawTernaryPlot(afmDiagram)
Insert cell
dividingLine = [
{ a: 35, f: 15, m: 50 },
{ a: 52, f: 23, m: 25 },
{ a: 35, f: 60, m: 5 }
]
Insert cell
dividingLineTernary = dividingLine
.map(afmBarycentric)
.map(([x, y]) => [x * radius, y * radius])
// .map(afmDiagram.convert)
Insert cell
makeCurvedLine = d3.line().curve(d3.curveCardinal)
Insert cell
drawTernaryPlot = someTernaryPlot => {
const svg = d3.select(DOM.svg(width, height));

const radius = someTernaryPlot.radius();

const chart = svg
.append("g")
.attr("transform", `translate(${width / 2} ${height / 2 + yOffset})`)
.attr("height", 300)
.attr("font-family", "sans-serif")
.attr("id", "chart")
.on("mousemove", d => {
const xy = d3.pointer(d);
mutable inverse = afmDiagram.invert(xy);
});

const defs = chart.append("defs");

const clipPath = defs
.append("clipPath")
.attr("id", "trianglePath")
.append("path")
.attr("d", someTernaryPlot.triangle());

const axisLabelsGroup = chart
.append("g")
.attr("class", "axis-labels")
.call(axisLabels, someTernaryPlot.axisLabels());

const gridLinesPaths = someTernaryPlot
.gridLines()
.map(axisGrid => axisGrid.map(d3.line()).join(" "));

const gridGroup = chart
.append("g")
.attr("class", "grid")
.call(grid, gridLinesPaths);

const axisTicksGroups = chart
.append("g")
.attr("class", "ternary-ticks")
.attr("font-size", 10)
.selectAll("g")
.data(someTernaryPlot.ticks())
.join("g")
.attr("class", "axis-ticks");

const trianglePath = chart
.append("path")
.attr("d", someTernaryPlot.triangle())
.attr("id", "triangle")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 2);

axisTicksGroups.call(ticks);

const dividingPath = chart
.append("path")
.attr("id", "divide")
.attr("d", makeCurvedLine(dividingLineTernary))
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 2);

return svg.node();
}
Insert cell
axisLabels = (g, labels) =>
g
.append("g")
.attr("class", "labels")
.attr("font-size", 16)
.selectAll("text")
.data(labels)
.join("text")
.attr("dy", (d, i) => (i === 2 ? "-0.5em" : ".5em"))
.attr(
"transform",
(d, i) => `translate(${d.position})rotate(${d.labelAngle})`
)
.attr("text-anchor", "middle")
.text(d => d.label)
Insert cell
ticks = (g, ticks) =>
g
.selectAll("g")
.data(d => d, d => d.tick)
.join(
enter => {
const tickGroups = enter
.append("g")
.attr("class", "tick")
.attr("transform", d => `translate(${d.position})`);

tickGroups
.append("text")
.attr("text-anchor", d => d.textAnchor)
.attr("transform", d => `rotate(${d.angle})`)
.attr("dx", d => (-d.size - 5) * (d.textAnchor === "start" ? -1 : 1))
.attr("dy", ".5em")
.text(d => d.tick);

tickGroups
.append("line")
.attr("transform", d => `rotate(${d.angle + 90})`)
.attr("y2", d => d.size * (d.textAnchor === "start" ? -1 : 1))
.attr("stroke", "grey");

return tickGroups;
},
update =>
update
.attr("fill", "steelblue")
.attr("transform", d => `translate(${d.position})`),
exit => exit.attr("opacity", 0).remove()
)
Insert cell
grid = (g, gridLines) =>
g
.selectAll("path")
.data(gridLines)
.join(
enter =>
enter
.append("path")
.attr("d", d => d)
.attr("stroke", "#f1f1f1")
.attr("stroke-width", (d, i) => (i & 1 ? 1 : 2)),
update => update.attr('d', d => d)
// theres no exit, lines are only drawn upto 'initial' triangle bounds
)
Insert cell
afmDiagram = ternaryPlot(afmBarycentric)
.radius(radius)
.labels(["A", "F", "M"])
.gridLineCounts([20, 20, 20])
.tickCounts([10, 10, 10])
.labelAngles([0, 0, 0])
Insert cell
mutable inverse = []
Insert cell
afmBarycentric = barycentric()
.a(d => d.a)
.b(d => d.f)
.c(d => d.m)
Insert cell
format = d3.format(".2%")
Insert cell
Insert cell
height = 640
Insert cell
radius = Math.min(width, height) / 2
Insert cell
// to keep the triangle centered
yOffset = radius / 4
Insert cell
Insert cell
import {
barycentric,
ternaryPlot
} from "@julesblm/scratchpad-d3-ternary-plot-ii"
Insert cell
d3 = require('d3@v6')
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