-
Notifications
You must be signed in to change notification settings - Fork 17.8k
feat: log scale and step size control for range filters [WIP] #18009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
4ab10f3
0bd7607
308c410
987e05c
4432e9f
70f6417
9a117da
c1f9248
fe0334f
b284f51
9d4f5a0
e81d710
7c4aa69
a2cd0f0
209403d
9ddd258
7af88c4
361ecc8
f05ebc9
2b64973
a6ab310
495a2ef
089df91
fef9b99
b57ba75
4997af6
6369dcc
f9f556b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ import { | |
| import React, { useCallback, useEffect, useMemo, useState } from 'react'; | ||
| import { Slider } from 'src/common/components'; | ||
| import { rgba } from 'emotion-rgba'; | ||
| // could add in scalePow and others | ||
| import { scaleLog, scaleLinear } from 'd3-scale'; | ||
| import { PluginFilterRangeProps } from './types'; | ||
| import { StatusMessage, StyledFormItem, FilterPluginStyle } from '../common'; | ||
| import { getRangeExtraFormData } from '../../utils'; | ||
|
|
@@ -111,8 +113,7 @@ const Wrapper = styled.div<{ validateStatus?: 'error' | 'warning' | 'info' }>` | |
|
|
||
| const numberFormatter = getNumberFormatter(NumberFormats.SMART_NUMBER); | ||
|
|
||
| const tipFormatter = (value: number) => numberFormatter(value); | ||
|
|
||
| // lower and upper are NOT transformed!!!! | ||
| const getLabel = (lower: number | null, upper: number | null): string => { | ||
| if (lower !== null && upper !== null && lower === upper) { | ||
| return `x = ${numberFormatter(lower)}`; | ||
|
|
@@ -129,20 +130,6 @@ const getLabel = (lower: number | null, upper: number | null): string => { | |
| return ''; | ||
| }; | ||
|
|
||
| const getMarks = ( | ||
| lower: number | null, | ||
| upper: number | null, | ||
| ): { [key: number]: string } => { | ||
| const newMarks: { [key: number]: string } = {}; | ||
| if (lower !== null) { | ||
| newMarks[lower] = numberFormatter(lower); | ||
| } | ||
| if (upper !== null) { | ||
| newMarks[upper] = numberFormatter(upper); | ||
| } | ||
| return newMarks; | ||
| }; | ||
|
|
||
| export default function RangeFilterPlugin(props: PluginFilterRangeProps) { | ||
| const { | ||
| data, | ||
|
|
@@ -157,22 +144,68 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) { | |
| const [row] = data; | ||
| // @ts-ignore | ||
| const { min, max }: { min: number; max: number } = row; | ||
| const { groupby, defaultValue, inputRef, enableSingleValue } = formData; | ||
| const { | ||
| groupby, | ||
| defaultValue, | ||
| inputRef, | ||
| stepSize, | ||
| logScale, | ||
| enableSingleValue, | ||
| } = formData; | ||
| const scaler = logScale | ||
| ? scaleLog().domain([min + 1, max + 1]) | ||
| : scaleLinear().range([min, max]); | ||
|
|
||
| const enableSingleMinValue = enableSingleValue === SingleValueType.Minimum; | ||
| const enableSingleMaxValue = enableSingleValue === SingleValueType.Maximum; | ||
| const enableSingleExactValue = enableSingleValue === SingleValueType.Exact; | ||
| const rangeValue = enableSingleValue === undefined; | ||
|
|
||
| const [col = ''] = ensureIsArray(groupby).map(getColumnLabel); | ||
| // these could be replaced with a property instead, to allow custom transforms | ||
| const transformScale = useCallback( | ||
| (val: number | null) => (val ? scaler(val + (logScale ? 1 : 0)) : val), | ||
| [logScale], | ||
| ); | ||
|
|
||
| const inverseScale = useCallback( | ||
| (val: number | null) => | ||
| val ? scaler.invert(val) - (logScale ? 1 : 0) : val, | ||
| [logScale], | ||
| ); | ||
|
|
||
| const [value, setValue] = useState<[number, number]>( | ||
| defaultValue ?? [min, enableSingleExactValue ? min : max], | ||
| (defaultValue ?? [min, enableSingleExactValue ? min : max]).map( | ||
| transformScale, | ||
| ), | ||
| ); | ||
| const [marks, setMarks] = useState<{ [key: number]: string }>({}); | ||
| const minIndex = 0; | ||
| const maxIndex = 1; | ||
| const minMax = value ?? [min, max]; | ||
| const minMax = useMemo( | ||
| () => value ?? [min ?? 0, max].map(transformScale), | ||
| [max, min, value, transformScale], | ||
| ); | ||
|
|
||
| const tipFormatter = (value: number) => | ||
| numberFormatter(inverseScale(Number(value))); | ||
|
|
||
| // lower & upper are transformed | ||
| const getMarks = useCallback( | ||
| (lower: number | null, upper: number | null): { [key: number]: string } => { | ||
| const newMarks: { [key: number]: string } = {}; | ||
| if (lower !== null) { | ||
| newMarks[lower] = numberFormatter(inverseScale(lower)); | ||
| } | ||
| if (upper !== null) { | ||
| newMarks[upper] = numberFormatter(inverseScale(upper)); | ||
| } | ||
| return newMarks; | ||
| }, | ||
| [inverseScale, value], | ||
| ); | ||
|
|
||
| // value is transformed | ||
| const getBounds = useCallback( | ||
| ( | ||
| value: [number, number], | ||
|
|
@@ -184,41 +217,49 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) { | |
| } | ||
|
|
||
| return { | ||
| lower: lowerRaw > min ? lowerRaw : null, | ||
| upper: upperRaw < max ? upperRaw : null, | ||
| lower: lowerRaw > Number(transformScale(min)) ? lowerRaw : null, | ||
| upper: upperRaw < Number(transformScale(max)) ? upperRaw : null, | ||
| }; | ||
| }, | ||
| [max, min, enableSingleExactValue], | ||
| [max, min, transformScale, value, enableSingleExactValue], | ||
| ); | ||
|
|
||
| const handleAfterChange = useCallback( | ||
| (value: [number, number]): void => { | ||
| // value is transformed | ||
| setValue(value); | ||
| // lower & upper are transformed | ||
| const { lower, upper } = getBounds(value); | ||
| setMarks(getMarks(lower, upper)); | ||
|
|
||
| // removed Number | ||
| setDataMask({ | ||
| extraFormData: getRangeExtraFormData(col, lower, upper), | ||
| extraFormData: getRangeExtraFormData( | ||
| col, | ||
| inverseScale(lower), | ||
| inverseScale(upper), | ||
| ), | ||
| filterState: { | ||
| value: lower !== null || upper !== null ? value : null, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @michael-s-molina What does the filterState.value do? The variable "value" is transformed, meaning it could be the log of the actual desired value. So I'd expect that this would need to actually be |
||
| label: getLabel(lower, upper), | ||
| label: getLabel(inverseScale(lower), inverseScale(upper)), | ||
| }, | ||
| }); | ||
| }, | ||
| [col, getBounds, setDataMask], | ||
| [col, getBounds, setDataMask, getMarks, inverseScale], | ||
| ); | ||
|
|
||
| // value is transformed | ||
| const handleChange = useCallback((value: [number, number]) => { | ||
| setValue(value); | ||
| }, []); | ||
|
|
||
| // value is transformed | ||
| useEffect(() => { | ||
| // when switch filter type and queriesData still not updated we need ignore this case (in FilterBar) | ||
| if (row?.min === undefined && row?.max === undefined) { | ||
| return; | ||
| } | ||
|
|
||
| let filterStateValue = filterState.value ?? [min, max]; | ||
| let filterStateValue = filterState.value ?? [min, max].map(transformScale); | ||
| if (enableSingleMaxValue) { | ||
| const filterStateMax = | ||
| filterStateValue[maxIndex] <= minMax[maxIndex] | ||
|
|
@@ -292,49 +333,53 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) { | |
| > | ||
| {enableSingleMaxValue && ( | ||
| <Slider | ||
| min={min} | ||
| max={max} | ||
| min={transformScale(min) ?? 0} | ||
| max={transformScale(max) ?? undefined} | ||
| value={minMax[maxIndex]} | ||
| tipFormatter={tipFormatter} | ||
| marks={marks} | ||
| onAfterChange={value => handleAfterChange([min, value])} | ||
| onChange={value => handleChange([min, value])} | ||
| step={stepSize} | ||
| /> | ||
| )} | ||
| {enableSingleMinValue && ( | ||
| <StyledMinSlider | ||
| validateStatus={filterState.validateStatus} | ||
| min={min} | ||
| max={max} | ||
| min={transformScale(min) ?? 0} | ||
| max={transformScale(max) ?? undefined} | ||
| value={minMax[minIndex]} | ||
| tipFormatter={tipFormatter} | ||
| marks={marks} | ||
| onAfterChange={value => handleAfterChange([value, max])} | ||
| onChange={value => handleChange([value, max])} | ||
| step={stepSize} | ||
| /> | ||
| )} | ||
| {enableSingleExactValue && ( | ||
| <Slider | ||
| min={min} | ||
| max={max} | ||
| min={transformScale(min) ?? 0} | ||
| max={transformScale(max) ?? undefined} | ||
| included={false} | ||
| value={minMax[minIndex]} | ||
| tipFormatter={tipFormatter} | ||
| marks={marks} | ||
| onAfterChange={value => handleAfterChange([value, value])} | ||
| onChange={value => handleChange([value, value])} | ||
| step={stepSize} | ||
| /> | ||
| )} | ||
| {rangeValue && ( | ||
| <Slider | ||
| range | ||
| min={min} | ||
| max={max} | ||
| min={transformScale(min) ?? 0} | ||
| max={transformScale(max) ?? undefined} | ||
| value={minMax} | ||
| onAfterChange={handleAfterChange} | ||
| onChange={handleChange} | ||
| tipFormatter={tipFormatter} | ||
| marks={marks} | ||
| step={stepSize} | ||
| /> | ||
| )} | ||
| </Wrapper> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.