Public
Edited
Oct 24, 2019
1 star
Insert cell
Insert cell
Insert cell
md`Notes and thoughts as I follow along [this](https://observablehq.com/@d3/d3-path) notebook. ☝🏻`
Insert cell
md`First insight: we can use d3.path both with canvas and svg. Cool.
Let's create a function that uses the context provided to execute **🐢**commands.
`
Insert cell
function draw(context) {
context.moveTo(10, 10); // move current point to ⟨10,10⟩
context.lineTo(100, 10); // draw straight line to ⟨100,10⟩
context.arcTo(150, 150, 300, 10, 40); // draw an arc, the turtle ends up at ⟨194.4,108.5⟩
context.lineTo(300, 10); // draw straight line to ⟨300,10⟩
// etc.
return context; // not mandatory, but will make it easier to chain operations
}
Insert cell
md`Now, let's create a canvas context and pass it to our draw function. Then we hang the canvas new element into the next cell.`
Insert cell
{
const context = DOM.context2d(width, 200);
context.beginPath();
draw(context);
context.stroke();
return context.canvas;
}
Insert cell
md`Now, our context is an object returned by d3.path. After passing that object to our draw function, the state of that object will keep all the turtle moves. When we convert to string, it generates the necessary SVG path values/commands.`
Insert cell
svg`<svg>
<path stroke="black" fill="none" d="${draw(d3.path())}" />
</svg>`
Insert cell
md`Here we are doing the same but creating the svg element from d3.`
Insert cell
d3.create("svg")
.call(svg => svg.append("path")
.style("stroke", "black")
.style("fill", "none")
.attr("d", draw(d3.path())))
.node()
Insert cell
md`Ok, let's see how we can use this. Let's say I have a dataset that shows the correlations between different entities:`
Insert cell
data = {
const edges = [];
edges.push({from: "A", to: "X", value: 10});
edges.push({from: "A", to: "Y", value: 20});
edges.push({from: "A", to: "Z", value: 30});
edges.push({from: "Longer Text", to: "X", value: 300});
edges.push({from: "B", to: "Foo Bar", value: 5000});
/*
edges.push({from: "B", to: "X", value: 10});
edges.push({from: "B", to: "Y", value: 20});
edges.push({from: "B", to: "Z", value: 30});
*/
const nodes = {
A: [100, 10],
B: [100, 30],
"Longer Text" : [100, 50],

X: [500, 30],
Y: [500, 50],
Z: [500, 70],
"Foo Bar": [500, 90],
}
return {
nodes,
edges,
}
}
Insert cell
md`Let's draw circles for the locations and connect them.`
Insert cell
drawConnector(data)
Insert cell
drawConnector = {
return function (data, width=800, height=100) {
const linkGenerator = d3.linkHorizontal().x(d => d[0]).y(d => d[1])
const svg = d3.create("svg")
.style("width", width)
.style("height", height)
.style("background", "white");
svg.selectAll("path")
.data(data.edges)
.enter()
.append("path")
.attr("d", (edge) => {
const [fromLabel, toLabel] = [edge.from, edge.to];
const [from, to] = [data.nodes[fromLabel], data.nodes[toLabel]];
console.log(from, to);
return linkGenerator({source: from, target:to});
})
.attr("class", "link")
svg.selectAll("circle")
.data(Object.keys(data.nodes))
.enter()
.append("circle")
.attr("cx", (label) => data.nodes[label][0])
.attr("cy", (label) => data.nodes[label][1])
.attr("r", 5)
.attr("class", "node")

svg.selectAll("text")
.data(Object.keys(data.nodes))
.enter()
.append("text")
.attr("x", d => data.nodes[d][0])
.attr("y", d => data.nodes[d][1])
.attr("dx", d => data.nodes[d][0] > 250 ? 10 : -10)
.attr("dy", 5)
.attr("text-anchor", d => data.nodes[d][0] > 250 ? "start" : "end")
.text(d => d)
return svg.node();
}
}
Insert cell
md`I started this notebook to explore how to visualize a dataset I am working on. I have two sets and correlations between them (${tex`||A|| \approx 100, ||B|| \approx 99`}).`
Insert cell
mosaicConnectorData = {
const edges = [];
mosaicData.forEach(({mosaic, geoSocial, correlation}) => {
edges.push({
from: mosaic,
to: geoSocial,
value: correlation,
})
})
const filteredEdges = edges.filter(e => Math.abs(e.value) > 0.15);
const nodes = {};
segmentNames.mosaics.forEach((n, i) => nodes[n] = [250, 10 + (i*15)]);
segmentNames.geoSocials.forEach((n, i) => nodes[n] = [750, 10 + (i*15)]);
return {
nodes,
edges: filteredEdges
}
}
Insert cell
mosaicData = {
const url = "https://dropbox.davidrio.org/mosaic-vs-geosocial.csv"
const data = await d3.csv(url, ({mosaic, geosocial, correlation}) => ({
mosaic: mosaic.replace(/^\s/g, ''),
geoSocial: geosocial.replace(/^\s/g, ''),
correlation: +correlation,
}));
const sortedData = data.sort((a,b) => (a.correlation > b.correlation) ? 1 : -1);
return sortedData;

}
Insert cell
segmentNames = {
function l(attr) {
const s = new Set([]);
mosaicData.forEach(row => s.add(row[attr]));
return [... s]
}
return {
mosaics: l("mosaic"),
geoSocials: l("geoSocial"),
}
}
Insert cell
d3 = require('d3')
Insert cell
function fetchImage(src) {
return new Promise((resolve, reject) => {
const image = new Image;
image.crossOrigin = "anonymous";
image.src = src;
image.onload = () => resolve(image);
image.onerror = reject;
});
}
Insert cell
html`<style>

svg text {
fill: black;
font-size: 12px;
font-family: sans-serif;
}
.node {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
font: 12px sans-serif;
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}

.link {
fill: none;
stroke: #ccc;
stroke-width: 1px;
}

</style>
`
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