-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Line averaging, width, and color controls #300
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 all commits
4a7ca82
a03516d
289e401
683c812
a5eea5b
07409fb
49808bc
2f0b2c3
589248f
9a2eca1
6d7c99b
0b1575d
3025629
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 |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { InfoCircleOutlined } from "@ant-design/icons"; | ||
| import { Tooltip } from "antd"; | ||
| import React, { ReactElement, ReactNode, useRef } from "react"; | ||
|
|
||
| type InlineHintProps = { | ||
| title?: ReactNode; | ||
| }; | ||
|
|
||
| /** An icon that can be hovered or focused to show an informational tooltip. */ | ||
| export default function InlineHint(props: InlineHintProps): ReactElement { | ||
| const popupContainerRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| return ( | ||
| <div ref={popupContainerRef}> | ||
| <Tooltip | ||
| trigger={["focus", "hover"]} | ||
| title={props.title} | ||
| getPopupContainer={() => popupContainerRef.current ?? document.body} | ||
| > | ||
| <button | ||
| style={{ | ||
| background: "none", | ||
| border: "none", | ||
| padding: 0, | ||
| margin: 0, | ||
| cursor: "help", | ||
| color: "unset", | ||
| }} | ||
| aria-label="More information" | ||
| > | ||
| <InfoCircleOutlined></InfoCircleOutlined> | ||
| </button> | ||
| </Tooltip> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| import { InputNumber, Row } from "antd"; | ||
| import Slider, { SliderSingleProps } from "antd/es/slider"; | ||
| import React, { ReactElement, useEffect, useState } from "react"; | ||
| import React, { ReactElement, ReactNode, useEffect, useId, useState } from "react"; | ||
|
|
||
| type LabeledSliderProps = { | ||
| sliderProps: SliderSingleProps; | ||
| label: string; | ||
| label: string | ReactNode; | ||
|
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. Updated to allow the inline hint to also be shown here. I'd love to pull in a bunch of the components I've written in TFE, especially for handling input and settings organization...... |
||
| labelWidth?: string; | ||
| id?: string; | ||
| inputMax?: number; | ||
|
|
@@ -25,7 +25,8 @@ export default function LabeledSlider(props: LabeledSliderProps): ReactElement { | |
| } | ||
| }; | ||
|
|
||
| const inputId = props.id || `labeled-slider-${props.label.toLowerCase().replace(/\s+/g, "-")}`; | ||
| const generatedId = useId(); | ||
| const inputId = props.id || `labeled-slider-${generatedId}`; | ||
|
|
||
| useEffect(() => { | ||
| setInputValue(props.sliderProps.value); | ||
|
|
@@ -43,7 +44,10 @@ export default function LabeledSlider(props: LabeledSliderProps): ReactElement { | |
| }} | ||
| wrap={false} | ||
| > | ||
| <label htmlFor={inputId} style={{ width: props.labelWidth }}> | ||
| <label | ||
| htmlFor={inputId} | ||
| style={{ width: props.labelWidth, flexBasis: props.labelWidth, flexShrink: 0 }} | ||
| > | ||
| {props.label} | ||
| </label> | ||
|
|
||
|
|
@@ -61,7 +65,7 @@ export default function LabeledSlider(props: LabeledSliderProps): ReactElement { | |
| onBlur={onConfirmInputValue} | ||
| ></InputNumber> | ||
|
|
||
| <div style={{ width: "100%" }} ref={containerRef}> | ||
| <div style={{ width: "100%", flexGrow: 1, flexShrink: 1 }} ref={containerRef}> | ||
| <Slider | ||
| {...props.sliderProps} | ||
| tooltip={{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| import React, { ReactElement } from "react"; | ||
|
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. |
||
| import { connect } from "react-redux"; | ||
| import { ActionCreator } from "redux"; | ||
|
|
||
| import { | ||
| SetLineAverageWindowAction, | ||
| SetLineDefaultColorAction, | ||
| SetLineWidthAction, | ||
| } from "../../state/selection/types"; | ||
| import { State } from "../../state/types"; | ||
| import { useDebouncedSetter } from "../../hooks"; | ||
| import LabeledSlider from "../LabeledSlider"; | ||
| import { | ||
| getLineDefaultColor, | ||
| getLineMovingAverageWindow, | ||
| getLineWidth, | ||
| } from "../../state/selection/selectors"; | ||
| import { | ||
| setConnectLineDefaultColor, | ||
| setConnectLineAverageWindow, | ||
| setConnectLineWidth, | ||
| } from "../../state/selection/actions"; | ||
| import ResettableColorPicker from "../ResettableColorPicker"; | ||
| import { GENERAL_PLOT_SETTINGS, PALETTE } from "../../constants"; | ||
| import InlineHint from "../InlineHint"; | ||
|
|
||
| type PropsFromState = { | ||
| lineAverageWindow: number; | ||
| lineWidth: number; | ||
| lineDefaultColor: string; | ||
| }; | ||
|
|
||
| type DispatchProps = { | ||
| handleSetLineAverageWindow: ActionCreator<SetLineAverageWindowAction>; | ||
| handleSetLineWidth: ActionCreator<SetLineWidthAction>; | ||
| handleSetLineDefaultColor: ActionCreator<SetLineDefaultColorAction>; | ||
| }; | ||
|
|
||
| type PlotLineSettingsProps = PropsFromState & | ||
| DispatchProps & { | ||
| labelWidth?: string; | ||
|
Contributor
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. maybe a comment on what labelWidth is for? (I'm wondering: why is it a string? what label?)
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. Yeah, can do. It's a string because it's intended to take a CSS specifier, e.g. "100px" or "100%". The label is the label for any settings inputs (sliders or numeric inputs) that sits to the left.
Contributor
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.
|
||
| }; | ||
|
|
||
| const PlotLineSettings = (props: PlotLineSettingsProps): ReactElement => { | ||
| const [lineAverageWindow, setLineAverageWindow] = useDebouncedSetter( | ||
| props.lineAverageWindow, | ||
| props.handleSetLineAverageWindow | ||
| ); | ||
| const [lineWidth, setLineWidth] = useDebouncedSetter(props.lineWidth, props.handleSetLineWidth); | ||
| const [lineDefaultColor, setLineDefaultColor] = useDebouncedSetter( | ||
| props.lineDefaultColor, | ||
| props.handleSetLineDefaultColor | ||
| ); | ||
|
|
||
| return ( | ||
| <div style={{ display: "flex", flexDirection: "column", width: "100%" }}> | ||
| <p style={{ fontWeight: 600, marginBottom: 0, color: PALETTE.white }}>Line settings</p> | ||
| <LabeledSlider | ||
| label={ | ||
| <div style={{ display: "flex", flexDirection: "row", gap: "6px" }}> | ||
| Average window | ||
| <InlineHint title="Total number of points to average over, including past and future." /> | ||
| </div> | ||
| } | ||
| id={"line-average-window-input"} | ||
| labelWidth={props.labelWidth} | ||
| sliderProps={{ | ||
| value: lineAverageWindow, | ||
| onChange: setLineAverageWindow, | ||
| min: 1, | ||
| max: 31, | ||
| step: 2, | ||
| }} | ||
| inputMin={1} | ||
| inputMax={101} | ||
|
ShrimpCryptid marked this conversation as resolved.
|
||
| ></LabeledSlider> | ||
| <LabeledSlider | ||
| label="Line width" | ||
| labelWidth={props.labelWidth} | ||
| sliderProps={{ | ||
| value: lineWidth, | ||
| onChange: setLineWidth, | ||
| min: 0.1, | ||
| max: 3.5, | ||
| step: 0.1, | ||
| marks: { 1.5: <></> }, | ||
| tooltip: { | ||
| formatter: (value) => value?.toFixed(1), | ||
| }, | ||
| }} | ||
| inputMin={0} | ||
| inputMax={100} | ||
|
ShrimpCryptid marked this conversation as resolved.
|
||
| ></LabeledSlider> | ||
| <div | ||
| style={{ | ||
| display: "flex", | ||
| flexDirection: "row", | ||
| justifyContent: "flex-start", | ||
| marginTop: "4px", | ||
| }} | ||
| > | ||
| <label style={{ width: props.labelWidth }}>Default color</label> | ||
| <ResettableColorPicker | ||
| value={lineDefaultColor} | ||
| onChange={(color) => { | ||
| setLineDefaultColor(color.toHexString()); | ||
| }} | ||
| size="small" | ||
| onReset={function (): void { | ||
| setLineDefaultColor(GENERAL_PLOT_SETTINGS.connectionLineDefaultColor); | ||
| }} | ||
| ></ResettableColorPicker> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| function mapStateToProps(state: State): PropsFromState { | ||
| return { | ||
| lineAverageWindow: getLineMovingAverageWindow(state), | ||
| lineWidth: getLineWidth(state), | ||
| lineDefaultColor: getLineDefaultColor(state), | ||
| }; | ||
| } | ||
|
|
||
| const dispatchToPropsMap: DispatchProps = { | ||
| handleSetLineAverageWindow: setConnectLineAverageWindow, | ||
| handleSetLineDefaultColor: setConnectLineDefaultColor, | ||
| handleSetLineWidth: setConnectLineWidth, | ||
| }; | ||
| export default connect<PropsFromState, DispatchProps, unknown, State>( | ||
| mapStateToProps, | ||
| dispatchToPropsMap | ||
| )(PlotLineSettings); | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied from TFE. Appears as a small hoverable or tab-focusable tooltip.