Public
Edited
Jun 10, 2023
10 forks
Importers
178 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import {rangeSlider} from "@bumbeishvili/utils"
Insert cell
data = require('@observablehq/alphabet')
Insert cell
viewof v = rangeSlider(data, d=>d.frequency)
Insert cell
Insert cell
v
Insert cell
Insert cell
Insert cell
Insert cell
cars = require('@observablehq/cars')
Insert cell
Insert cell
viewof c = rangeSlider(cars,d=>d.Year)
Insert cell
c
Insert cell
Insert cell
viewof d = rangeSlider(cars,d=>d.Horsepower)
Insert cell
d
Insert cell
Insert cell
Insert cell
viewof e = rangeSlider(cars, d=>d.Year , group=> d3.mean(group.values, d=>d.Horsepower) )
Insert cell
e
Insert cell
Insert cell
Insert cell
viewof f = rangeSlider(cars, d => d.Year, group => d3.max(group.values, d=>d.Horsepower) )
Insert cell
f
Insert cell
Insert cell
viewof g = rangeSlider(cars, d=>d.Year , group=> d3.min(group.values, d=>d.Horsepower) )
Insert cell
g
Insert cell
Insert cell
Insert cell
Insert cell
viewof h = rangeSlider(dateData,
d=>d.date,
value=>d3.sum(value.values.map(d=>d.amount)) // We want sum of amount for range
)
Insert cell
h
Insert cell
Insert cell
Insert cell
viewof i = rangeSlider(dateData,
d=>d.date,
value=>d3.sum(value.values.map(d=>d.amount)),
{
height:300,
yTicks:15,
yScale:d3.scalePow().exponent(0.7) // Domain and Range is automatically assigned
} // passing parameters object
)
Insert cell
i
Insert cell
Insert cell
Insert cell

viewof a1 = rangeSlider(cars, d=>d.Horsepower ,{freezeMin:true} )
Insert cell
a1
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function barRangeSlider(
initialDataArray,
accessorFunction,
aggregatorFunction,
paramsObject
) {
const initialData = initialDataArray;
const accessor = accessorFunction;
const aggregator = aggregatorFunction;
const argumentsArr = [...arguments];
let params = argumentsArr.filter(isPlainObj)[0];
if (!params) {
params = {};
}

let chartHeight = 100;
let startSelection = 100;

params.minY = params.yScale ? 0.0001 : 0;
params.yScale = params.yScale || d3.scaleLinear();
chartHeight = params.height || chartHeight;
params.yTicks = params.yTicks || 4;
params.freezeMin = params.freezeMin || false;
params.color = params.color || (() => "steelblue");

const chartWidth = width - 40 - (params.marginRight || 0);

var accessorFunc = (d) => d;
if (initialData[0].value != null) {
accessorFunc = (d) => d.value;
}
if (typeof accessor == "function") {
accessorFunc = accessor;
}
const dataFinal = initialData; //
const isDate =
Object.prototype.toString.call(accessor(dataFinal[0])) === "[object Date]";
var dateExtent, dateScale, scaleTime, dateRangesCount, dateRanges, scaleTime;
if (isDate) {
dateExtent = d3.extent(dataFinal.map(accessorFunc));
dateRangesCount = Math.round(width / 5);
dateScale = d3.scaleTime().domain(dateExtent).range([0, dateRangesCount]);
scaleTime = d3.scaleTime().domain(dateExtent).range([0, chartWidth]);
dateRanges = d3
.range(dateRangesCount)
.map((d) => [dateScale.invert(d), dateScale.invert(d + 1)]);
}

d3.selection.prototype.patternify = function (params) {
var container = this;
var selector = params.selector;
var elementTag = params.tag;
var data = params.data || [selector];

// Pattern in action
var selection = container.selectAll("." + selector).data(data, (d, i) => {
if (typeof d === "object") {
if (d.id) {
return d.id;
}
}
return i;
});
selection.exit().remove();
selection = selection.enter().append(elementTag).merge(selection);
selection.attr("class", selector);
return selection;
};

const handlerWidth = 2,
handlerFill = "#E1E1E3",
middleHandlerWidth = 10,
middleHandlerStroke = "#8E8E8E",
middleHandlerFill = "#EFF4F7";

const svg = d3
.select(DOM.svg(chartWidth, chartHeight))
.style("overflow", "visible");

const chart = svg
.append("g")
.attr("transform", `translate(${params.marginLeft ?? 30},5)`);

const groupedInitial = group(dataFinal)
.by((d, i) => {
const field = accessorFunc(d);
if (isDate) {
return Math.round(dateScale(field));
}
return field;
})
.orderBy((d) => d.key)
.run();

const grouped = groupedInitial.map((d) =>
Object.assign(d, {
value: typeof aggregator == "function" ? aggregator(d) : d.values.length
})
);

const values = grouped.map((d) => d.value);
const min = d3.min(values);
const max = d3.max(values);
const maxX = grouped[grouped.length - 1].key;
const minX = grouped[0].key;

var minDiff = d3.min(grouped, (d, i, arr) => {
if (!i) return Infinity;
return d.key - arr[i - 1].key;
});

let eachBarWidth = chartWidth / minDiff / (maxX - minX);

if (eachBarWidth > 20) {
eachBarWidth = 20;
}

if (minDiff < 1) {
eachBarWidth = eachBarWidth * minDiff;
}

if (eachBarWidth < 1) {
eachBarWidth = 1;
}

const scale = params.yScale
.domain([params.minY, max])
.range([0, chartHeight - 25]);
const scaleY = scale
.copy()
.domain([max, params.minY])
.range([0, chartHeight - 25]);

const scaleX = d3.scaleLinear().domain([minX, maxX]).range([0, chartWidth]);
var axis = d3.axisBottom(scaleX);
if (isDate) {
axis = d3.axisBottom(scaleTime);
}
const axisY = d3
.axisLeft(scaleY)
.tickSize(-chartWidth - 20)
.ticks(max == 1 ? 1 : params.yTicks)
.tickFormat(d3.format(".2s"));

const bars = chart
.selectAll(".bar")
.data(grouped)
.enter()
.append("rect")
.attr("class", "bar")
.attr("width", eachBarWidth)
.attr("height", (d) => scale(d.value))
.attr("fill", params.color)
.attr("y", (d) => -scale(d.value) + (chartHeight - 25))
.attr("x", (d, i) => scaleX(d.key) - eachBarWidth / 2)
.attr("opacity", 0.9);

const xAxisWrapper = chart
.append("g")
.attr("transform", `translate(${0},${chartHeight - 25})`)
.call(axis);

const yAxisWrapper = chart
.append("g")
.attr("transform", `translate(${-10},${0})`)
.call(axisY);

const brush = chart
.append("g")
.attr("class", "brush")
.call(
d3
.brushX()
.extent([
[0, 0],
[chartWidth, chartHeight]
])
.on("start", brushStarted)
.on("end", brushEnded)
.on("brush", brushed)
);

chart.selectAll(".selection").attr("fill-opacity", 0.1);

var handle = brush
.patternify({
tag: "g",
selector: "custom-handle",
data: [
{
left: true
},
{
left: false
}
]
})
.attr("cursor", "ew-resize")
.attr("pointer-events", "all");

handle
.patternify({
tag: "rect",
selector: "custom-handle-rect",
data: (d) => [d]
})
.attr("width", handlerWidth)
.attr("height", 100)
.attr("fill", handlerFill)
.attr("stroke", handlerFill)
.attr("y", -50)
.attr("pointer-events", "none");

handle
.patternify({
tag: "rect",
selector: "custom-handle-rect-middle",
data: (d) => [d]
})
.attr("width", middleHandlerWidth)
.attr("height", 30)
.attr("fill", middleHandlerFill)
.attr("stroke", middleHandlerStroke)
.attr("y", -16)
.attr("x", -middleHandlerWidth / 4)
.attr("pointer-events", "none")
.attr("rx", 3);

handle
.patternify({
tag: "rect",
selector: "custom-handle-rect-line-left",
data: (d) => [d]
})
.attr("width", 0.7)
.attr("height", 20)
.attr("fill", middleHandlerStroke)
.attr("stroke", middleHandlerStroke)
.attr("y", -100 / 6 + 5)
.attr("x", -middleHandlerWidth / 4 + 3)
.attr("pointer-events", "none");

handle
.patternify({
tag: "rect",
selector: "custom-handle-rect-line-right",
data: (d) => [d]
})
.attr("width", 0.7)
.attr("height", 20)
.attr("fill", middleHandlerStroke)
.attr("stroke", middleHandlerStroke)
.attr("y", -100 / 6 + 5)
.attr("x", -middleHandlerWidth / 4 + middleHandlerWidth - 3)
.attr("pointer-events", "none");

handle.attr("display", "none");

function brushStarted() {
if (d3.event.selection) {
startSelection = d3.event.selection[0];
}
}

function brushEnded() {
console.log("ended");
if (!d3.event.selection) {
handle.attr("display", "none");

output({
range: [minX, maxX]
});
return;
}
if (d3.event.sourceEvent.type === "brush") return;

var d0 = d3.event.selection.map(scaleX.invert),
d1 = d0.map(d3.timeDay.round);

if (d1[0] >= d1[1]) {
d1[0] = d3.timeDay.floor(d0[0]);
d1[1] = d3.timeDay.offset(d1[0]);
}
console.log(d0, d1);
}

function brushed(d) {
if (d3.event.sourceEvent.type === "brush") return;
console.log("brushed", d3.event.selection);

if (params.freezeMin) {
if (d3.event.selection[0] < startSelection) {
d3.event.selection[1] = Math.min(
d3.event.selection[0],
d3.event.selection[1]
);
}
if (d3.event.selection[0] >= startSelection) {
d3.event.selection[1] = Math.max(
d3.event.selection[0],
d3.event.selection[1]
);
}

d3.event.selection[0] = 0;
// console.log(d3.event.selection)

d3.select(this).call(d3.event.target.move, d3.event.selection);
}

var d0 = d3.event.selection.map(scaleX.invert);
const s = d3.event.selection;
console.log(s);

handle.attr("display", null).attr("transform", function (d, i) {
console.log(d);
return "translate(" + (s[i] - 2) + "," + chartHeight / 2 + ")";
});
output({
range: d0
});
}

yAxisWrapper.selectAll(".domain").remove();
xAxisWrapper.selectAll(".domain").attr("opacity", 0.1);

chart.selectAll(".tick line").attr("opacity", 0.1);

function isPlainObj(o) {
return typeof o == "object" && o.constructor == Object;
}

function output(value) {
const node = svg.node();
node.value = value;
node.value.data = getData(node.value.range);
if (isDate) {
node.value.range = value.range.map((d) => dateScale.invert(d));
}
node.dispatchEvent(new CustomEvent("input"));
}

function getData(range) {
const dataBars = bars
.attr("fill", params.color)
.filter((d) => {
return d.key >= range[0] && d.key <= range[1];
})
.attr("fill", "red")
.nodes()
.map((d) => d.__data__)
.map((d) => d.values)
.reduce((a, b) => a.concat(b), []);

return dataBars;
}

const returnValue = Object.assign(svg.node(), {
value: {
range: [minX, maxX],
data: initialData
}
});

if (isDate) {
returnValue.value.range = returnValue.value.range.map((d) =>
dateScale.invert(d)
);
}

return returnValue;
}
Insert cell
Insert cell
Insert cell
d3 = require('d3@v5')
Insert cell
import {group} from "@bumbeishvili/utils"
Insert cell
import {comments} from "@bumbeishvili/utils"
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more