-
Notifications
You must be signed in to change notification settings - Fork 121
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
fix(heatmap): remove values when brushing only over axes #1364
Conversation
@rshen91 can you please add a link to an issue for this, or describe it here? |
return ( | ||
const pointerOnLeftAxis = | ||
pointer && pointer.lastDrag ? pointer.lastDrag?.start.position.x < canvasDimension.left : false; | ||
return !pointerOnLeftAxis ? ( |
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.
Nice ternary here! I try not to negate a singular predicate in such cases, no real issue with null
being in the 1st outcome of the ternary, in fact a bit nicer as it gets the non-result out of the way and the code reader can focus on the DOM fragment
const pointerOnLeftAxis = | ||
pointer && pointer.lastDrag ? pointer.lastDrag?.start.position.x < canvasDimension.left : false; |
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.
When there's a true
or false
as a value expressly yielded eg. via a ternary, it almost always means, it can be simplified:
const pointerOnLeftAxis =
pointer && pointer.lastDrag && pointer.lastDrag?.start.position.x < canvasDimension.left;
Explanation: if pointer
or pointer.lastDrag
are falsey, the expression evaluates to falsey anyway.
The question mark after the second use of lastDrag
isn't needed, because TS will type guard it anyway, since you never land on that if pointer.lastDrag
is nullish:
const pointerOnLeftAxis =
pointer && pointer.lastDrag && pointer.lastDrag.start.position.x < canvasDimension.left;
Also, pointer && pointer.lastDrag
can usually be collapsed like this:
const pointerOnLeftAxis =
pointer?.lastDrag && pointer.lastDrag.start.position.x < canvasDimension.left;
It still has some repetition: the pointer.lastDrag
chain. We can NOT simply say,
const pointerOnLeftAxis = pointer?.lastDrag?.start.position.x < canvasDimension.left;
in part because TypeScript prevents us from doing automatic type coercion, so only a number can be <
compared with another number (or eg. string with string); in part because a nullish comparison in JS may yield unexpected results, which is why TS prevents coercion to begin with (eg. undefined < 3 === false
but null < 3
is true, as null
gets coerced to zero (null == 0
is false though...)
However we can use a value for nullish coalescing, which will be always greater than canvasDimension.left
, avoiding the above repetition as well as the ternary:
const pointerOnLeftAxis = | |
pointer && pointer.lastDrag ? pointer.lastDrag?.start.position.x < canvasDimension.left : false; | |
const pointerOnLeftAxis = (pointer?.lastDrag?.start.position.x ?? Infinity) < canvasDimension.left; |
(Caveats, I haven't tried these TS-wise or runtime-wise, pls. mention if it doesn't work out; also, I'm not 100% sure if it's a <
or <=
predicate that's better here)
@@ -36,11 +38,14 @@ export const HighlighterCellsComponent: FC<HighlighterCellsProps> = ({ | |||
canvasDimension, | |||
brushArea, | |||
brushMask, | |||
pointer, | |||
}) => { | |||
if (!initialized || dragShape === null) return null; | |||
|
|||
const maskId = `echHighlighterMask__${chartId}`; |
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.
While in the neighborhood, I'd inline maskId
in the preexisting code even if it's not your addition in this PR: maskId
has a single use, not reused; its meaning is clear already at that place of use, (<mask id={...}>
is clearly a mask id; the variable name doesn't add info); and no need to precompute it as we may still yield null
; one fewer line, and clarity about no multiple-use variable. So, the suggestion is, <mask id={
echHighlighterMask__${chartId}}>
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.
Sounds good to me - small fix in e5ca9d9 thanks
@@ -36,11 +38,14 @@ export const HighlighterCellsComponent: FC<HighlighterCellsProps> = ({ | |||
canvasDimension, | |||
brushArea, | |||
brushMask, | |||
pointer, | |||
}) => { | |||
if (!initialized || dragShape === null) return null; |
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.
Also a preexisting code improvement opportunity: Here, we yield null
if these predicates are true. But the continuation branch also yields null
if pointerOnLeftAxis
is true. So the bail can be deleted like this:
}) => {
const pointerOnLeftAxis = (pointer?.lastDrag?.start.position.x ?? Infinity) < canvasDimension.left;
return dragShape === null || !initialized || pointerOnLeftAxis ? null : (
A possible objection is that maybe it's stupid to calculate pointerOnLeftAxis
if eg. dragShape === null
, its value won't be used. However, the way current JS engines work, it likely won't be computed, as JS engines are free to reorder operations, so most likely, it won't evaluate the pointerOnLeftAxis
expression unless needed so. It could be a different situation if the pointerOnLeftAxis
expression called some expensive function.
Also, it's such a cheap calculation even if it were done, performance is not a concern, any difference is unmeasurable.
The gain is not just fewer lines to read (us) and send to the browser (payload) but the simpler, linear start to finish flow. Unless there's a strong reason, it's nice to work with expressions rather than statements, and it's nice to have a singular return point
@@ -22,6 +23,7 @@ export interface HighlighterCellsProps { | |||
dragShape: DragShape | null; | |||
brushMask: Config['brushMask']; | |||
brushArea: Config['brushArea']; | |||
pointer: PointerStates | null; |
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.
Let's avoid adding a union type: an initial value, currently defined as null
, can be replaced with something like
pointer = { dragging: false, current: { position: { x: NaN, y: NaN}, time: -Infinity }, down: null, up: null, lastDrag: null, lastClick: null }
Yes it's a bit verbose, but then the const pointerOnLeftAxis = ...
predicate can further simplify (there's always a pointer object, etc.)
Having said that, the entirety of PointerStates
, or its props except the first 1 or 2 props could be profitably replaced with a Map
, as it is already a Map in spirit. No need for all these ternaries. Or PointerStates
props could be retained as they are, except removing the | null
ternary (initial values are trivial) or at least, not needing a null
(can be an optional value, left undefined
if unused)
@markov00 do you agree with such eventual refactoring, and in the scope of this PR, at least the avoidance of yet another union type?
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 code looks good and the PR addresses the second point of the issue: the raised event is not specific to X values.
As it's a clear impovement, and helps the requestor test the changes, I think it's a positive step, so I'm approving it.
The first half of the issue is not addressed by this PR right now: the highlighting only highlighs the first column, rather than the entire width of the highlighted rows. So please remove the "closes [issue number]" to avoid closing the issue if it gets merged without this half
Also, while the issue was filed with reference to the Y axis, it should be the same behavior for the X axis too, otherwise users will be confused.
Another observation, may or may not be a Storybook artifact: after I click on the Actions data on the right, I'm sometimes unable to highlight the approx. same area again, unless I highlight something else. It may be a bug, as it seems to happen if I highlight within the area of a previous highlight
So it means there'd be two more tasks for full closure of the issue, as well as the filing of a bug report for this selection glitch, if needed
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 code looks good and the PR addresses the second point of the issue: the raised event is not specific to X values.
As it's a clear impovement, and helps the requestor test the changes, I think it's a positive step, so I'm approving it.
The first half of the issue is not addressed by this PR right now: the highlighting only highlighs the first column, rather than the entire width of the highlighted rows. So please remove the "closes [issue number]" to avoid closing the issue if it gets merged without this half
Also, while the issue was filed with reference to the Y axis, it should be the same behavior for the X axis too, otherwise users will be confused.
Another observation, may or may not be a Storybook artifact: after I click on the Actions data on the right, I'm sometimes unable to highlight the approx. same area again, unless I highlight something else. It may be a bug, as it seems to happen if I highlight within the area of a previous highlight
So it means there'd be two more tasks for full closure of the issue, as well as the filing of a bug report for this selection glitch, if needed
nit: please reduce and simplify the title length, and move it to the description |
7148acf
to
604b153
Compare
# [35.0.0](v34.2.1...v35.0.0) (2021-09-13) ### Bug Fixes * **a11y:** restore focus after popover close with color picker ([#1272](#1272)) ([0c6f945](0c6f945)), closes [#1266](#1266) [#935](#935) * **build:** fix license in package.json ([#1362](#1362)) ([d524fdf](d524fdf)) * **deps:** update dependency @elastic/eui to ^37.5.0 ([#1341](#1341)) ([fb05c98](fb05c98)) * **deps:** update dependency @elastic/eui to ^37.6.1 ([#1359](#1359)) ([2ae90ce](2ae90ce)) * **deps:** update dependency @elastic/eui to ^37.7.0 ([#1373](#1373)) ([553b6b0](553b6b0)) * **heatmap:** filter out tooltip picked shapes in x-axis area ([#1351](#1351)) ([174047d](174047d)), closes [#1215](#1215) * **heatmap:** remove values when brushing only over axes ([#1364](#1364)) ([77ff8d3](77ff8d3)) ### Features * **annotations:** add onClickHandler for annotations ([#1293](#1293)) ([48198be](48198be)), closes [#1211](#1211) * **heatmap:** add text color contrast to heatmap cells ([#1342](#1342)) ([f9a26ef](f9a26ef)), closes [#1296](#1296) * **heatmap:** reduce font size to fit label within cells ([#1352](#1352)) ([16b5546](16b5546)) * **xy:** mutilayer time axis step 1 ([#1326](#1326)) ([867b1f5](867b1f5)) ### BREAKING CHANGES * **xy:** - feat: removes the axis deduplication feature - fix: `showDuplicatedTicks` causes a duplication check on the actual axis tick label (possibly yielded by `Axis.tickLabel` rather than the more general `tickFormat`) * **heatmap:** the `config.label.fontSize` prop is replaced by `config.label.minFontSize` and `config.label.maxFontSize`. You can specify the same value for both properties to have a fixed font size. The `config.label.align` and `config.label.baseline` props are removed from the `HeatmapConfig` object.
🎉 This PR is included in version 35.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
If a user brushes only over the y axis, x values should not be calculated in the pickedCells. If a user brushes only over the x axis, the y values should not be populated.
Details
This PR removes x values in picked cells when brushing only over y-axis and remove y values in picked cells when brushing over the x axis.
Issues
Closes #1338
Checklist
:xy
,:partition
)closes #123
,fixes #123
)