-
Notifications
You must be signed in to change notification settings - Fork 11
Improve color legend tick marks in ColorLegendsContainer
#1169
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
base: main
Are you sure you want to change the base?
Conversation
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.
Pull Request Overview
This PR improves the color legend tick marks in ColorLegendsContainer by implementing a new algorithm for generating nicely positioned tick marks with proper rounding. The changes ensure min/max values are preserved on the scale while using cleaner tick positioning.
- Implements a new
generateNiceAxisTicksutility function for calculating optimal tick positions - Replaces manual tick spacing calculations with smart algorithm that generates round numbers
- Adds comprehensive test coverage for the new axis utilities
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| frontend/tests/unit/axisUtils.test.ts | Comprehensive test suite for the new axis tick generation functionality |
| frontend/src/modules/_shared/utils/numberFormatting.ts | Adds utility function to format numbers without trailing zeros |
| frontend/src/modules/_shared/utils/axisUtils.ts | New utility module with tick generation algorithm and spacing constraints |
| frontend/src/modules/_shared/components/ColorLegendsContainer/colorLegendsContainer.tsx | Refactored to use new tick generation utilities instead of manual calculations |
Comments suppressed due to low confidence (1)
frontend/src/modules/_shared/utils/axisUtils.ts:1
- [nitpick] The threshold
1e-10should be extracted as a named constant likeFLOATING_POINT_EPSILONto make the intent clearer and allow for easier adjustment.
export interface AxisTickOptions {
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| // Remove trailing zeros after decimal point, and remove decimal point if no digits follow | ||
| // This handles cases like: "1.000" -> "1", "1.200" -> "1.2", "1.20K" -> "1.2K" | ||
| return formatted.replace(/(\.\d*?)0+(\D|$)/, "$1$2").replace(/\.$/, ""); |
Copilot
AI
Oct 7, 2025
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.
[nitpick] The regex pattern (\.\d*?)0+(\D|$) could be clearer. Consider breaking this into separate steps or adding a comment explaining the pattern matches trailing zeros followed by non-digit characters or end of string.
| // Remove trailing zeros after decimal point, and remove decimal point if no digits follow | |
| // This handles cases like: "1.000" -> "1", "1.200" -> "1.2", "1.20K" -> "1.2K" | |
| return formatted.replace(/(\.\d*?)0+(\D|$)/, "$1$2").replace(/\.$/, ""); | |
| // Remove trailing zeros from the decimal part (e.g., "1.200" -> "1.2", "1.20K" -> "1.2K") | |
| const noTrailingZeros = formatted.replace(/(\.\d*?[1-9])0+(\D|$)/, "$1$2"); | |
| // Remove dangling decimal point if no digits follow (e.g., "1." -> "1") | |
| return noTrailingZeros.replace(/\.([^\d]|$)/, "$1"); |
| if (effectiveMaxTicks < 2 && options.availableSpace >= options.minTickSpacing) { | ||
| effectiveMaxTicks = 2; | ||
| } |
Copilot
AI
Oct 7, 2025
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.
[nitpick] The magic number 2 should be extracted as a named constant like MIN_TICKS_WITH_BOUNDARIES to clarify that this ensures at least min and max ticks are included.
| * // Returns fewer ticks to prevent overlap | ||
| */ | ||
|
|
||
| export function generateNiceAxisTicks(min: number, max: number, maxTicks: number, options?: AxisTickOptions): number[] { |
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.
I feel like "nice" is a bit too vague, consider something like generateRoundedAxisTicks
| const actualMin = Math.min(min, max); | ||
| const actualMax = Math.max(min, max); | ||
| const range = actualMax - actualMin; | ||
| const MIN_TICK_WITH_BOUNDARIES = 2; // Minimum ticks to ensure min/max is included |
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.
You should write constants like this at the top level of the file
|
|
||
| test("should handle asymmetric ranges around zero", () => { | ||
| const result = generateNiceAxisTicks(-2, 8, 5); | ||
| expect(result).toEqual([-2, 0, 5, 8]); |
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.
Isn't this uneven? The steps between ticks here is 2, 5, 3, but I'd expect something like3,4,3
| describe("edge cases", () => { | ||
| test("should handle maxTicks of 1", () => { | ||
| const result = generateNiceAxisTicks(0, 100, 1); | ||
| expect(result.length).toBeGreaterThanOrEqual(1); |
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.
The resulting list shouldn't be allowed to be longer than the maxTicks value, no? Shouldnt this test be expect(result.length).toBeLessThanOrEqual(1)
|
|
||
| test("should handle maxTicks of 2", () => { | ||
| const result = generateNiceAxisTicks(0, 100, 2); | ||
| expect(result.length).toBeGreaterThanOrEqual(2); |
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.
Same here
| * // Returns fewer ticks to prevent overlap | ||
| */ | ||
|
|
||
| export function generateNiceAxisTicks(min: number, max: number, maxTicks: number, options?: AxisTickOptions): number[] { |
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.
As discussed, might be slightly too aggressive on the computation; these should be able to fit 3/5 ticks
For instance, with the range (-178.726, 762.265), if we consider tens to be a round number, we could have
I sketched up a very quick test in python, I think something like this should work? You'd still need to figure out how to best decide what multiple to round to
real_start = -178.726
real_end = 762.265
ticks = 5
rounding_digits = -1
# Round to the limits outside the
round_start = round(real_start, rounding_digits) # Round to tens
round_end = round(real_end, rounding_digits) + 10**(-rounding_digits) # Round to tens
distance = round_end - round_start
stepSize = round(distance / (ticks - 1), rounding_digits) # stepSize should be the same rounding
rounded_ticks = [(round_start + t * stepSize) for t in range(1, ticks - 1)]
rounded_ticks.insert(0, real_start)
rounded_ticks.append(real_end)Rounded limits: -180.0 , 770.0
Step size: 240.0
Rounded ticks: [-178.726, 60.0, 300.0, 540.0, 762.265]
|
Putting this on hold for now, until after persistance. @HansKallekleiv and @rubenthoms discussed the need of also supporting log scales. |
Closes #1145