Published
Edited
Apr 7, 2020
Insert cell
Insert cell
Insert cell
values = FileAttachment("values-.json").json();
Insert cell
d3 = require("d3@5", "d3-delaunay@5")
Insert cell
[d3.min(values), d3.median(values), d3.max(values)]
Insert cell
import {chart as chart1} with {values as data} from "@d3/histogram"
Insert cell
chart1
Insert cell
height=200
Insert cell
import {chart as chart2} with {values as data, height} from "@d3/histogram"
Insert cell
chart2
Insert cell
Insert cell
Insert cell
values3 = Float64Array.from({length: 2000}, d3.randomNormal(mu, 2))
Insert cell
Insert cell
x = d3.scaleLinear([-10, 10], [margin.left, width - margin.right])
Insert cell
import {chart as chart3, margin, width} with {x, values3 as data, height} from "@d3/histogram"
Insert cell
chart3
Insert cell
Insert cell
FileAttachment("temperature.csv")
Insert cell
FileAttachment("temperature.csv").text()
Insert cell
d3.csvParse(await FileAttachment("temperature.csv").text())
Insert cell
d3.csvParse(await FileAttachment("temperature.csv").text(), d3.autoType)
Insert cell
data = {
const text = await FileAttachment("temperature.csv").text();
const parseDate = d3.utcParse("%Y-%m-%d");
return d3.csvParse(text, ({date, temperature}) => ({
date: parseDate(date),
temperature: +temperature,
}));
}
Insert cell
md`计算数据的范围`
Insert cell
d3.extent(data, ({ date }) => date)
Insert cell
d3.extent(data, ({ temperature }) => temperature)
Insert cell
temperatures = data.map(({ temperature }) => temperature)
Insert cell
import {chart as temperatureHistogram} with {temperatures as data, height} from "@d3/histogram"
Insert cell
temperatureHistogram
Insert cell
Insert cell
fruits = [
{name: "🍊", count: 21},
{name: "🍇", count: 13},
{name: "🍏", count: 8},
{name: "🍌", count: 5},
{name: "🍐", count: 3},
{name: "🍋", count: 2},
{name: "🍎", count: 1},
{name: "🍉", count: 1}
]
Insert cell
fruits_x = d3.scaleLinear()
.domain([0, d3.max(fruits, ({ count }) => count)])
.range([margin.left, width - margin.right])
.interpolate(d3.interpolateRound)
Insert cell
fruits_y = d3.scaleBand()
.domain(fruits.map(({ name }) => name))
.range([margin.top, height - margin.bottom])
.padding(0.1)
.round(true)
Insert cell
fruits_x.domain()
Insert cell
fruits_x.range()
Insert cell
fruits_y.domain()
Insert cell
fruits_y.range()
Insert cell
fruits_y.bandwidth()
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}" style="max-width: ${width}px; font: 10px sans-serif">
${d3.select(svg`<g transform="translate(0,${margin.top})">`)
.call(d3.axisTop(fruits_x))
.call(g => g.select(".domain").remove())
.node()}

${d3.select(svg`<g transform="translate(${margin.left},0)">`)
.call(d3.axisLeft(fruits_y))
.call(g => g.select(".domain").remove())
.node()}

<g fill="steelblue">
${fruits.map(({name, count}) => svg`
<rect
y="${fruits_y(name)}"
x="${fruits_x(0)}"
width="${fruits_x(count) - fruits_x(0)}"
height="${fruits_y.bandwidth()}"
>
</rect>
`
)}
</g>

<g fill="white" text-anchor="end" transform="translate(-6, ${fruits_y.bandwidth()/2})">
${fruits.map(({name, count}) => svg`
<text y="${fruits_y(name)}" x="${fruits_x(count)}" dy="0.35em">
${count}
</text>
`)}
</g>
</svg>
`
Insert cell
color = d3.scaleSequential()
.domain([0, d3.max(fruits, ({ count }) => count)])
.interpolator(d3.interpolateBlues)
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}" style="max-width: ${width}px; font: 10px sans-serif">
${d3.select(svg`<g transform="translate(0,${margin.top})">`)
.call(d3.axisTop(fruits_x))
.call(g => g.select(".domain").remove())
.node()}

${d3.select(svg`<g transform="translate(${margin.left},0)">`)
.call(d3.axisLeft(fruits_y))
.call(g => g.select(".domain").remove())
.node()}

<g fill="steelblue">
${fruits.map(({name, count}) => svg`
<rect
y="${fruits_y(name)}"
x="${fruits_x(0)}"
width="${fruits_x(count) - fruits_x(0)}"
height="${fruits_y.bandwidth()}"
fill="${color(count)}"
>
</rect>
`
)}
</g>

<g fill="white" text-anchor="end" transform="translate(-6, ${fruits_y.bandwidth()/2})">
${fruits.map(({name, count}) => svg`
<text y="${fruits_y(name)}" x="${fruits_x(count)}" dy="0.35em">
${count}
</text>
`)}
</g>
</svg>
`
Insert cell
md`## 形状`
Insert cell
shape_data = d3.csvParse(await FileAttachment("aapl-bollinger.csv").text(), d3.autoType)
Insert cell
shape_x = d3.scaleUtc()
.domain(d3.extent(shape_data, d => d.date))
.range([margin.left, width - margin.right])
Insert cell
shape_y = d3.scaleLinear()
.domain([0, d3.max(shape_data, d => d.upper)])
.range([height - margin.bottom, margin.top])
Insert cell
{
let path = `M${shape_x(shape_data[0].date)},${shape_y(shape_data[0].close)}`;
for (let i = 1; i < shape_data.length; ++i) {
path += `L${shape_x(shape_data[i].date)},${shape_y(shape_data[i].close)}`;
}
return path;
}
Insert cell
line = d3.line()
.x(d => shape_x(d.date))
.y(d => shape_y(d.close))
Insert cell
line(shape_data)
Insert cell
shape_xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(shape_x).ticks(width/80).tickSizeOuter(0))
.call(g => g.select(".domain").remove())
Insert cell
shape_yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(shape_y).ticks(height / 40))
.call(g => g.select(".domain").remove())
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path
d="${line(shape_data)}"
fill="none"
stroke="steelblue"
stroke-width="1.5"
stroke-miterlimit="1"
>
</path>
</svg>
`
Insert cell
shape_area = d3.area()
.x(d => shape_x(d.date))
.y0(shape_y(0))
.y1(d => shape_y(d.close))
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path fill="steelblue" d="${shape_area(shape_data)}">
</path>
</svg>
`
Insert cell
shape_band = d3.area()
.x(d => shape_x(d.date))
.y0(d => shape_y(d.lower))
.y1(d => shape_y(d.upper))
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path fill="steelblue" d="${shape_band(shape_data)}">
</path>
</svg>
`
Insert cell
shape_middle = d3.line()
.x(d => shape_x(d.date))
.y(d => shape_y(d.middle))
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path fill="steelblue" d="${shape_band(shape_data)}">
</path>

<g fill="none" stroke-width="1.5">
<path d="${shape_middle(shape_data)}" stroke="#00f"></path>
<path d="${line(shape_data)}" stroke="#f90"></path>
</g>
</svg>
`
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path fill="steelblue" d="${shape_band(shape_data)}">
</path>

<g fill="none" stroke-width="1.5">
<path d="${shape_band.lineY0()(shape_data)}" stroke="#00f"></path>
<path d="${shape_band.lineY1()(shape_data)}" stroke="#f00"></path>
</g>
</svg>
`
Insert cell
arc = d3.arc()
.innerRadius(210)
.outerRadius(310)
.startAngle(([startAngle]) => startAngle)
.endAngle(([_, endAngle]) => endAngle)
Insert cell
arc([Math.PI / 2, Math.PI])
Insert cell
viewof n = {
const form = html`
<form style="font: 12px var(--sans-serif); display: flex; align-items: center; min-height: 33px;">
<label style="display: block;">
<input name="input" type="range" min="1" max="50" value="12" step="1"/>
n = <output name="output"></output>
</label>
</form>
`;
form.oninput = () => form.output.value = form.value = form.input.valueAsNumber;
form.oninput();
return form;
}
Insert cell
html`
<svg viewBox="-320 -320 640 640" style="max-width: 640px;">
${Array.from({ length: n }, (_, i) =>
svg`<path stroke="black"
fill="${d3.interpolateRainbow(i/n)}"
d="${arc([i/n*2*Math.PI, (i+1)/n*2*Math.PI])}"
></path>`
)}
</svg>
`
Insert cell
pieArcData = d3.pie()
.value(d => d.count)
(fruits)
Insert cell
arcPie = d3.arc()
.innerRadius(210)
.outerRadius(310)
.padRadius(300)
.padAngle(2 / 300)
.cornerRadius(8)
Insert cell
html`
<svg viewBox="-320 -320 640 640" style="max-width: 640px;" text-anchor="middle" font-family="sans-serif">
${pieArcData.map(d => svg`
<path fill="steelblue" d=${arcPie(d)}></path>
<text fill="white" transform="translate(${arcPie.centroid(d).join(",")})">
<tspan x="0" font-size="24">${d.data.name}</tspan>
<tspan x="0" font-size="12" dy="1.3em">${d.value.toLocaleString("en")}</tspan>
</text>
`)}
</svg>
`
Insert cell
md`## 动画`
Insert cell
reveal = path => path.transition()
.duration(5000)
.ease(d3.easeLinear)
.attrTween("stroke-dasharray", function() {
const length = this.getTotalLength();
return d3.interpolate(`0,${length}`, `${length},${length}`);
})
Insert cell
viewof replay = html`<button>Replay</button>`
Insert cell
replay, html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
${d3.select(svg`
<path
d="${line(shape_data)}"
fill="none"
stroke="steelblue"
stroke-width="1.5"
stroke-miterlimit="1"
stroke-dasharray="0,1"
>
</path>
`).call(reveal).node()}
</svg>
`
Insert cell
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path
d="${line(shape_data)}"
fill="none"
stroke="steelblue"
stroke-width="1.5"
stroke-miterlimit="1"
stroke-dasharray="${lineLength * t},${lineLength}"
>
</path>
</svg>
`
Insert cell
lineLength = svg`<path d="${line(shape_data)}">`.getTotalLength();
Insert cell
Insert cell
{
replay2;
const path = svg`
<path
d="${line(shape_data)}"
fill="none"
stroke="steelblue"
stroke-width="1.5"
stroke-miterlimit="1"
>
</path>
`;
const chart = svg`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
${path}
</svg>
`;
for(let i = 0, n = 300; i < n; ++i) {
const t = (i+1)/n;
path.setAttribute("stroke-dasharray", `${t*lineLength},${lineLength}`);
yield chart;
}
}
Insert cell
Insert cell
chart4 = {
const svg = d3.create("svg")
.attr("viewBox", [0,0, width, height]);
const zx = shape_x.copy();
const line = d3.line()
.x(d => zx(d.date))
.y(d => shape_y(d.close));
const path = svg.append("path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-miterlimit", 1)
.attr("d", line(shape_data));
const gx = svg.append("g")
.call(shape_xAxis, zx);
const gy = svg.append("g")
.call(shape_yAxis, shape_y);
return Object.assign(svg.node(), {
update(domain) {
const t = svg.transition().duration(750);
zx.domain(domain);
gx.transition(t).call(shape_xAxis, zx);
path.transition(t).attr("d", line(shape_data))
}
})
}
Insert cell
chart4.update(timeframe)
Insert cell
Insert cell
Insert cell
alphabet = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
Insert cell
chart5 = {
const svg = d3.create("svg")
.attr("viewBox", [0,0,width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("display", "block");
svg.selectAll("text")
.data(alphabet)
.join("text")
.attr("x", (d, i) => i * 17)
.attr("y", 17)
.attr("dy", "0.35em")
.text(d => d);
return svg.node()
}
Insert cell
randomLetters = {
while (true) {
yield d3.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.ascending);
await Promises.delay(3000);
}
}
Insert cell
chart6 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.style("display", "block");

let text = svg.selectAll("text");

return Object.assign(svg.node(), {
update(letters) {
const t = svg.transition().duration(750);

text = text
.data(letters, d => d)
.join(
enter => enter.append("text")
.attr("y", -7)
.attr("dy", "0.35em")
.attr("x", (d, i) => i * 17)
.text(d => d),
update => update,
exit => exit
.call(text => text.transition(t).remove()
.attr("y", 41))
)
.call(text => text.transition(t)
.attr("y", 17)
.attr("x", (d, i) => i * 17));
}
});
}
Insert cell
chart6.update(randomLetters)
Insert cell
Insert cell
voronoi = d3.Delaunay
.from(shape_data, d => shape_x(d.date), d => shape_y(d.close))
.voronoi([0,0,width, height])
Insert cell
html`
<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<g>`).call(shape_xAxis).node()}
${d3.select(svg`<g>`).call(shape_yAxis).node()}
<path
d="${line(shape_data)}"
fill="none"
stroke="steelblue"
stroke-width="1.5"
stroke-miterlimit="1"
>
</path>
<g fill="none" pointer-events="all" stroke="red" stroke-width="0.5">
${shape_data.map((d, i) => svg`
<path d="${voronoi.renderCell(i)}">
<title>${d.date} - ${d.close}</title>
</path>
`)}
</g>
</svg>
`
Insert cell
{
const tooltip = new Tooltip()
}
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