Published
Edited
Jul 18, 2022
Importers
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof sp_nominal = sp_nominal_select()
Insert cell
viewof sp_numerical = sp_numerical_select()
Insert cell
viewof sp_opacity = sp_opacity_range()
Insert cell
viewof sp_selected = sp_chart_draw(data, sp_nominal, sp_numerical, sp_opacity)
Insert cell
sp_selected.map(d => d.lib_comp_voe_ins + " - " + d.acc_tot)
Insert cell
Insert cell
function sp_chart_draw(
data,
sp_nominal,
sp_numerical,
sp_opacity,
brush_default_extent = [[0, 0], [0, 0]]
) {
// Layout ______________________________
const margin = { top: 20, right: 10, bottom: 10, left: 150 };

const line_height = 20;

const height =
margin.top +
margin.bottom +
line_height *
(1 +
columns_stp.filter(e => e.name === sp_nominal)[0].uniques_not_null
.length);

// Axis ______________________________
const x = d3
.scaleLinear()
.domain(
d3.extent(
data.filter(d => typeof d[sp_numerical] === "number"), // Cull any non-numerical values e.g. null
d => d[sp_numerical]
)
)
.range([margin.left + 10, width - margin.right]);

const y = d3
.scaleBand()
.domain(data.map(d => d[sp_nominal]))
.range([margin.top, height - margin.bottom])
.padding(1);

const xAxis = g =>
g
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(x).ticks())
.call(g =>
g
.selectAll(".tick line")
.clone()
.attr("stroke-opacity", 0.1)
.attr("y2", height - margin.bottom - margin.top)
)
.call(g => g.selectAll(".domain").remove());

const yAxis = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g =>
g
.selectAll(".tick line")
.clone()
.attr("stroke-opacity", 0.1)
.attr("x2", width - margin.right - margin.left)
)
.call(g => g.selectAll(".domain").remove());

// Draw SVG ______________________________
const svg = d3
.select(DOM.svg(width, height))
.style("background-color", "white")
.property("value", []);

const brush = d3
.brush()
.on("start brush end", brushed)
.on("end", brushended);

svg
.append("g")
.attr("class", "x axis")
.call(xAxis);

const yAx = svg
.append("g")
.attr("class", "y axis")
.call(yAxis);

// Attempt to autosize left margin to fit with label ... hard
// function autoBox() {
// document.body.appendChild(this);
// const { x, y, width, height } = this.getBBox();
// document.body.removeChild(this);
// return [x, y, width, height];
// }
//
// document.body.appendChild(
// svg
// .append("g")
// .attr("class", "y axis")
// .call(yAxis)
// .node()
// );
// const bbox = document.querySelector(".y.axis");
// console.log(bbox);
//yAx.remove();

// console.log(svg_yAx.node().getBBox());

// svg
// .selectAll(".tick")
// .selectAll('text')
// //.node()
// .each(function() {
// console.log(this);
// });

// var maxw = 0;
// d3.select(this)
// .select('.axis-y')
// .selectAll('text')
// .each(function() {
// if (this.getBBox().width > maxw) maxw = this.getBBox().width;
// });
// //console.log(maxw);

const square = svg
.append("g")
.style("fill", "black")
.style("fill-opacity", sp_opacity)
.selectAll("rect")
.data(data.filter(d => typeof d[sp_numerical] === "number"))
.join("rect")
.attr("width", 10)
.attr("height", 10)
.attr("x", d => (Math.random() - 0.5) * 6 + x(d[sp_numerical]) - 5)
.attr("y", d => (Math.random() - 0.5) * 6 + y(d[sp_nominal]) - 5);

svg
.append("g")
.call(brush)
.call(brush.move, brush_default_extent);

// Brush logic ______________________________
function brushed({ selection }) {
let value = [];
if (selection) {
const [[x0, y0], [x1, y1]] = selection;
value = square
.style("fill", "black")
.filter(
d =>
x0 <= x(d[sp_numerical]) &&
x(d[sp_numerical]) < x1 &&
y0 <= y(d[sp_nominal]) &&
y(d[sp_nominal]) < y1
)
.style("fill", "blue")
.data();
} else {
square.style("stroke", "steelblue");
}
svg.property("value", value).dispatch("input");
}

function brushended(event) {
const selection = event.selection;
if (!event.sourceEvent || !selection) return;
d3.select(this)
.transition()
.call(brush.move, selection.map(d => brush_snap(d)));
}

// Reactively adjust the brush to align with data ______________________________
function brush_snap([x, y]) {
if (y < margin.top + line_height) {
y = margin.top + 0.5 * line_height;
} else if (height - y - 0.5 * line_height < margin.bottom) {
y = height - margin.bottom - 0.5 * line_height;
} else {
y =
(Math.floor((y - margin.top - 0.5 * line_height) / line_height) +
Math.round(
((y - margin.top - 0.5 * line_height) % line_height) / line_height
)) *
line_height +
margin.top +
0.5 * line_height;
}
if (x < margin.left) {
x = margin.left;
}
return [x, y];
}

return svg.node();
}
Insert cell
Insert cell
function sp_select(are_all_numerical, value) {
return Select(
new Map(
columns_stp
.filter(e => are_all_numerical === e.are_all_numerical)
.sort((a, b) => a.id - b.id)
.map(e => [
`${properties.filter(g => g.key === e.name)[0].title} — ${
e.uniques_not_null.length
} ${are_all_numerical ? "unique value" : "item"}${
e.uniques_not_null.length > 1 ? "s" : ""
} — key: ${e.name}`,
e.name
])
),
{
value: value,
label: are_all_numerical
? `Numerical attribute to plot (x axis)`
: `Nominal attribute to group by (y axis)`
}
);
}
Insert cell
sp_nominal_select = (value = "fili") => sp_select(false, value)
Insert cell
sp_numerical_select = (value = "pct_bg_mention") => sp_select(true, value)
Insert cell
function sp_opacity_range(value = .015) {
return Range([0, .1], {
step: 0.005,
transform: Math.exp,
value: value,
label: "Mark opacity"
});
}
Insert cell
Insert cell
import {
data,
properties
} from "@aymericbr/in-depth-getting-data-and-its-structure"
Insert cell
properties_with_id = properties.map((d, i) => {
d.id = i;
return d;
})
Insert cell
columns_stp = Object.keys(data[0])
.map(d => ({
name: d,
uniques_not_null: Array.from(new Set(data.map(e => e[d]))).filter(
x => x != null
),
are_all_numerical:
Array.from(new Set(data.map(e => e[d])))
.filter(x => x != null)
.filter(f => !Number.isNaN(Number(f))).length /
Array.from(new Set(data.map(e => e[d]))).filter(x => x != null)
.length ===
1
}))
.map(d => {
d.id = properties_with_id.filter(e => d.name === e.key)[0].id;
return d;
})
.sort((a, b) => a.id - b.id)
Insert cell
Insert cell
import { toc } from "@aymericbr/indented-table-of-content"
Insert cell
import { headings_numbering } from "@aymericbr/multilevel-numbering-headings"
Insert cell
headings_numbering
Insert cell
import { header } from "@aymericbr/header-for-sub-notebook-to-redirect-to-its-parent"
Insert cell
d3 = require("d3@6")
Insert cell
import { Range, Select } from "@observablehq/inputs"
Insert cell
import {legend} from "@d3/color-legend"
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