From f20fae2c592264b7387b33139b8b37c048e2b5b9 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Wed, 13 Jan 2021 14:59:25 +1100 Subject: [PATCH] fix: improve rounding logic for range filters (#348) * fix: improve rounding logic for range filters * chore: changeset * docs: update docs for formatter --- .changeset/pink-flies-collect.md | 7 ++++++ docs/pages/classes/rangefilterbuilder.mdx | 22 ++++++++++--------- .../controllers/filters/RangeFilterBuilder.ts | 12 ++++++++-- .../controllers/filters/types.ts | 2 ++ packages/hooks/src/useRangeFilter/index.ts | 1 + packages/search-ui/src/Filter/RangeFilter.tsx | 5 +++-- packages/utils/src/number.ts | 2 +- 7 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 .changeset/pink-flies-collect.md diff --git a/.changeset/pink-flies-collect.md b/.changeset/pink-flies-collect.md new file mode 100644 index 000000000..131cde8a2 --- /dev/null +++ b/.changeset/pink-flies-collect.md @@ -0,0 +1,7 @@ +--- +'sajari-sdk-docs': patch +'@sajari/react-hooks': patch +'@sajari/react-search-ui': patch +--- + +Improve rounding logic for range filters diff --git a/docs/pages/classes/rangefilterbuilder.mdx b/docs/pages/classes/rangefilterbuilder.mdx index 58100b64d..52642974f 100644 --- a/docs/pages/classes/rangefilterbuilder.mdx +++ b/docs/pages/classes/rangefilterbuilder.mdx @@ -48,16 +48,17 @@ function Example() { The constructor accepts a single object with the following properties: -| Name | Type | Default | Description | -| ----------- | ----------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `string` | | The name of the filter. This should be unique. | -| `group` | `string` | | A group name, for grouping multiple filters together using [`ARRAY_MATCH`](https://docs.sajari.com/user-guide/integrating-search/filters/). | -| `field` | `string` | | A field in schema. | -| `initial` | `[number, number]` | `null` | An intially selected range. | -| `min` | `number` | `0` | The min value of the filter. | -| `max` | `number` | `0` | The max value of the filter. | -| `aggregate` | `boolean` | `true` | If true, set value for min and max from the backend response. | -| `formatter` | `(value: [number, number]) => [number, number]` | `(v) => v.map(Math.round)` | The function to format the range. For example, format `[0.1, 5.5]` to `[0, 6]`. | +| Name | Type | Default | Description | +| ----------- | ----------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | | The name of the filter. This should be unique. | +| `group` | `string` | | A group name, for grouping multiple filters together using [`ARRAY_MATCH`](https://docs.sajari.com/user-guide/integrating-search/filters/). | +| `field` | `string` | | A field in schema. | +| `initial` | `[number, number]` | `null` | An intially selected range. | +| `min` | `number` | `0` | The min value of the filter. | +| `max` | `number` | `aggregate ? 0 : 100` | The max value of the filter. | +| `step` | `number` | `1` | A number that specifies the granularity that the values must adhere to. | +| `aggregate` | `boolean` | `true` | If true, set value for min and max from the backend response. | +| `formatter` | `(value: [number, number]) => [number, number]` | | The function to format the range. By default, this will round to the nearest step for the value. For example if step is `1`, the formatter will format `[0.1, 5.5]` to `[0, 6]`. | ## Properties @@ -72,6 +73,7 @@ For more advanced implementations, you may want to access properties on the `Fil | `setMin(value: number)` | Set the minimum value. | | `setMax(value: number)` | Set the maximum value. | | `getMinMax()` | Returns the current min and max in an array. | +| `getStep()` | Returns the number that specifies the granularity that the values must adhere to. | | `filter()` | Builds up the filter string from the current filter and it's children. | | `getName()` | Returns the `name` property of the filter. | | `getGroup()` | Returns the `group` property of the filter. | diff --git a/packages/hooks/src/ContextProvider/controllers/filters/RangeFilterBuilder.ts b/packages/hooks/src/ContextProvider/controllers/filters/RangeFilterBuilder.ts index 87e5f43b8..6b48b0c33 100644 --- a/packages/hooks/src/ContextProvider/controllers/filters/RangeFilterBuilder.ts +++ b/packages/hooks/src/ContextProvider/controllers/filters/RangeFilterBuilder.ts @@ -1,4 +1,4 @@ -import { isArray, round } from '@sajari/react-sdk-utils'; +import { isArray, round, roundToStep } from '@sajari/react-sdk-utils'; import { EVENT_RANGE_UPDATED } from '../../events'; import { Listener } from '../Listener'; @@ -21,6 +21,8 @@ export default class RangeFilterBuilder { private max: number; + private step: number; + private aggregate: boolean; private listeners: { [k: string]: Listener }; @@ -35,7 +37,8 @@ export default class RangeFilterBuilder { initial, min = 0, max = aggregate ? 0 : 100, - formatter = (value: Range) => value.map((v) => round(v, 2)) as Range, + step = 1, + formatter = (value: Range) => value.map((v) => roundToStep(v, step)) as Range, }: RangeFilterOptions) { if (typeof initial === 'undefined') { this.initial = aggregate ? null : [min, max]; @@ -50,6 +53,7 @@ export default class RangeFilterBuilder { this.formatter = formatter; this.min = min; this.max = max; + this.step = step; this.aggregate = aggregate; this.listeners = { [EVENT_RANGE_UPDATED]: new Listener(), @@ -103,6 +107,10 @@ export default class RangeFilterBuilder { return [this.min, this.max]; } + public getStep() { + return this.step; + } + /** * Builds up the filter string from the current state. */ diff --git a/packages/hooks/src/ContextProvider/controllers/filters/types.ts b/packages/hooks/src/ContextProvider/controllers/filters/types.ts index 0c4244a2f..b154ca224 100644 --- a/packages/hooks/src/ContextProvider/controllers/filters/types.ts +++ b/packages/hooks/src/ContextProvider/controllers/filters/types.ts @@ -38,6 +38,8 @@ export interface RangeFilterOptions { min?: number; /** The max value of the filter */ max?: number; + /** The step to increment values */ + step?: number; /** If true, set value for min and max from the backend response */ aggregate?: boolean; /** The function to format the range. For example, format [0.1, 5.5] to [0, 6] */ diff --git a/packages/hooks/src/useRangeFilter/index.ts b/packages/hooks/src/useRangeFilter/index.ts index dec64f3c5..8e228ecbb 100644 --- a/packages/hooks/src/useRangeFilter/index.ts +++ b/packages/hooks/src/useRangeFilter/index.ts @@ -136,6 +136,7 @@ function useRangeFilter(name: string) { return { min, max, + step: filter.getStep(), setRange, range, reset, diff --git a/packages/search-ui/src/Filter/RangeFilter.tsx b/packages/search-ui/src/Filter/RangeFilter.tsx index ababfbdf4..76e43d018 100644 --- a/packages/search-ui/src/Filter/RangeFilter.tsx +++ b/packages/search-ui/src/Filter/RangeFilter.tsx @@ -7,8 +7,9 @@ import Box from './Box'; import { RangeFilterProps } from './types'; import { getHeaderId } from './utils'; -const RangeFilter = ({ name, title, format, showInputs, step, steps, tick, ticks }: Omit) => { - const { min, max, range, setRange, reset, showReset } = useRangeFilter(name); +const RangeFilter = (props: Omit) => { + const { name, title, format, showInputs, steps, tick, ticks } = props; + const { min, max, range, setRange, reset, showReset, step } = useRangeFilter(name); const { disableDefaultStyles = false, customClassNames, currency, language } = useSearchUIContext(); if (!range || max === 0) { diff --git a/packages/utils/src/number.ts b/packages/utils/src/number.ts index 68c9b4138..eb669d3f3 100644 --- a/packages/utils/src/number.ts +++ b/packages/utils/src/number.ts @@ -48,7 +48,7 @@ export function getDecimalPlaces(value: number): number { } /** - * Round to the nearest step + * Round to decimal places * @param input - the number to round * @param places - how many decimal places to round to */