Published
Edited
Sep 1, 2021
Insert cell
Insert cell
Insert cell
contours = d3
.contourDensity()
.x((d) => d.x)
.y((d) => d.y)
.size([width * 2, height * 2])
.bandwidth(bandwidth)
.thresholds(thresholds)(ternaryData)
Insert cell
Insert cell
Insert cell
viewof contourTernaryPlot = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const chart = svg
.append("g")
.attr("transform", `translate(${width / 2} ${height / 2 + yOffset})`)
.attr("height", 300)
.attr("font-family", "sans-serif")
.attr("id", "chart");

chart.append("circle").attr("r", 4).attr("fill", "gold");

chart
.append("circle")
.attr("r", 4)
.attr("cx", 90)
.attr("cy", -3 * bandwidth)
.attr("fill", "coral");

chart
.append("circle")
.attr("r", 4)
.attr("cx", -3 * bandwidth)
.attr("cy", 120)
.attr("fill", "coral");

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

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

// initial triangle
const trianglePath = chart
.append("path")
.attr("d", densityContourTernaryPlot.triangle())
.attr("fill", "none")
.attr("stroke-width", "none");

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

const gridLinesPaths = densityContourTernaryPlot
.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(densityContourTernaryPlot.ticks())
.join("g")
.attr("class", "axis-ticks");

axisTicksGroups.call(ticks);

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

// data
const dots = chart
.append("g")
.attr("stroke", "white")
.attr("class", "data")
.attr("clip-path", "url(#trianglePath)")
.selectAll("circle")
.data(ternaryData)
.join("circle")
.attr("r", 2)
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y);

dots.append("title").text(
(d) => `${d.country}

Agriculture: ${d.agriculture}
Industry: ${d.industry}
Service: ${d.service}`
);

chart
.append("g")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("clip-path", "url(#trianglePath)")
.selectAll("path")
.data(contours)
.join("path")
.attr("stroke-width", (d, i) => (i % 5 ? 0.25 : 1))
.attr("d", d3.geoPath()); // a geoJSON multiPolygon is passed to d3.geoPath to display, using null or d3.geoIdentity

return svg.node();
}
Insert cell
contourBarycentric = barycentric()
.a(d => d.agriculture)
.b(d => d.industry)
.c(d => d.service)
Insert cell
ternaryData = data.map(d => {
const [x, y] = densityContourTernaryPlot(d);
return { x, y, ...d };
})
Insert cell
densityContourTernaryPlot = ternaryPlot(contourBarycentric)
.radius(radius)
.labels(["Agriculture (A)", "Industry (B)", "Service (C)"])
Insert cell
Insert cell
// to keep the triangle centered
yOffset = radius / 4
Insert cell
radius = Math.min(width, height) / 2
Insert cell
height = 700
Insert cell
Insert cell
import {
data,
ternaryPlot,
barycentric,
grid,
ticks,
axisLabels
} from "@julesblm/d3-ternary"
Insert cell
import { Range } from "@observablehq/inputs"
Insert cell
d3 = require("d3@7", "d3-contour@3")
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