Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ab10f3
feat: log scale and step size control for range filters
stevetracvc Jan 12, 2022
0bd7607
fix: prettier
stevetracvc Jan 12, 2022
308c410
fix: dupe min/max props
stevetracvc Jan 12, 2022
987e05c
fix: boolean math issue
stevetracvc Jan 12, 2022
4432e9f
fix: doubled up in controlPanel
stevetracvc Jan 12, 2022
70f6417
merged in master 1.4.1ish
stevetracvc Mar 24, 2022
9a117da
fixes to default/reset value and options for scaling
stevetracvc Mar 24, 2022
c1f9248
fix: prettier stuff
stevetracvc Mar 24, 2022
fe0334f
fix: more prettier
stevetracvc Mar 24, 2022
b284f51
fix: once more
stevetracvc Mar 24, 2022
9d4f5a0
fix: test needed scaling value
stevetracvc Mar 24, 2022
e81d710
Fixed issue with reset filters and added cube/quad roots
stevetracvc Mar 24, 2022
7c4aa69
added tests for all scaling functions
stevetracvc Mar 24, 2022
a2cd0f0
fix: prettier
stevetracvc Mar 24, 2022
209403d
fix: foreach issue
stevetracvc Mar 24, 2022
9ddd258
fix: cleanup old code
stevetracvc Mar 24, 2022
7af88c4
fix: issue with default scaling function
stevetracvc Mar 25, 2022
361ecc8
fix: cleaned up FiltersConfigModal a bit
stevetracvc Apr 2, 2022
f05ebc9
fix: clarified step size is increments of transformed scale
stevetracvc Apr 2, 2022
2b64973
fix: issue with using range filter as a dashboard filter
stevetracvc Apr 2, 2022
a6ab310
feat: added validators to FiltersConfigForm loaded from filter's cont…
stevetracvc Apr 4, 2022
495a2ef
fix: display precision now linked to stepSize
stevetracvc Apr 4, 2022
089df91
fix: remove direct dependence on rc-field-form
stevetracvc Apr 4, 2022
fef9b99
fix: max value now pushed to an integer increment of stepSize
stevetracvc Apr 6, 2022
b57ba75
fix: getControlItemsMap conditional on CheckboxControl
stevetracvc Apr 7, 2022
4997af6
fix: value type for SelectControl onChange
stevetracvc Apr 7, 2022
6369dcc
fix: prev commit requires config type to explicitly be CheckboxControl
stevetracvc Apr 7, 2022
f9f556b
added a couple tests for the new SelectControl in getControlItemsMap
stevetracvc Apr 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { FormInstance } from 'antd/lib/form';
import { getChartControlPanelRegistry, styled, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import { FormItem } from 'src/components/Form';
import SelectControl from 'src/explore/components/controls/SelectControl';
import {
doesColumnMatchFilterType,
getControlItems,
Expand Down Expand Up @@ -146,6 +147,7 @@ export default function getControlItemsMap({
(controlItem: CustomControlItem) =>
controlItem?.config?.renderTrigger &&
controlItem.name !== 'sortAscending' &&
controlItem?.config?.type !== 'SelectControl' &&
Comment thread
stevetracvc marked this conversation as resolved.
Outdated
controlItem.name !== 'enableSingleValue',
)
.forEach(controlItem => {
Expand Down Expand Up @@ -211,6 +213,69 @@ export default function getControlItemsMap({
);
mapControlItems[controlItem.name] = { element, checked: initialValue };
});
controlItems
.filter(
(controlItem: CustomControlItem) =>
controlItem?.config?.renderTrigger &&
controlItem.name !== 'sortAscending' &&
Comment thread
stevetracvc marked this conversation as resolved.
Outdated
controlItem?.config?.type === 'SelectControl',
)
.forEach(controlItem => {
const initialValue =
filterToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default;
const element = (
<>
<CleanFormItem
name={['filters', filterId, 'requiredFirst', controlItem.name]}
hidden
initialValue={
controlItem?.config?.requiredFirst && filterToEdit?.requiredFirst
}
/>
<Tooltip
key={controlItem.name}
placement="left"
title={
controlItem.config.affectsDataMask &&
disabled &&
t('Populate "Default value" to enable this control')
}
>
<StyledRowFormItem
key={controlItem.name}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={initialValue}
valuePropName="option"
colon={false}
label={
<StyledLabel>
{t(`${controlItem.config?.label}`) || t('Select')}
</StyledLabel>
}
>
<SelectControl
name={controlItem.name}
clearable={false}
freeForm={controlItem.config.freeForm}
disabled={controlItem.config.affectsDataMask && disabled}
onChange={(value: any) => {
Comment thread
stevetracvc marked this conversation as resolved.
Outdated
setNativeFilterFieldValues(form, filterId, {
[controlItem.name]: value,
defaultDataMask: null,
});
// controlItem.config.value = value;
Comment thread
stevetracvc marked this conversation as resolved.
Outdated
forceUpdate();
}}
value={controlItem.config.value || initialValue}
choices={controlItem.config.choices}
/>
</StyledRowFormItem>
</Tooltip>
</>
);
mapControlItems[controlItem.name] = { element, checked: false };
});
return {
controlItems: mapControlItems,
mainControlItems: mapMainControlItems,
Expand Down
115 changes: 80 additions & 35 deletions superset-frontend/src/filters/components/Range/RangeFilterPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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)}`;
Expand All @@ -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,
Expand All @@ -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],
Expand All @@ -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,

@stevetracvc stevetracvc Mar 31, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
value.map(inverseScale)
like how it is below for the lower and upper vars. But the queries appear to run properly, and the text labels are correct.... 🤔 I don't see any difference when I inverse this value or leave it transformed.

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]
Expand Down Expand Up @@ -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>
Expand Down
35 changes: 35 additions & 0 deletions superset-frontend/src/filters/components/Range/controlPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,41 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'logScale',
config: {
type: 'CheckboxControl',
label: t('Logarithmic Scale'),
Comment thread
stevetracvc marked this conversation as resolved.
Outdated
default: false,
renderTrigger: true,
description: t('Make the scale logarithmic.'),
},
},
],
[
{
name: 'stepSize',
config: {
type: 'SelectControl',
label: t('Step Size'),
Comment thread
stevetracvc marked this conversation as resolved.
default: 1,
renderTrigger: true,
freeForm: true,
choices: [
[0.001, 0.001],
[0.01, 0.01],
[0.1, 0.1],
[1, 1],
[2, 2],
[10, 10],
[25, 25],
[100, 100],
],
description: t('Set the slider step size.'),
},
},
],
],
},
],
Expand Down
6 changes: 4 additions & 2 deletions superset-frontend/src/filters/components/Range/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ import {
import { RefObject } from 'react';
import { PluginFilterHooks, PluginFilterStylesProps } from '../types';

interface PluginFilterSelectCustomizeProps {
interface PluginFilterRangeCustomizeProps {
max?: number;
min?: number;
stepSize: number;
logScale: boolean;
}

export type PluginFilterRangeQueryFormData = QueryFormData &
PluginFilterStylesProps &
PluginFilterSelectCustomizeProps;
PluginFilterRangeCustomizeProps;

export type PluginFilterRangeProps = PluginFilterStylesProps & {
data: DataRecord[];
Expand Down