Skip to content
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

test date input for time slider and adjust input element #1095

Merged
merged 17 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -24,6 +24,8 @@ export interface AxisRangeControlProps
logScale?: boolean;
/** specify step for increment/decrement buttons in MUI number inputs; MUI's default is 1 */
step?: number;
/** specify the height of the input element */
inputHeight?: number;
}

export default function AxisRangeControl({
Expand All @@ -36,6 +38,7 @@ export default function AxisRangeControl({
disabled = false,
logScale = false,
step = undefined,
inputHeight,
}: AxisRangeControlProps) {
const validator = useCallback(
(
Expand Down Expand Up @@ -79,6 +82,7 @@ export default function AxisRangeControl({
validator={validator}
// add disabled prop to disable input fields
disabled={disabled}
inputHeight={inputHeight}
/>
) : (
<NumberRangeInput
Expand All @@ -91,6 +95,7 @@ export default function AxisRangeControl({
// add disabled prop to disable input fields
disabled={disabled}
step={step}
inputHeight={inputHeight}
/>
)
) : null;
Expand Down
88 changes: 44 additions & 44 deletions packages/libs/components/src/components/plotControls/TimeSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,46 +91,6 @@ function TimeSlider(props: TimeSliderProps) {
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;

// computing the offset of 2 pixel (SAFE_PIXEL) in domain (milliseconds)
// https://github.com/airbnb/visx/blob/86a851cb3bf622b013b186f02f955bcd6548a87f/packages/visx-brush/src/Brush.tsx#L14
const brushOffset =
xBrushScale.invert(2).getTime() - xBrushScale.invert(0).getTime();

// compensating the offset
// x0 and x1 are millisecond value
const startDate = millisecondTodate(x0 + brushOffset);
const endDate = millisecondTodate(x1 - brushOffset);

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, xAxisRange]
);

// Cancel any pending onBrushChange requests when this component is unmounted
useEffect(() => {
return () => {
onBrushChange.cancel();
};
}, []);

// bounds
const xBrushMax = Math.max(width - margin.left - margin.right, 0);
// take 70 % of given height considering axis tick/tick labels at the bottom
Expand Down Expand Up @@ -176,10 +136,50 @@ function TimeSlider(props: TimeSliderProps) {
);

// `brushKey` makes/fakes the brush as a controlled component,
const brushKey = 'not_fake_controlled';
// selectedRange != null
// ? selectedRange.start + ':' + selectedRange.end
// : 'no_brush';
const brushKey =
selectedRange != null
? selectedRange.start + ':' + selectedRange.end
: 'no_brush';

const onBrushChange = useMemo(
() =>
debounce((domain: Bounds | null) => {
if (!domain) return;
const { x0, x1 } = domain;

// computing the offset of 2 pixel (SAFE_PIXEL) in domain (milliseconds)
// https://github.com/airbnb/visx/blob/86a851cb3bf622b013b186f02f955bcd6548a87f/packages/visx-brush/src/Brush.tsx#L14
const brushOffset =
xBrushScale.invert(2).getTime() - xBrushScale.invert(0).getTime();

// compensating the offset
// x0 and x1 are millisecond value
const startDate = millisecondTodate(x0 + brushOffset);
const endDate = millisecondTodate(x1 - brushOffset);

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, xAxisRange, debounceRateMs, xBrushScale]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting those missing deps!

);

// Cancel any pending onBrushChange requests when this component is unmounted
useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

return () => {
onBrushChange.cancel();
};
}, [onBrushChange]);

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type BaseProps<M extends NumberOrDate> = {
disabled?: boolean;
/** Style the Text Field with the warning color and bold stroke */
applyWarningStyles?: boolean;
/** specify the height of the input element */
inputHeight?: number;
};

export type NumberInputProps = BaseProps<number> & { step?: number };
Expand Down Expand Up @@ -86,6 +88,8 @@ function BaseInput({
displayRangeViolationWarnings = true,
disabled = false,
applyWarningStyles = false,
// default value is 36.5
inputHeight = 36.5,
...props
}: BaseInputProps) {
if (validator && (required || minValue != null || maxValue != null))
Expand All @@ -102,7 +106,7 @@ function BaseInput({

const classes = makeStyles({
root: {
height: 36.5, // default height is 56 and is waaaay too tall
height: inputHeight, // default height is 56 and is waaaay too tall
// 34.5 is the height of the reset button, but 36.5 lines up better
// set width for date
width: valueType === 'date' ? 165 : '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export type BaseProps<M extends NumberOrDateRange> = {
clearButtonLabel?: string;
/** add disabled prop to disable input fields */
disabled?: boolean;
/** specify the height of the input element */
inputHeight?: number;
};

export type NumberRangeInputProps = BaseProps<NumberRange> & { step?: number };
Expand Down Expand Up @@ -86,6 +88,7 @@ function BaseInput({
clearButtonLabel = 'Clear',
// add disabled prop to disable input fields
disabled = false,
inputHeight,
...props
}: BaseInputProps) {
if (validator && required)
Expand Down Expand Up @@ -175,7 +178,9 @@ function BaseInput({
{label}
</Typography>
)}
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div
style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}
>
{valueType === 'number' ? (
<NumberInput
value={min as number}
Expand All @@ -192,6 +197,7 @@ function BaseInput({
// add disabled prop to disable input fields
disabled={disabled}
step={step}
inputHeight={inputHeight}
/>
) : (
<DateInput
Expand All @@ -208,6 +214,7 @@ function BaseInput({
}}
// add disabled prop to disable input fields
disabled={disabled}
inputHeight={inputHeight}
/>
)}
<div style={{ display: 'flex', flexDirection: 'row' }}>
Expand Down Expand Up @@ -241,6 +248,7 @@ function BaseInput({
// add disabled prop to disable input fields
disabled={disabled}
step={step}
inputHeight={inputHeight}
/>
) : (
<DateInput
Expand All @@ -257,6 +265,7 @@ function BaseInput({
}}
// add disabled prop to disable input fields
disabled={disabled}
inputHeight={inputHeight}
/>
)}
{showClearButton && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useState } from 'react';
import { useState, useCallback } from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';
import { LinePlotProps } from '../../plots/LinePlot';
import TimeSlider, {
TimeSliderDataProp,
} from '../../components/plotControls/TimeSlider';
import { DraggablePanel } from '@veupathdb/coreui/lib/components/containers';

import AxisRangeControl from '../../components/plotControls/AxisRangeControl';
import { NumberOrDateRange } from '../../types/general';

export default {
title: 'Plot Controls/TimeSlider',
component: TimeSlider,
Expand Down Expand Up @@ -301,7 +304,56 @@ export const TimeFilter: Story<LinePlotProps> = (args: any) => {

// set constant values
const defaultSymbolSize = 0.8;
const defaultColor = '#333';

// control selectedRange
const handleAxisRangeChange = useCallback(
(newRange?: NumberOrDateRange) => {
if (newRange)
setSelectedRange({
start: newRange.min as string,
end: newRange.max as string,
});
},
[setSelectedRange]
);

const handleArrowClick = useCallback(
(arrow: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is STORY code. I didn't understand how it worked but now I realise I should ignore it.

// let's assume that selectedRange has the format of 'yyyy-mm-dd'
if (
selectedRange &&
selectedRange.start != null &&
selectedRange.end != null
) {
const selectedRangeArray =
arrow === 'left'
? selectedRange.start.split('-')
: selectedRange.end.split('-');
const addSubtractYear =
arrow === 'left'
? String(Number(selectedRangeArray[0]) - 1)
: String(Number(selectedRangeArray[0]) + 1);
const changeYear =
addSubtractYear +
'-' +
selectedRangeArray[1] +
'-' +
selectedRangeArray[2];
setSelectedRange((prev) => {
return arrow === 'left'
? {
start: changeYear as string,
end: prev?.end as string,
}
: {
start: prev?.start as string,
end: changeYear as string,
};
});
}
},
[selectedRange, setSelectedRange]
);

return (
<DraggablePanel
Expand All @@ -322,16 +374,47 @@ export const TimeFilter: Story<LinePlotProps> = (args: any) => {
>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr repeat(1, auto) 1fr',
gridColumnGap: '5px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingTop: '1em',
}}
>
{/* display start to end value */}
<div style={{ gridColumnStart: 2 }}>
{selectedRange?.start} ~ {selectedRange?.end}
<div>
<button
style={{ marginRight: '1em' }}
onClick={() => handleArrowClick('left')}
>
<i className="fa fa-arrow-left" aria-hidden="true"></i>
</button>
</div>
{/* add axis range control */}
<AxisRangeControl
range={
selectedRange != null
? {
min: selectedRange.start,
max: selectedRange.end,
}
: undefined
}
onRangeChange={handleAxisRangeChange}
valueType={'date'}
// set maxWidth
containerStyles={{
maxWidth: '350px',
}}
// default height of the input element is 36.5 which may be too high for timeSlider
// thus, introduced new prop to control it
inputHeight={20}
/>
<div>
<button
style={{ marginLeft: '2em' }}
onClick={() => handleArrowClick('right')}
>
<i className="fa fa-arrow-right" aria-hidden="true"></i>
</button>
</div>
</div>
<TimeSlider
Expand Down
Loading
Loading