facetRange = ({
facet,
label,
buildFilter = (extent) => (datum) => {
const val = _.property(facet)(datum);
return (
val == null || isNaN(val) || (val >= extent.min && val <= extent.max)
);
}
}) => (data) => {
const [w, h] = [300, 70];
const padding = 25;
const form = htl.html`<form class="facet-form">
<legend>${label}</legend>
<svg width=${w} height=${h}></svg>
</form>`;
const [min, max] = d3.extent(data, _.property(facet));
let extent = { min, max };
const bin = d3
.bin()
.value(_.property(facet))
.domain([min, max])
.thresholds((values, mi, ma) => {
let vals = [
extent.min,
extent.max,
...d3.ticks(mi, ma, d3.thresholdSturges(values) - 2)
];
vals.sort();
return vals;
});
const histogram = bin(data);
const x = d3
.scaleLinear()
.domain([min, max])
.range([padding, w]);
const y = d3
.scaleLinear()
.domain([0, d3.max(histogram, _.property("length"))])
.range([h - padding, 0]);
const svg = d3.select(form).select("svg");
const g = svg.append("g");
const dataContainer = g.append("g");
const render = (data) => {
const histogram = bin(data);
// draw histogram values
dataContainer
.selectAll("rect")
.data(histogram)
.join("rect")
// .transition()
// .duration(80)
.attr("x", (d) => x(d.x0))
.attr("y", (d) => y(d.length || 0))
.attr("width", (d) => x(d.x1) - x(d.x0))
.attr("height", (d) => h - padding - y(d.length || 0))
.style("fill", (d) => {
if (d.x0 >= extent.min && d.x1 <= extent.max) {
return "steelblue";
}
return "#ccc";
});
};
render(data);
var brush = d3
.brushX()
.extent([
[padding, 0],
[w, h - padding]
])
.on("brush", function (e) {
if (e.selection) {
let inverted = e.selection.map((v) => x.invert(v));
extent.min = Math.min(inverted[0], inverted[1]);
extent.max = Math.max(inverted[0], inverted[1]);
} else {
extent.min = min;
extent.max = max;
}
form.dispatchEvent(new Event("input", { bubbles: true }));
})
.on("end", function (e) {
if (e.selection == null) {
extent.min = min;
extent.max = max;
form.dispatchEvent(new Event("input", { bubbles: true }));
}
});
g.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${h - padding})`)
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${padding},0)`)
.call(d3.axisLeft(y).ticks(2));
var gBrush = g.append("g").attr("class", "brush").call(brush);
form.resetFilters = function () {
extent.min = min;
extent.max = max;
brush.clear(g.select(".brush"));
};
return Object.defineProperties(form, {
value: {
get() {
return buildFilter(extent);
}
},
crossfilteredData: {
set(cf) {
render(cf);
}
}
});
}