Public
Edited
Apr 28
2 forks
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof rangeValue = {
const width = 500;
const marginLR = 24;

const chartWidth = width - marginLR * 2;
const chartHeight = 60;

const labelRowHeight = 20;
const rectHeight = chartHeight - labelRowHeight;
const handleWidth = 8;

const domain = [domainFrom, domainTo];
const range = [0, chartWidth];
const scale = d3.scaleLinear(domain, range);

const domainDiff = domainTo - domainFrom;

const rangeColorKeys = Object.keys(rangeColors);
const ranges = rangeColorKeys.map((key, i) => {
return {
color: rangeColors[key],
offset: domain[0] + i * Math.round(domainDiff / rangeColorKeys.length),
size: Math.round(domainDiff / rangeColorKeys.length)
};
});

// Generates handles from the initial range data.
// Subsequently we will update the range data when the handles are dragged.
const handleData = ranges.slice(1).map((d, i) => {
return {
x: scale(d.offset)
};
});

const svg = html`<svg width=${width} height=${chartHeight}>
<marker
id="arrow"
viewBox="0 0 10 10"
refX="0"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="grey" />
</marker>
</svg>`;

const vis = d3
.select(svg)
.append("g")
.attr("transform", `translate(${marginLR})`);

const dragBehaviour = d3
.drag()
.container(vis)
.on("drag", (e) => {
const handle = e.subject;
const handleIndex = handleData.indexOf(handle);
const minExtent =
handleIndex === 0 ? range[0] : handleData[handleIndex - 1].x + 1;
const maxExtent =
handleIndex === handleData.length - 1
? range[1]
: handleData[handleIndex + 1].x - 1;
handle.x = Math.min(maxExtent, Math.max(minExtent, e.x));
update();
});

const rects = vis.selectAll("rect").data(ranges).enter().append("rect");

const handles = vis
.selectAll("g")
.data(handleData)
.enter()
.append("g")
.classed("handle", true)
.call(dragBehaviour);

const handleRects = handles
.style("cursor", "ew-resize")
.append("rect")
.attr("x", -handleWidth / 2)
.attr("y", 0)
.attr("width", handleWidth)
.attr("height", rectHeight + labelRowHeight)
.attr("fill", "rgba(10,10,10,0.0)");

const handleLines = handles
.append("line")
.classed("handle-line", true)
.attr("x1", 0)
.attr("x2", "0")
.attr("y2", 0)
.attr("y1", rectHeight + labelRowHeight / 4)
.attr("stroke", "grey")
.attr("marker-start", "url(#arrow)");

const labelRow = vis
.append("g")
.classed("labels", true)
.attr("transform", `translate(0, ${rectHeight})`);

const labels = labelRow
.selectAll("text")
.data([{ x: 0 }].concat(handleData).concat([{ x: range[1] }]))
.enter()
.append("text")

.attr("text-anchor", "middle")
.attr("y", labelRowHeight)
.attr("font-size", 14);

update();

function update() {
labels.attr("x", (d) => d.x).text((d) => Math.round(scale.invert(d.x)));

ranges.forEach((d, i) => {
d.offset =
i === 0 ? domain[0] : Math.round(scale.invert(handleData[i - 1].x));
d.size =
i === ranges.length - 1
? domain[1] - d.offset
: Math.round(scale.invert(handleData[i].x)) - d.offset;
});

rects
.attr("transform", (d) => `translate(${scale(d.offset)})`)
.attr("x", 0)
.attr("y", 0)
.attr("width", (d) => scale(domain[0] + d.size))
.attr("height", rectHeight)
.attr("fill", (d) => d.color);

handles.attr("transform", (d) => `translate(${d.x})`);

svg.value = ranges;
svg.dispatchEvent(new CustomEvent("input"));
}

return svg;
}
Insert cell
Insert cell
Insert cell
Insert cell
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