Public
Edited
May 26, 2023
Importers
1 star
Insert cell
Insert cell
viewof selected = FacetedSearch(data, {
// attribs: ["date_of_birth","nationality"], // leave blank for using all attributes
filterData: filterDataJS,
value: this?.value
})
Insert cell
selected.filters
Insert cell
selected
Insert cell
viewof data = dataInput({value: olympians, format: "csv"})
Insert cell
data[0].date_of_birth
Insert cell
d = new Date()
Insert cell
d.toLocaleString()
Insert cell
attr="year"
Insert cell
extent = d3.extent(data, (d) => +new Date(d[attr]));
Insert cell
interval(extent, {
label: attr,
format: ([s, e]) => {
console.log(s, e, typeof s);
return `${new Date(s).toLocaleString()} ... ${new Date(e).toLocaleString()}`;
}
})
Insert cell
function FacetedSearch(
data,
{ attribs, value, filterData = filterDataJS } = {}
) {
if (!data || data.length < 0) {
throw Error("Please provide an array of objects");
}

let changed = false;
const filtersContainer = html`<div id="filters"></div>`;
const target = html`
${filtersContainer}
`;
const filters = new Map();
// the selected elements
target.value = data;
target.value.filters = filters;

function recreate(value) {
debugger;
for (let [id, filter] of filters) {
if (value.filters.has(id)) {
filter.selected = value.filters.get(id).selected;
filter.ele.dispatchEvent(new Event("input", {bubbles: true}))
}
}
updateAndRedraw();
}

function redraw() {
filtersContainer.innerHTML = "";
filtersContainer.appendChild(
html`<div >Selected ${target?.value.length} of ${data.length} ${
changed ? "&#10060" : "&check;"
}
${Array.from(filters.values()).map(({ attr, ele }) => {
return html`
<div>${ele}</div>
`;
})}
<button id="applyButton" ${changed ? "" : "disabled"}>Apply</button>
</div>`
);

filtersContainer
.querySelector("#applyButton")
.addEventListener("click", () => {
changed = false;
updateAndRedraw();
filtersContainer.querySelector("#applyButton").disabled = !changed;
});
}

async function onChange() {
changed = true;
redraw();
}

async function updateAndRedraw() {
let before;
if (debug) {
before = performance.now();
}

// Filter the data
target.value = await filterData(data, filters);

debug && console.log(`Finished filtering ${performance.now() - before}`);
target.value.filters = filters;
// Trigger an update
target.dispatchEvent(new Event("input", { bubbles: true }));

redraw();
}

attribs = attribs || Object.keys(data[0]);

for (let attr of attribs) {
// Assumes NaN => categorical
if (isNaN(data[0][attr])) {
// if (possibleValues.length < 20) {
addSearchCheckboxes({
attr,
filters,
data,
updateAndRedraw,
target
});
// We need a better filter for many values
// }
// else {
// addMultiAutoSelect({
// attr,
// filters,
// data,
// updateAndRedraw,
// target,
// possibleValues
// });
// }
} else {
// *** Quantitative or date
addRange({ attr, filters, data, updateAndRedraw: onChange, target });
}
}

if (value) {
//recreate(value);
}

redraw();

return target;
}
Insert cell
function addSearchCheckboxes({ attr, filters, data, updateAndRedraw, target }) {
const groupCounts = d3.group(data, (d) => d[attr]),
possibleValues = Array.from(groupCounts.keys());
const ele = conditionalShow(
searchCheckbox(possibleValues, {
value: possibleValues,
label: attr,
height: 200,
format: attr => `${attr} (${groupCounts.get(attr).length})`
}),
{ label: attr }
);

ele.addEventListener("change", (evt) => {
evt.stopPropagation();
// Update the filter
const filter = filters.get(attr);
filter.selected = ele.value;
filters.set(attr, filter);

updateAndRedraw();
});

filters.set(attr, {
attr,
ele,
type: "categorical",
selected: possibleValues,
allOptions: possibleValues
});
}
Insert cell
function addMultiAutoSelect({
attr,
filters,
data,
updateAndRedraw,
target
}) {
const possibleValues = Array.from(d3.group(data, (d) => d[attr]).keys());
const ele = conditionalShow(
multiAutoSelect( {options: possibleValues, label: attr }),
{ label: attr, value: possibleValues }
);
ele.addEventListener("change", (evt) => {
evt.stopPropagation();
// Update the filter
const filter = filters.get(attr);
filter.selected = ele.value;
filters.set(attr, filter);

updateAndRedraw();
});

filters.set(attr, {
attr,
ele,
type: "categorical",
selected: possibleValues,
allOptions: possibleValues
});
}
Insert cell
Number("1.2") === 1.2
Insert cell
d3.format(",d")(31234234)
Insert cell
function addRange({
attr,
filters,
data,
updateAndRedraw,
target,
format,
step
} = {}) {
if (data[0][attr] instanceof Date) {
const fmt = (d) => new Date(d).toLocaleString();
format = format || (([s, e]) => html`${fmt(s)} ...<br> ${fmt(e)}`);
step = 1;
} else if (+data[0][attr] % 1 === 0) {
// Integer
const fmt = d3.format(",d");
format = format || (([s, e]) => `${fmt(s)} ... ${fmt(e)}`);
step = 1;
} else {
// float
const fmt = d3.format(",.2f");
format = format || (([s, e]) => `${fmt(s)} ... ${fmt(e)}`);
step = 0.01;
}
const extent = d3.extent(data, (d) => +d[attr]);

const ele = conditionalShow(
interval(extent, {
label: attr,
format,
step
}),
{
label: attr
}
);
ele.addEventListener("input", (evt) => {
// Cath input
evt.stopPropagation();
});
ele.addEventListener("change", (evt) => {
evt.stopPropagation();

// Update the filter
const filter = filters.get(attr);
filter.selected = ele.value;
filters.set(attr, filter);
updateAndRedraw();
});

filters.set(attr, {
attr,
ele,
type: "range",
selected: extent,
allOptions: extent
});
}
Insert cell
// A simple JS function to apply the filters on the data
async function filterDataJS(data, filters) {
let res = data;

for (let filter of filters.values()) {
if (filter.type === "categorical") {
res = res.filter((d) => filter.selected.includes(d[filter.attr]));
} else if (filter.type === "range") {
res = res.filter(
(d) =>
d[filter.attr] >= filter.selected[0] &&
d[filter.attr] <= filter.selected[1]
);
}
}

return res;
}
Insert cell
test
Insert cell
{
(viewof test).value = [0.6,0.7];
new Event("input", {bubbles: true})
}
Insert cell
viewof test = conditionalShow(interval());
Insert cell
viewof test2 = Inputs.range([0, 100], {step: 1})
Insert cell
debug = true
Insert cell
import {searchCheckbox} from "@john-guerra/search-checkbox"
Insert cell
import {multiAutoSelect} from "@john-guerra/multi-auto-select"
Insert cell
import {conditionalShow} from "@john-guerra/conditional-show"
Insert cell
import {interval} from '@mootari/range-slider'
Insert cell
import { dataInput} from "@john-guerra/data-input"
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