function sp_chart_draw(
data,
sp_nominal,
sp_numerical,
sp_opacity,
brush_default_extent = [[0, 0], [0, 0]]
) {
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);
const x = d3
.scaleLinear()
.domain(
d3.extent(
data.filter(d => typeof d[sp_numerical] === "number"),
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();
}