Skip to content

Commit 42d91c0

Browse files
zubeydecivelekzzacharo
authored andcommitted
docs: add range facet
1 parent f3fbe18 commit 42d91c0

2 files changed

Lines changed: 383 additions & 0 deletions

File tree

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
---
2+
id: range-facet
3+
title: RangeFacet
4+
---
5+
6+
`RangeFacet` renders a histogram, slider, and optional default/custom ranges for a numeric year range.
7+
8+
It reads buckets from the `results` aggregations and stores the selected range in the query `filters`.
9+
10+
## Usage
11+
12+
```jsx
13+
<RangeFacet
14+
title="Publication Year"
15+
agg={{ aggName: "years" }}
16+
rangeSeparator=".."
17+
// optional
18+
defaultRanges={[
19+
{ label: "Last 1 year", type: "years", value: 1 },
20+
{ label: "Last 5 years", type: "years", value: 5 },
21+
{ label: "Last 6 months", type: "months", value: 6 },
22+
]}
23+
enableCustomRange
24+
// customize custom-range labels and placeholders
25+
customDatesLabel="Pick dates"
26+
dateRangeToLabel=""
27+
datePlaceholders={{ YYYY: "Year", MM: "Mo", DD: "Day" }}
28+
/>
29+
```
30+
31+
## Aggregation format
32+
33+
The component expects the aggregation result under `results.aggregations[aggName].buckets`
34+
with a numeric `key` (or `key_as_string`) and `doc_count`:
35+
36+
```json
37+
{
38+
"years": {
39+
"buckets": [
40+
{ "key": 2021, "doc_count": 12 },
41+
{ "key": 2022, "doc_count": 7 }
42+
]
43+
}
44+
}
45+
```
46+
47+
## Filters format
48+
49+
The selected filter is stored as `[ "<aggName>", "<from><rangeSeparator><to>" ]`.
50+
`<from>` and `<to>` can be `YYYY` or full ISO dates (`YYYY-MM-DD`) when custom ranges
51+
include months/days.
52+
53+
## Props
54+
55+
* **title** `String`
56+
57+
The title rendered in the card header.
58+
59+
* **agg** `Object`
60+
61+
An object that describes the aggregation to read from `results`:
62+
63+
* **aggName** `String`: the aggregation name to look for in `results`
64+
65+
* **rangeSeparator** `String`
66+
67+
The separator used when building the filter value (for example `..` or `--`).
68+
69+
* **defaultRanges** `Array` *optional*
70+
71+
Default ranges rendered as checkboxes. Each entry should have:
72+
73+
* **label** `String`
74+
* **type** `String`: `"years"` or `"months"`
75+
* **value** `Number`: number of years/months back
76+
77+
* **enableCustomRange** `Boolean` *optional*
78+
79+
When `true`, enables the custom date range checkbox with optional month/day inputs.
80+
81+
* **customDatesLabel** `String` *optional*
82+
83+
Label for the button that expands the custom date inputs (year/month/day). Default: `"Custom Dates"`.
84+
85+
* **dateRangeToLabel** `String` *optional*
86+
87+
Text shown between the "from" and "to" date inputs. Default: `"to"`.
88+
89+
* **datePlaceholders** `Object` *optional*
90+
91+
Placeholder text for the date input fields. Only keys you provide are used; others keep their defaults. Keys: `YYYY`, `MM`, `DD`. Example: `{ YYYY: "Year", MM: "Mo", DD: "Day" }`. Default placeholders when not set: `"YYYY"`, `"MM"`, `"DD"`.
92+
93+
* **histogramHeight** `Number` *optional*
94+
95+
Height in pixels for the histogram area.
96+
97+
* **overridableId** `String` *optional*
98+
99+
Optional string to define a specific overridable id.
100+
101+
## Usage when overriding
102+
103+
### Overriding full component
104+
105+
Use this when you want to replace the whole `RangeFacet` logic and UI.
106+
107+
Brief flow (matches the default behavior):
108+
109+
1. Read buckets from `currentResultsState.data.aggregations[agg.aggName].buckets`.
110+
2. Compute `min/max` years from bucket keys.
111+
3. Compute the histogram data with aggregation keys and doc counts.
112+
4. Store the selected range in `currentQueryState.filters` as:
113+
`[agg.aggName, `${from}${rangeSeparator}${to}`]`.
114+
5. Render your own UI (histogram/slider/etc).
115+
116+
Example with only histogram and custom filter facet:
117+
118+
```jsx
119+
class MyRangeFacet extends React.Component {
120+
constructor(props) {
121+
super(props);
122+
const { min, max } = this.getMinMax();
123+
this.state = { range: [min, max], activeFilter: null };
124+
}
125+
126+
applyRange = (from, to) => {
127+
// Apply the filter
128+
};
129+
130+
onBarClick = (year) => {
131+
this.applyRange(year, year);
132+
};
133+
134+
onClear = () => {
135+
// Clear the filter
136+
};
137+
138+
getBuckets = () => {
139+
const { currentResultsState, agg } = this.props;
140+
const resultsAggregations = currentResultsState?.data?.aggregations;
141+
return resultsAggregations?.[agg.aggName]?.buckets ?? [];
142+
};
143+
144+
render() {
145+
const { title, agg, rangeSeparator, currentQueryState, histogramHeight } =
146+
this.props;
147+
const { min, max } = this.getMinMax();
148+
const hasActiveFilter;
149+
// Extract the buckets in aggregations
150+
const buckets = getBuckets();
151+
// Active filter range: [from(year), to(year)]
152+
const { range } = this.state
153+
154+
const containerCmp = (
155+
<>
156+
<RangeHistogram
157+
data={
158+
// Extract key and doc_count from agg into a map, then fill every year with the count.
159+
getHistogramData(buckets, min, max)
160+
}
161+
range={range}
162+
height={histogramHeight}
163+
onBarClick={this.onBarClick}
164+
/>
165+
<RangeCustomFilter
166+
min={min}
167+
max={max}
168+
value={range}
169+
rangeSeparator={rangeSeparator}
170+
activeMode={RANGE_MODES.CUSTOM}
171+
activeFilter={this.state.activeFilter}
172+
onApply={(r, s) => this.applyRange(r[0], r[1])}
173+
onClear={this.onClear}
174+
/>
175+
</>
176+
);
177+
178+
return (
179+
<RangeFacetElement
180+
title={title}
181+
containerCmp={containerCmp}
182+
hasActiveFilter={hasActiveFilter}
183+
onClear={this.onClear}
184+
/>
185+
);
186+
}
187+
}
188+
189+
const overriddenComponents = {
190+
RangeFacet: MyRangeFacet,
191+
};
192+
```
193+
194+
### Overriding the element
195+
196+
Wraps the default content and lets you customize the header or layout while keeping
197+
the built-in histogram/slider/custom filters.
198+
199+
```jsx
200+
const MyRangeFacetElement = ({ title, containerCmp, hasActiveFilter, onClear }) => {
201+
return (
202+
<section>
203+
<header>
204+
<strong>{title}</strong>
205+
{hasActiveFilter && (
206+
<button type="button" onClick={onClear}>
207+
Clear
208+
</button>
209+
)}
210+
</header>
211+
{containerCmp}
212+
</section>
213+
);
214+
};
215+
216+
const overriddenComponents = {
217+
"RangeFacet.element": MyRangeFacetElement,
218+
};
219+
```
220+
221+
### Overriding the histogram
222+
223+
Replace the histogram visualization. Use `data` and `range` to show counts and
224+
highlight the selected years. Call `onBarClick(year)` to update the range.
225+
226+
```jsx
227+
const MyRangeHistogram = ({ data, range, onBarClick }) => {
228+
return (
229+
<ul>
230+
{data.map((item) => {
231+
...
232+
})}
233+
</ul>
234+
);
235+
};
236+
237+
const overriddenComponents = {
238+
"RangeFacet.Histogram.element": MyRangeHistogram,
239+
};
240+
```
241+
242+
#### Tooltip override (histogram)
243+
244+
The histogram tooltip is its own overridable element. The `tooltip` value has the
245+
shape `{ year, count, x, y }` where `x/y` are viewport coordinates used to position
246+
the tooltip.
247+
248+
```jsx
249+
const MyHistogramTooltip = ({ tooltip }) => {
250+
if (!tooltip) return null;
251+
return (
252+
<div>
253+
{tooltip.year}: {tooltip.count}
254+
</div>
255+
);
256+
};
257+
258+
const overriddenComponents = {
259+
"RangeFacet.Histogram.Tooltip.element": MyHistogramTooltip,
260+
};
261+
```
262+
263+
### Overriding the slider
264+
265+
Replace the range slider. Call `onChange([min, max])` when the selected range changes.
266+
267+
```jsx
268+
const MyRangeSlider = ({ min, max, value, onChange }) => {
269+
...
270+
};
271+
272+
const overriddenComponents = {
273+
"RangeFacet.Slider.element": MyRangeSlider,
274+
};
275+
```
276+
277+
### Overriding the default filters
278+
279+
Replace the default ranges list. Call `onToggle(range, checked)` to select or clear
280+
a range option.
281+
282+
```jsx
283+
const MyDefaultFilters = ({ ranges, activeLabel, onToggle }) => {
284+
...
285+
};
286+
287+
const overriddenComponents = {
288+
"RangeFacet.DefaultFilters.element": MyDefaultFilters,
289+
};
290+
```
291+
292+
### Overriding the custom filter
293+
294+
Replace the custom range checkbox and inputs wrapper. Use `checked/expanded` to
295+
control layout and render `dateError` when validation fails.
296+
297+
```jsx
298+
const MyCustomFilter = ({ checked, expanded, dateError, activeFilter, children }) => {
299+
...
300+
};
301+
302+
const overriddenComponents = {
303+
"RangeFacet.CustomFilter.element": MyCustomFilter,
304+
};
305+
```
306+
307+
### Overriding the date range inputs (for custom filter)
308+
309+
Replace the date input layout for the custom filter. Use `format` to decide which
310+
parts to render and `values` to populate them.
311+
312+
```jsx
313+
const MyDateRangeInputs = ({ format, values, disabled, children }) => {
314+
...
315+
};
316+
317+
const overriddenComponents = {
318+
"RangeFacet.DateInputs.Layout": MyDateRangeInputs,
319+
};
320+
```
321+
322+
`format` controls the input structure:
323+
324+
- `YYYY` renders a single row with year-only inputs.
325+
- `YYYY-MM` and `YYYY-MM-DD` render stacked rows for from/to values.
326+
327+
### RangeFacet parameters
328+
329+
Component that wraps the histogram, slider, and optional filters.
330+
331+
* **title** `String`
332+
333+
The title to render.
334+
335+
* **containerCmp** `React component`
336+
337+
The rendered content (histogram, slider, and filters).
338+
339+
* **hasActiveFilter** `Boolean`
340+
341+
`true` if a range filter is active, `false` otherwise.
342+
343+
* **onClear** `Function`
344+
345+
Function to call to clear the active range filter.
346+
347+
## Callback methods used by overrides
348+
349+
* **onClear** `Function`
350+
351+
Clears the active range filter and resets to the full available range.
352+
353+
* **onBarClick** `Function`
354+
355+
Accepts a `year` and updates the range to that single year.
356+
357+
* **onChange** `Function`
358+
359+
Accepts `[from, to]` to update the selected range (used by slider overrides).
360+
361+
* **onToggle** `Function`
362+
363+
Accepts `(range, checked)` to select a default range or clear it.
364+
365+
## Utilities
366+
367+
Helpers from `src/lib/components/RangeFacet/utils.js` used by `RangeFacet` and its sub-components.
368+
369+
* **extractBuckets(resultsAggregations, aggName)**: read aggregation buckets safely.
370+
* **getKey(bucket)**: returns `key_as_string` when present, otherwise `key`.
371+
* **getHistogramData(buckets, min, max)**: build continuous year data with counts.
372+
* **resolveDefaultRange(range, min, max, rangeSeparator)**: compute the year range
373+
and optional ISO date range for a default option.
374+
* **normalizeFilterValue(filterValue, rangeSeparator, minYear, maxYear)**: sanitize
375+
and normalize a filter string like `YYYY..YYYY` or ISO dates.
376+
* **parseFilterYears(filterValue, rangeSeparator)**: parse years from a filter
377+
string (supports ISO dates).
378+
* **findDefaultLabel(defaultRanges, filterValue, min, max, rangeSeparator)**:
379+
match an active filter to a default range label.
380+
* **buildDateRange({ fromYear, fromMonth, fromDay, toYear, toMonth, toDay, rangeSeparator })**:
381+
build a `YYYY..YYYY` or ISO `YYYY-MM-DD..YYYY-MM-DD` string.
382+
* **RANGE_MODES**: enum with `DEFAULT` and `CUSTOM` used for optional default and custom filters.

docs/website/sidebars.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"components/react-searchkit",
1313
"components/active-filters",
1414
"components/bucket-aggregation",
15+
"components/range-facet",
1516
"components/count",
1617
"components/empty-results",
1718
"components/error",

0 commit comments

Comments
 (0)