Published
Edited
May 13, 2021
3 stars
Insert cell
Insert cell
dashboard({
type: 'hbox',
flex: 1,
children: [{
type: 'cell',
content: 'splom',
title: 'Correlation among dimensions',
flex: 2
},{
type: 'vbox',
flex: 1,
children: [{
type: 'cell',
content: 'bars',
title: 'Selected records counted by species',
flex: 1
},{
type: 'cell',
content: 'table',
title: 'Selected records'
}]
}]
})
Insert cell
splom = {
const svg = d3.create("svg")
.attr("viewBox", [-padding, 0, w, w])
.style('flex', 1);

svg.append("style")
.text(`circle.hidden { fill: #000; fill-opacity: 1; r: 1px; }`);

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

const cell = svg.append("g")
.selectAll("g")
.data(d3.cross(d3.range(columns.length), d3.range(columns.length)))
.join("g")
.attr("transform", ([i, j]) => `translate(${i * size},${j * size})`);

cell.append("rect")
.attr("fill", "none")
.attr("stroke", "#aaa")
.attr("x", padding / 2 + 0.5)
.attr("y", padding / 2 + 0.5)
.attr("width", size - padding)
.attr("height", size - padding);

cell.each(function([i, j]) {
d3.select(this).selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x[i](d[columns[i]]))
.attr("cy", d => y[j](d[columns[j]]));
});

const circle = cell.selectAll("circle")
.attr("r", 3.5)
.attr("fill-opacity", 0.7)
.attr("fill", d => z(d.species));

cell.call(brush, circle);

svg.append("g")
.style("font", "bold 10px sans-serif")
.style("pointer-events", "none")
.selectAll("text")
.data(columns)
.join("text")
.attr("transform", (d, i) => `translate(${i * size},${i * size})`)
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(d => d);

return svg.node();
}
Insert cell
function brush(cell, circle) {
const brush = d3.brush()
.extent([[padding / 2, padding / 2], [size - padding / 2, size - padding / 2]])
.on("start", brushstarted)
.on("brush", brushed)
.on("end", brushended);

cell.call(brush);

let brushCell;

// Clear the previously-active brush, if any.
function brushstarted() {
console.log("brushstarted", brushCell === this);
if (brushCell !== this) {
d3.select(brushCell).call(brush.move, null);
brushCell = this;
}
}

// Highlight the selected circles.
function brushed([i, j]) {
if (d3.event.selection === null) return;
const [[x0, y0], [x1, y1]] = d3.event.selection;
let acc = [];
circle.classed("hidden", d => {
let outside = x0 > x[i](d[columns[i]])
|| x1 < x[i](d[columns[i]])
|| y0 > y[j](d[columns[j]])
|| y1 < y[j](d[columns[j]]);
if(!outside && !(acc.includes(d))){ acc.push(d); }
return outside;
});
mutable selection = acc;
}

// If the brush is empty, select all circles.
function brushended() {
if (d3.event.selection !== null) return;
circle.classed("hidden", false);
mutable selection = data;
}
}
Insert cell
viewof table = Table(selection, {
height: 400,
columns: ['species', 'sepalLength', 'sepalWidth', 'petalLength', 'petalWidth'],
format: {
species: x => html`<span style="color: ${z(x)};">${x}</span>`,
sepalLength: x => sparkbar(d3.max(data, d => d.sepalLength), x, oneDecimal),
sepalWidth: x => sparkbar(d3.max(data, d => d.sepalWidth), x, oneDecimal),
petalLength: x => sparkbar(d3.max(data, d => d.petalLength), x, oneDecimal),
petalWidth: x => sparkbar(d3.max(data, d => d.petalWidth), x, oneDecimal)
}
})
Insert cell
html`<style>
td {
padding: 3px !important;
margin: 0 !important;
}
td:not(:last-child) {
border-right: 1px solid #eee;
}
</style>`
Insert cell
function sparkbar(max, x, format) {
let w = `${100 * x / max}%`
return html`
<div style="
background-image: linear-gradient(to right, #e6e6e6, #e6e6e6 ${w}, white ${w}, white);
width: 100%;
height: 100%;
padding: 2px;
box-sizing: border-box;">
${format(x).toLocaleString("en")}
</div>`
}
Insert cell
oneDecimal = d3.format('.1f')
Insert cell
bars = svg`<svg style="flex: 1;" viewBox="0 0 500 190">
<defs>
<style>
.shadow { fill: #EEE; }
text { font-family: sans-serif; font-size: 16px; }
</style>
</defs>
<rect class="shadow" x="10" y="10" width="480" height="50"/>
<rect class="shadow" x="10" y="70" width="480" height="50"/>
<rect class="shadow" x="10" y="130" width="480" height="50"/>

<rect fill="${z(species[0])}" x="10" y="10" width="${sel_by_species.has(species[0]) ? bar_x(sel_by_species.get(species[0]).length) : 0}" height="50"/>
<rect fill="${z(species[1])}" x="10" y="70" width="${sel_by_species.has(species[1]) ? bar_x(sel_by_species.get(species[1]).length) : 0}" height="50"/>
<rect fill="${z(species[2])}" x="10" y="130" width="${sel_by_species.has(species[2]) ? bar_x(sel_by_species.get(species[2]).length) : 0}" height="50"/>

<text stroke="#EEE" stroke-width="2" stroke-linejoin="round" stroke-opacity="0.9" x="20" y="30">${species[0]}</text>
<text stroke="#EEE" stroke-width="2" stroke-linejoin="round" stroke-opacity="0.9" x="20" y="90">${species[1]}</text>
<text stroke="#EEE" stroke-width="2" stroke-linejoin="round" stroke-opacity="0.9" x="20" y="150">${species[2]}</text>
<text x="20" y="30">${species[0]}</text>
<text x="20" y="90">${species[1]}</text>
<text x="20" y="150">${species[2]}</text>
</svg>`
Insert cell
'setosa' in sel_by_species
Insert cell
bar_x = d3.scaleLinear()
.domain([0, 50])
.range([0, 480])
Insert cell
sel_by_species = d3.group(selection, d => d.species)
Insert cell
Insert cell
mutable selection = data
Insert cell
x = columns.map(c => d3.scaleLinear()
.domain(d3.extent(data, d => d[c]))
.rangeRound([padding / 2, size - padding / 2]))
Insert cell
y = x.map(x => x.copy().range([size - padding / 2, padding / 2]))
Insert cell
z = d3.scaleOrdinal()
.domain(data.map(d => d.species))
.range(d3.schemeDark2)
Insert cell
xAxis = {
const axis = d3.axisBottom()
.ticks(6)
.tickSize(size * columns.length);
return g => g.selectAll("g").data(x).join("g")
.attr("transform", (d, i) => `translate(${i * size},0)`)
.each(function(d) { return d3.select(this).call(axis.scale(d)); })
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").attr("stroke", "#ddd"));
}
Insert cell
yAxis = {
const axis = d3.axisLeft()
.ticks(6)
.tickSize(-size * columns.length);
return g => g.selectAll("g").data(y).join("g")
.attr("transform", (d, i) => `translate(0,${i * size})`)
.each(function(d) { return d3.select(this).call(axis.scale(d)); })
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").attr("stroke", "#ddd"));
}
Insert cell
data = d3.csv("https://gist.githubusercontent.com/mbostock/b038321e2a8177baf9e6a547195da966/raw/6c8eb7f5c644be0394f7fc384e42de9fab41927f/iris.csv", d3.autoType)
Insert cell
columns = data.columns.filter(d => d !== "species")
Insert cell
w = 800
Insert cell
size = (w - (columns.length + 1) * padding) / columns.length + padding
Insert cell
padding = 20
Insert cell
import {dashboard} from '@nitaku/flex-dashboard'
Insert cell
d3 = require("d3@5", 'd3-array')
Insert cell
import {Table} from "@observablehq/inputs"
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