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

[FEATURE] Ability to edit thresholds in panel options for gauge chart #968

Merged
merged 16 commits into from
Feb 27, 2023

Conversation

eunicorn
Copy link
Contributor

@eunicorn eunicorn commented Feb 17, 2023

thresholds-editor

CHANGES

  • You can now define default thresholds colors and add color palette in charts theme.
  • You can add, update, and delete thresholds
  • Implemented a color picker using react-colorful
  • Added percentage mode

TO DO

  • tests
  • add thresholds editor to other charts
  • add support for min ?

Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
@@ -43,6 +43,7 @@
"lodash-es": "^4.17.21",
"mathjs": "^10.6.4",
"mdi-material-ui": "^7.4.0",
"react-colorful": "^5.6.1",
Copy link
Contributor Author

@eunicorn eunicorn Feb 17, 2023

Choose a reason for hiding this comment

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

I was choosing between either react-color or react-colorful, the two most popular color picker libraries.

I ended up choosing react-colorful because the design is simple (@foxbat07 approved), package is smaller, and it is built with react hooks and functional components. It also ended up being very easy to use. But definitely feel free to let me know if you think otherwise/have a strong opinion which color picker to use!

export type GaugeColorStop = [number, string];

export type EChartsAxisLineColors = GaugeColorStop[];

export interface StepOptions {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved StepOptions and ThresholdOptions to plugin-system since I needed these types in ThresholdsEditor which lives in plugin-system/components

Copy link
Member

Choose a reason for hiding this comment

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

Another option could be to move ThresholdsEditor to @perses-dev/components and the types to @perses-dev/core.

We definitely need to decide on long term rules for what goes in each package since currently it isn't consistent (see discussion here: LegendOptionsEditor, UnitSelector, unit types are in @perses-dev/components for instance). Probably fine to wait until @juliepagano looks into package reorganization though too

Copy link
Member

@sjcobb sjcobb Feb 21, 2023

Choose a reason for hiding this comment

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

I think ThresholdsEditor being in @perses-dev/components will also make it easier to have context-aware components in the future (ex: TimeSeriesChart proposal). If it lives in plugin-system, then ColorPicker.tsx would need to move to avoid a circular dependency for instance

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think i was debating to move it to @perses-dev/components but got confused since some components like CalculationSelector are in plugin-system. Can't wait to get this figure out lol.

Anyways moved ThresholdsEditor to @perses-dev/component 😄

const defaultThresholdSteps: EChartsAxisLineColors = [[0, defaultThresholdColor]];

if (thresholds.steps !== undefined) {
// https://echarts.apache.org/en/option.html#series-gauge.axisLine.lineStyle.color
// color segments must be decimal between 0 and 1
const segmentMax = 1;
const valuesArr: number[] = thresholds.steps.map((step: StepOptions) => {
if (thresholds.mode === 'percentage') {
return step.value / 100;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

we will have to update this once we support min

Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
@eunicorn eunicorn requested review from a team, saminzadeh and sjcobb February 17, 2023 20:50
@eunicorn eunicorn marked this pull request as ready for review February 17, 2023 20:50
Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
RED: 'rgba(234, 71, 71, 1)', // red.500
};

export const ThresholdColorsPalette = [ThresholdColors.ORANGE, ThresholdColors.RED];
Copy link
Member

Choose a reason for hiding this comment

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

I really like that threshold colors moved to the chartsTheme, but one difference I'm seeing is previously orange and red were the defaults if a step color wasn't set explicitly, now it shows as blue for ex:

image

Copy link
Contributor Author

@eunicorn eunicorn Feb 21, 2023

Choose a reason for hiding this comment

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

talked to @foxbat07 about this and decided to follow the mui alert colors (info, warning, error)

thresholds: {
  defaultColor: muiTheme.palette.success.main,
  palette: [muiTheme.palette.info.main, muiTheme.palette.warning.main, muiTheme.palette.error.main],
}

Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
Copy link
Contributor

@cndonovan cndonovan left a comment

Choose a reason for hiding this comment

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

This is so nice, yay for our users

const inputValue = e.target.value.replace(/([^0-9A-F]+)/gi, '').substring(0, 8);
setValue(`#${inputValue}`);
// only set color if input value is a valid hex color
if (isValidHex(e.target.value)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see there's a difference in usage between color and value -- if you wanted, you could leave a comment above the useState lines to explain the difference between the two variables

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm reading as:
color stores the actual input from the user, but only if it's valid hex format
value stores the hex value we want to use, including the #, and does some sanitizing of the user input -- is that right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh maybe it's that value is meant to be the visible value for the controlled text input, and color is what we'll pass around internally to use in other parts of the code -- ok I think I'm following now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you're correct - i'll add comments in the code to make this more clear :)

);
};

const isValidHex = (value: string, alpha?: boolean): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the color-picker library come with a function that does this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not yet but i actually got this function from the library. It currently only exports an input component with the validation logic, but there is an open issue to support custom input components omgovich/react-colorful#157

Copy link
Contributor

Choose a reason for hiding this comment

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

sweet, sg

fullWidth
value={value}
onChange={handleInputChange}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

food for thought: when I'm entering colors, I sometimes don't know if I'm supposed to use rgb format (e.g. (24, 32, 150)) or hex format.

ideas:

  • We could add a label that says "Hex value" or something like
  • We could have a placeholder value in hex format

not a big deal though, just ideas

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right now it always prefixes the input with a pound sign to indicate that it's hex format and i don't think it should let you enter parentheses and commas

Copy link
Contributor

Choose a reason for hiding this comment

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

oo yes the # prefix does exactly that!

<Stack flex={1} direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<ThresholdColorPicker label={label} color={color} onColorChange={onColorChange} />
<FormLabel htmlFor={label}>{label}</FormLabel>
<TextField id={label} inputRef={inputRef} type="number" value={value} onChange={onChange} onBlur={onBlur} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Yessss love using the type="number" (those little up and down arrows!)

iconColor?: string;
isSelected?: boolean;
}>(({ iconColor, isSelected }) => ({
backgroundColor: isSelected && iconColor ? `${iconColor}3F` : 'undefined', // 3F represents 25% opacity
Copy link
Contributor

Choose a reason for hiding this comment

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

Pretttty

const [steps, setSteps] = useState(thresholds?.steps);
useEffect(() => {
setSteps(thresholds?.steps);
}, [thresholds?.steps]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Noticed that we're using palette in other places (vs steps). Is palette like a simplified version of the same data in steps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

palette is the color palette we'll use in the chart and steps are the actual values.

for example, if steps[0] is 10 and palette[0] is yellow, we'll show yellow for values greater than or equal to 10

if (!recentlyAddedInputRef.current || !focusRef.current) return;
recentlyAddedInputRef.current?.focus();
focusRef.current = false;
}, [steps?.length]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Might not be possible, but is there a way to call .focus() from inside the addThresholdChange handler and get it to work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm i don't think so? We don't have a ref to the text input yet since it hasn't been rendered until steps is updated

const color = palette[steps.length] ?? getRandomColor(); // we will assign color from the palette first, then generate random color
steps.push({ color, value: (lastStep?.value ?? 0) + 10 });
} else if (steps) {
steps.push({ value: 10 });
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this saying that any newly added threshold defaults to a value of 10?

Copy link
Contributor Author

@eunicorn eunicorn Feb 22, 2023

Choose a reason for hiding this comment

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

if there is no previous threshold set (steps.length === 0), then the first threshold you add will default to 10

Signed-off-by: Eunice Wong <eunicewong@live.com>
@juliepagano
Copy link
Contributor

UX feedback for a potential follow-up with @foxbat07. I find the top bar that communicates the ranges a little overwhelming to process quickly in some cases (e.g. it's a lot when there's a bunch of gauges right next to each other) because there's so many colors. I wonder if it might be helpful to add an option where you can disable them if you like?

image

@juliepagano
Copy link
Contributor

I noticed that the button toggle highlights the "%" when it's selected...
image

...but not the "#." Is this a bug?
image

onChange(updatedThresholds);
}
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Optional suggestion -- You've got a decent amount of logic around adjusting these values that's probably a bit of a pain to test in the context of a react component. If you pulled the core logic out into a few utility functions that you call from the component, you could more easily write some unit tests on those functions outside the react context. That said, you've already written a bunch of tests for the components, so may not be worth refactoring now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh nice i'll keep that in mind for next time, and writing those tests in react context actually helped me uncover a few accessibility issues

@juliepagano
Copy link
Contributor

If you have the time, I think this would be a good case to add some additional visual tests for the gauge chart. Let me know if you want help doing so.

Signed-off-by: Eunice Wong <eunicewong@live.com>
@eunicorn
Copy link
Contributor Author

If you have the time, I think this would be a good case to add some additional visual tests for the gauge chart. Let me know if you want help doing so.

yes definitely! i am hoping to do this as one of the follow ups since this PR is already so big

Copy link
Contributor

@juliepagano juliepagano left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

@juliepagano
Copy link
Contributor

juliepagano commented Feb 22, 2023

FYI, in case you haven't done it before, you can remove the ❌ next to "happo" by clicking "details" and selecting "approve" from the review dropdown.

image


interface ColorPickerProps {
initialColor?: string;
onColorChange?: (color: string) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of onColorChange? Seems like initialColor means that component is uncontrolled so I wonder if this can be misleading. Otherwise, it might make sense to use the controlled component paradigm with value and onChange

Copy link
Contributor Author

@eunicorn eunicorn Feb 23, 2023

Choose a reason for hiding this comment

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

good call, replaced initialColor with color and onColorChange to onChange

Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
…hreshold-gauge

Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
Signed-off-by: Eunice Wong <eunicewong@live.com>
Copy link
Member

@sjcobb sjcobb left a comment

Choose a reason for hiding this comment

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

🚀

@eunicorn eunicorn merged commit f61f37e into main Feb 27, 2023
@eunicorn eunicorn deleted the eunicorn/threshold-gauge branch February 27, 2023 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants