diff --git a/packages/libs/components/src/components/plotControls/EzTimeFilter.tsx b/packages/libs/components/src/components/plotControls/TimeSlider.tsx similarity index 72% rename from packages/libs/components/src/components/plotControls/EzTimeFilter.tsx rename to packages/libs/components/src/components/plotControls/TimeSlider.tsx index 777f9496d5..01e39f05b5 100755 --- a/packages/libs/components/src/components/plotControls/EzTimeFilter.tsx +++ b/packages/libs/components/src/components/plotControls/TimeSlider.tsx @@ -12,29 +12,33 @@ import { millisecondTodate } from '../../utils/date-format-change'; import { Bar } from '@visx/shape'; import { debounce } from 'lodash'; -export type EZTimeFilterDataProp = { +export type TimeSliderDataProp = { x: string; y: number; }; -export type EzTimeFilterProps = { +export type TimeSliderProps = { /** Ez time filter data */ - data: EZTimeFilterDataProp[]; + data: TimeSliderDataProp[]; /** current state of selectedRange */ selectedRange: { start: string; end: string } | undefined; /** update function selectedRange */ setSelectedRange: ( selectedRange: { start: string; end: string } | undefined ) => void; + /** optional xAxisRange - will limit the selection to be within this */ + xAxisRange?: { start: string; end: string }; /** width */ width?: number; /** height */ height?: number; - /** color of the selected range */ + /** color of the 'has data' bars - default is black */ + barColor?: string; + /** color of the selected range - default is lightblue */ brushColor?: string; - /** axis tick and tick label color */ + /** axis tick and tick label color - default is black */ axisColor?: string; - /** opacity of selected brush */ + /** opacity of selected brush - default is 0.4 */ brushOpacity?: number; /** debounce rate in millisecond */ debounceRateMs?: number; @@ -43,13 +47,14 @@ export type EzTimeFilterProps = { }; // using forwardRef -function EzTimeFilter(props: EzTimeFilterProps) { +function TimeSlider(props: TimeSliderProps) { const { data, // set default width and height width = 720, height = 100, brushColor = 'lightblue', + barColor = '#333', axisColor = '#000', brushOpacity = 0.4, selectedRange, @@ -57,6 +62,7 @@ function EzTimeFilter(props: EzTimeFilterProps) { // set a default debounce time in milliseconds debounceRateMs = 500, disabled = false, + xAxisRange, } = props; const resizeTriggerAreas: ResizeTriggerAreas[] = disabled @@ -82,25 +88,32 @@ function EzTimeFilter(props: EzTimeFilterProps) { }; // accessors for data - const getXData = (d: EZTimeFilterDataProp) => new Date(d.x); - const getYData = (d: EZTimeFilterDataProp) => d.y; + const getXData = (d: TimeSliderDataProp) => new Date(d.x); + const getYData = (d: TimeSliderDataProp) => d.y; const onBrushChange = useMemo( () => debounce((domain: Bounds | null) => { if (!domain) return; - const { x0, x1 } = domain; - - const selectedDomain = { - // x0 and x1 are millisecond value - start: millisecondTodate(x0), - end: millisecondTodate(x1), - }; - - setSelectedRange(selectedDomain); + // x0 and x1 are millisecond value + const startDate = millisecondTodate(x0); + const endDate = millisecondTodate(x1); + setSelectedRange({ + // don't let range go outside the xAxisRange, if provided + start: xAxisRange + ? startDate < xAxisRange.start + ? xAxisRange.start + : startDate + : startDate, + end: xAxisRange + ? endDate > xAxisRange.end + ? xAxisRange.end + : endDate + : endDate, + }); }, debounceRateMs), - [setSelectedRange] + [setSelectedRange, xAxisRange] ); // Cancel any pending onBrushChange requests when this component is unmounted @@ -112,8 +125,8 @@ function EzTimeFilter(props: EzTimeFilterProps) { // bounds const xBrushMax = Math.max(width - margin.left - margin.right, 0); - // take 80 % of given height considering axis tick/tick labels at the bottom - const yBrushMax = Math.max(0.8 * height - margin.top - margin.bottom, 0); + // take 70 % of given height considering axis tick/tick labels at the bottom + const yBrushMax = Math.max(0.7 * height - margin.top - margin.bottom, 0); // scaling const xBrushScale = useMemo( @@ -143,6 +156,10 @@ function EzTimeFilter(props: EzTimeFilterProps) { () => selectedRange != null ? { + // If we reenable the fake controlled behaviour of the component using the key prop + // then we'll need to figure out why both brush handles drift every time you adjust one of them. + // The issue is something to do with the round-trip conversion of pixel/date/millisecond positions. + // A good place to start looking is here. start: { x: xBrushScale(new Date(selectedRange.start)) }, end: { x: xBrushScale(new Date(selectedRange.end)) }, } @@ -150,17 +167,11 @@ function EzTimeFilter(props: EzTimeFilterProps) { [selectedRange, xBrushScale] ); - // compute bar width manually as scaleTime is used for Bar chart - const barWidth = xBrushMax / data.length; - - // data bar color - const defaultColor = '#333'; - - // this makes/fakes the brush as a controlled component - const brushKey = - initialBrushPosition != null - ? initialBrushPosition.start + ':' + initialBrushPosition.end - : 'no_brush'; + // `brushKey` makes/fakes the brush as a controlled component, + const brushKey = 'not_fake_controlled'; + // selectedRange != null + // ? selectedRange.start + ':' + selectedRange.end + // : 'no_brush'; return (
{ const barHeight = yBrushMax - yBrushScale(getYData(d)); + const x = xBrushScale(getXData(d)); + // calculate the width using the next bin's date: + // subtract a constant to create separate bars for each bin + const barWidth = + i + 1 >= data.length + ? 0 + : xBrushScale(getXData(data[i + 1])) - x - 1; return ( barHeight = 0; dataY = 1 -> barHeight = 60 @@ -190,8 +208,8 @@ function EzTimeFilter(props: EzTimeFilterProps) { y={barHeight === 0 ? yBrushMax : (1 / 4) * yBrushMax} height={(1 / 2) * barHeight} // set the last data's barWidth to be 0 so that it does not overflow to dragging area - width={i === data.length - 1 ? 0 : barWidth} - fill={defaultColor} + width={barWidth} + fill={barColor} /> ); @@ -210,9 +228,8 @@ function EzTimeFilter(props: EzTimeFilterProps) { yScale={yBrushScale} width={xBrushMax} height={yBrushMax} - margin={margin} handleSize={8} - // resize + margin={margin /* prevents brushing offset */} resizeTriggerAreas={resizeTriggerAreas} brushDirection="horizontal" initialBrushPosition={initialBrushPosition} @@ -249,4 +266,4 @@ function BrushHandle({ x, height, isBrushActive }: BrushHandleRenderProps) { ); } -export default EzTimeFilter; +export default TimeSlider; diff --git a/packages/libs/components/src/stories/plotControls/EzTimeFilter.stories.tsx b/packages/libs/components/src/stories/plotControls/TimeSlider.stories.tsx similarity index 96% rename from packages/libs/components/src/stories/plotControls/EzTimeFilter.stories.tsx rename to packages/libs/components/src/stories/plotControls/TimeSlider.stories.tsx index 2d50ee2eb1..eaf144135b 100755 --- a/packages/libs/components/src/stories/plotControls/EzTimeFilter.stories.tsx +++ b/packages/libs/components/src/stories/plotControls/TimeSlider.stories.tsx @@ -1,14 +1,14 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { Story, Meta } from '@storybook/react/types-6-0'; import { LinePlotProps } from '../../plots/LinePlot'; -import EzTimeFilter, { - EZTimeFilterDataProp, -} from '../../components/plotControls/EzTimeFilter'; +import TimeSlider, { + TimeSliderDataProp, +} from '../../components/plotControls/TimeSlider'; import { DraggablePanel } from '@veupathdb/coreui/lib/components/containers'; export default { - title: 'Plot Controls/EzTimeFilter', - component: EzTimeFilter, + title: 'Plot Controls/TimeSlider', + component: TimeSlider, } as Meta; // GEMS1 Case Control; x: Enrollment date; y: Weight @@ -246,7 +246,7 @@ const LineplotData = { export const TimeFilter: Story = (args: any) => { // converting lineplot data to visx format - const timeFilterData: EZTimeFilterDataProp[] = LineplotData.series[0].x.map( + const timeFilterData: TimeSliderDataProp[] = LineplotData.series[0].x.map( (value, index) => { // return { x: value, y: LineplotData.series[0].y[index] }; return { x: value, y: LineplotData.series[0].y[index] >= 9 ? 1 : 0 }; @@ -334,7 +334,7 @@ export const TimeFilter: Story = (args: any) => { {selectedRange?.start} ~ {selectedRange?.end}
- { + if (filterRange) { + const diff = DateMath.diff( + new Date(filterRange.min as string), + new Date(filterRange.max as string), + unit as DateMath.Unit + ); + // 12 is somewhat arbitrary, but it basically + // means if there are >= 12 years, use year bins. + // Otherwise if >= 12 months, use month bins, etc + return diff >= 12; + } + } + ) ?? 'day') as TimeUnit; + return { ...variable, vocabulary, @@ -176,9 +199,11 @@ export function useStudyEntities(filters?: Filter[]) { ? (filterRange.max as string) : (variable.distributionDefaults .rangeMax as string), + binUnits, + binWidth, }, }; - else + } else return { ...variable, vocabulary, diff --git a/packages/libs/eda/src/lib/core/utils/trim.ts b/packages/libs/eda/src/lib/core/utils/trim.ts new file mode 100644 index 0000000000..282a013411 --- /dev/null +++ b/packages/libs/eda/src/lib/core/utils/trim.ts @@ -0,0 +1,12 @@ +import { dropRightWhile, dropWhile } from 'lodash'; + +/** + * Something that should be in lodash! Oh, it nearly is. + * + * Trim an array from the start and end, removing elements for which the filter function returns true + * + * Unused as of 2023-10-20 + */ +export function trimArray(array: Array, filter: (val: T) => boolean) { + return dropRightWhile(dropWhile(array, filter), filter); +} diff --git a/packages/libs/eda/src/lib/map/analysis/EZTimeFilter.tsx b/packages/libs/eda/src/lib/map/analysis/EZTimeFilter.tsx deleted file mode 100755 index 4cc4aaa36a..0000000000 --- a/packages/libs/eda/src/lib/map/analysis/EZTimeFilter.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import { useMemo, useCallback, useState } from 'react'; -import { H6, Toggle } from '@veupathdb/coreui'; -import EzTimeFilterWidget, { - EZTimeFilterDataProp, -} from '@veupathdb/components/lib/components/plotControls/EzTimeFilter'; -import { InputVariables } from '../../core/components/visualizations/InputVariables'; -import { VariablesByInputName } from '../../core/utils/data-element-constraints'; -import { usePromise } from '../../core'; -import { - DateVariable, - NumberVariable, - StudyEntity, -} from '../../core/types/study'; -import { VariableDescriptor } from '../../core/types/variable'; - -import { SubsettingClient } from '../../core/api'; -import Spinner from '@veupathdb/components/lib/components/Spinner'; -import { useFindEntityAndVariable, Filter } from '../../core'; -import { zip } from 'lodash'; -import { AppState } from './appState'; -import { timeSliderVariableConstraints } from './config/eztimeslider'; - -interface Props { - studyId: string; - entities: StudyEntity[]; - // to handle filters - subsettingClient: SubsettingClient; - filters: Filter[] | undefined; - starredVariables: VariableDescriptor[]; - toggleStarredVariable: (targetVariableId: VariableDescriptor) => void; - - config: NonNullable; - updateConfig: (newConfig: NonNullable) => void; -} - -export default function EZTimeFilter({ - studyId, - entities, - subsettingClient, - filters, - starredVariables, - toggleStarredVariable, - config, - updateConfig, -}: Props) { - const findEntityAndVariable = useFindEntityAndVariable(); - const [minimized, setMinimized] = useState(true); - - const { variable, active, selectedRange } = config; - const variableMetadata = findEntityAndVariable(variable); - - // data request to distribution for time slider - const getTimeSliderData = usePromise( - useCallback(async () => { - // no data request if no variable is available - if ( - variableMetadata == null || - variable == null || - !( - NumberVariable.is(variableMetadata.variable) || - DateVariable.is(variableMetadata.variable) - ) - ) - return; - - const binSpec = { - displayRangeMin: - variableMetadata.variable.distributionDefaults.rangeMin + - (variableMetadata.variable.type === 'date' ? 'T00:00:00Z' : ''), - displayRangeMax: - variableMetadata.variable.distributionDefaults.rangeMax + - (variableMetadata.variable.type === 'date' ? 'T00:00:00Z' : ''), - binWidth: variableMetadata.variable.distributionDefaults.binWidth ?? 1, - binUnits: - 'binUnits' in variableMetadata.variable.distributionDefaults - ? variableMetadata.variable.distributionDefaults.binUnits - : undefined, - }; - const distributionResponse = await subsettingClient.getDistribution( - studyId, - variable.entityId, - variable.variableId, - { - valueSpec: 'count', - filters: filters ?? [], - binSpec, - } - ); - - return { - x: distributionResponse.histogram.map((d) => d.binStart), - // conditionally set y-values to be 1 (with data) and 0 (no data) - y: distributionResponse.histogram.map((d) => (d.value >= 1 ? 1 : 0)), - }; - }, [variableMetadata?.variable, variable, subsettingClient, filters]) - ); - - // converting data to visx format - const timeFilterData: EZTimeFilterDataProp[] = useMemo( - () => - !getTimeSliderData.pending && getTimeSliderData.value != null - ? zip(getTimeSliderData.value.x, getTimeSliderData.value.y) - .map(([xValue, yValue]) => ({ x: xValue, y: yValue })) - // and a type guard filter to avoid any `!` assertions. - .filter( - (val): val is EZTimeFilterDataProp => - val.x != null && val.y != null - ) - : [], - [getTimeSliderData] - ); - - // set time slider width and y position - const timeFilterWidth = 750; - - // inputVariables onChange function - function handleInputVariablesOnChange(selection: VariablesByInputName) { - if (!selection.overlayVariable) { - console.error( - `Expected overlayVariable to be defined but got ${typeof selection.overlayVariable}` - ); - return; - } - - updateConfig({ - variable: selection.overlayVariable, - selectedRange: undefined, - active: true, - }); - } - - // if no variable in a study is suitable to time slider, do not show time slider - return variable != null && variableMetadata != null ? ( -
setMinimized(false)} - onMouseLeave={() => setMinimized(true)} - > -
-
-
- {variableMetadata.variable.displayName + - (active && selectedRange - ? ` [${selectedRange?.start} to ${selectedRange?.end}]` - : ' (all dates)')} -
-
- {/* display start to end value - TO DO: make these date inputs? - {selectedRange && ( -
- {selectedRange?.start} ~ {selectedRange?.end} -
- )} - */} -
- updateConfig({ ...config, active })} - /> -
-
- {/* display data loading spinner while requesting data to the backend */} - {getTimeSliderData.pending && ( -
- -
- )} - {/* conditional loading for EzTimeFilter */} - {!getTimeSliderData.pending && - getTimeSliderData.value != null && - timeFilterData.length > 0 && ( - - updateConfig({ ...config, selectedRange }) - } - width={timeFilterWidth - 30} - height={75} - // fill color of the selectedRange - brushColor={'lightpink'} - brushOpacity={0.4} - // axis tick and tick label color - axisColor={'#000'} - // disable user-interaction - disabled={!active} - /> - )} - {!minimized && ( -
- -
- )} -
- ) : null; -} diff --git a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx index 805b84f598..499e68af51 100755 --- a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx +++ b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx @@ -78,7 +78,7 @@ import { donutMarkerPlugin, } from './mapTypes'; -import EZTimeFilter from './EZTimeFilter'; +import TimeSliderQuickFilter from './TimeSliderQuickFilter'; import { useToggleStarredVariable } from '../../core/hooks/starredVariables'; import { MapTypeMapLayerProps } from './mapTypes/types'; import { defaultViewport } from '@veupathdb/components/lib/map/config/map'; @@ -804,7 +804,7 @@ function MapAnalysisImpl(props: ImplProps) { {/* Time slider component - only if prerequisite variable is available */} {appState.timeSliderConfig && appState.timeSliderConfig.variable && ( - )} diff --git a/packages/libs/eda/src/lib/map/analysis/MapHeader.scss b/packages/libs/eda/src/lib/map/analysis/MapHeader.scss index 08cdfff5b4..62a409441d 100644 --- a/packages/libs/eda/src/lib/map/analysis/MapHeader.scss +++ b/packages/libs/eda/src/lib/map/analysis/MapHeader.scss @@ -7,7 +7,8 @@ position: relative; transition: top 0.1s ease; width: 100%; - z-index: 10; + // We now allow draggable items to go on top of the header: + z-index: 2; padding: 7px 0px; &__Contents { diff --git a/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx new file mode 100755 index 0000000000..b18426a968 --- /dev/null +++ b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx @@ -0,0 +1,335 @@ +import { useMemo, useCallback, useState } from 'react'; +import { ChevronRight, H6, Toggle } from '@veupathdb/coreui'; +import TimeSlider, { + TimeSliderDataProp, +} from '@veupathdb/components/lib/components/plotControls/TimeSlider'; +import { InputVariables } from '../../core/components/visualizations/InputVariables'; +import { VariablesByInputName } from '../../core/utils/data-element-constraints'; +import { usePromise } from '../../core'; +import { DateVariable, StudyEntity } from '../../core/types/study'; +import { VariableDescriptor } from '../../core/types/variable'; + +import { SubsettingClient } from '../../core/api'; +import Spinner from '@veupathdb/components/lib/components/Spinner'; +import { useFindEntityAndVariable, Filter } from '../../core'; +import { zip } from 'lodash'; +import { AppState } from './appState'; +import { timeSliderVariableConstraints } from './config/eztimeslider'; +import { useUITheme } from '@veupathdb/coreui/lib/components/theming'; +import HelpIcon from '@veupathdb/wdk-client/lib/Components/Icon/HelpIcon'; +import { SiteInformationProps } from './Types'; +import { mapSidePanelBackgroundColor } from '../constants'; + +interface Props { + studyId: string; + entities: StudyEntity[]; + // to handle filters + subsettingClient: SubsettingClient; + filters: Filter[] | undefined; + starredVariables: VariableDescriptor[]; + toggleStarredVariable: (targetVariableId: VariableDescriptor) => void; + + config: NonNullable; + updateConfig: (newConfig: NonNullable) => void; + siteInformation: SiteInformationProps; +} + +export default function TimeSliderQuickFilter({ + studyId, + entities, + subsettingClient, + filters, + starredVariables, + toggleStarredVariable, + config, + updateConfig, + siteInformation, +}: Props) { + const findEntityAndVariable = useFindEntityAndVariable(filters); // filter sensitivity + const theme = useUITheme(); + const [minimized, setMinimized] = useState(true); + + const { variable, active, selectedRange } = config; + const variableMetadata = findEntityAndVariable(variable); + const { siteName } = siteInformation; + + // extend the back end range request if our selectedRange is outside of it + const extendedDisplayRange = + variableMetadata && DateVariable.is(variableMetadata.variable) + ? selectedRange == null + ? { + start: variableMetadata.variable.distributionDefaults.rangeMin, + end: variableMetadata.variable.distributionDefaults.rangeMax, + } + : { + start: + variableMetadata.variable.distributionDefaults.rangeMin < + selectedRange.start + ? variableMetadata.variable.distributionDefaults.rangeMin + : selectedRange.start, + end: + variableMetadata.variable.distributionDefaults.rangeMax > + selectedRange.end + ? variableMetadata.variable.distributionDefaults.rangeMax + : selectedRange.end, + } + : undefined; + + // data request to distribution for time slider + const getTimeSliderData = usePromise( + useCallback(async () => { + // no data request if no variable is available + if ( + variableMetadata == null || + variable == null || + extendedDisplayRange == null || + !DateVariable.is(variableMetadata.variable) + ) + return; + + const binSpec = { + displayRangeMin: + extendedDisplayRange.start + + (variableMetadata.variable.type === 'date' ? 'T00:00:00Z' : ''), + displayRangeMax: + extendedDisplayRange.end + + (variableMetadata.variable.type === 'date' ? 'T00:00:00Z' : ''), + binWidth: variableMetadata.variable.distributionDefaults.binWidth ?? 1, + binUnits: + 'binUnits' in variableMetadata.variable.distributionDefaults + ? variableMetadata.variable.distributionDefaults.binUnits + : undefined, + }; + const distributionResponse = await subsettingClient.getDistribution( + studyId, + variable.entityId, + variable.variableId, + { + valueSpec: 'count', + filters: filters ?? [], + binSpec, + } + ); + + const histo = distributionResponse.histogram; + // return the bin starts and the final bin end (with a fixed y value of zero) + return { + x: histo + .map((d) => d.binStart) + .concat([histo[histo.length - 1].binEnd]), + // conditionally set y-values to be 1 (with data) and 0 (no data) + y: histo.map((d) => (d.value >= 1 ? 1 : 0)).concat([0]), + }; + }, [ + variableMetadata?.variable, + variable, + subsettingClient, + filters, + extendedDisplayRange?.start, + extendedDisplayRange?.end, + ]) + ); + + // converting data to visx format + const timeFilterData: TimeSliderDataProp[] = useMemo(() => { + const restructured = + !getTimeSliderData.pending && getTimeSliderData.value != null + ? zip(getTimeSliderData.value.x, getTimeSliderData.value.y) + .map(([xValue, yValue]) => ({ x: xValue, y: yValue })) + // and a type guard filter to avoid any `!` assertions. + .filter( + (val): val is TimeSliderDataProp => val.x != null && val.y != null + ) + : []; + + return restructured; + }, [getTimeSliderData]); + + // set time slider width and y position + const timeFilterWidth = 750; + + // inputVariables onChange function + function handleInputVariablesOnChange(selection: VariablesByInputName) { + if (!selection.overlayVariable) { + console.error( + `Expected overlayVariable to be defined but got ${typeof selection.overlayVariable}` + ); + return; + } + + updateConfig({ + variable: selection.overlayVariable, + selectedRange: undefined, + active: true, + }); + } + + // (easily) centering the variable picker requires two same-width divs either side + const sideElementStyle = { width: '70px' }; + + const sliderHeight = minimized ? 50 : 75; + + const background = + siteName === 'VectorBase' + ? '#F5FAF1D0' + : (theme?.palette.primary.hue[100] ?? mapSidePanelBackgroundColor) + 'D0'; // add transparency + + const borderRadius = '0px 0px 7px 7px'; // TO DO: add border radius and box shadow to the theme? + const boxShadow = + 'rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,rgba(0, 0, 0, 0.3) 0px 1px 3px -1px'; + + const helpText = ( +
+
Timeline help
+

+

    +
  • Black bars indicate when in time there is available data
  • +
  • Permanent filters are applied if applicable
  • +
  • + You currently have {filters?.length} active permanent filter(s) +
  • +
  • + Apply a temporary time-based filter by dragging a window across the + graphic +
  • +
  • + Click once on the graphic outside the window to cancel the temporary + filter +
  • +
+

+

+ Expand the panel with the{' '} + tab or click{' '} + setMinimized(false)}> + here + {' '} + to reveal further controls that allow you to: +

    +
  • + change the date variable (currently{' '} + {variableMetadata?.variable.displayName}) +
  • +
  • toggle the temporary time window filter on/off
  • +
+

+ {minimized && !active && ( +

+ + The timeline temporary filter is currently disabled. To enable it, + expand the panel and click on the toggle. + +

+ )} +
+ ); + + // if no variable in a study is suitable to time slider, do not show time slider + return variable != null && variableMetadata != null ? ( +
+
+ {/* container for the slider widget or spinner */} +
+ {/* conditional loading for TimeSlider */} + {!getTimeSliderData.pending && + getTimeSliderData.value != null && + timeFilterData.length > 0 ? ( + + updateConfig({ ...config, selectedRange }) + } + xAxisRange={extendedDisplayRange} + width={timeFilterWidth - 30} + height={sliderHeight} + // fill color of the selectedRange + brushColor={'lightpink'} + brushOpacity={0.4} + // axis tick and tick label color + barColor={!active ? '#aaa' : '#000'} + axisColor={!active ? '#888' : '#000'} + // disable user-interaction + disabled={!active} + /> + ) : ( + + )} +
+ +
+
+
+ {!minimized && ( + <> +
+
+ +
+
+ updateConfig({ ...config, active })} + /> +
+ + )} +
+
+ +
setMinimized(!minimized)} + style={{ + margin: 'auto', + fontSize: 18, // controls the SVG chevron size + width: 50, + height: 20, + textAlign: 'center', + background, + borderRadius, + boxShadow, + }} + > + +
+
+ ) : null; +} diff --git a/packages/libs/eda/src/lib/map/analysis/config/eztimeslider.ts b/packages/libs/eda/src/lib/map/analysis/config/eztimeslider.ts index 8d0931eb21..bffbaf8691 100644 --- a/packages/libs/eda/src/lib/map/analysis/config/eztimeslider.ts +++ b/packages/libs/eda/src/lib/map/analysis/config/eztimeslider.ts @@ -6,13 +6,7 @@ export const timeSliderVariableConstraints: DataElementConstraintRecord[] = [ isRequired: true, minNumVars: 1, maxNumVars: 1, - // TODO: testing with SCORE S. mansoni Cluster Randomized Trial study - // however, this study does not have date variable, thus temporarily use below for test purpose - // i.e., additionally allowing 'integer' - // allowedTypes: ['date', 'integer'], - // TODO: below two are correct ones allowedTypes: ['date'], - // isTemporal: true, }, }, ];