fix: heatmap snap domain to interval#1253
Conversation
nickofthyme
left a comment
There was a problem hiding this comment.
Looking good to me. I left a few comments below.
packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| /** @internal */ | ||
| export type UnixTimestamp = number; |
There was a problem hiding this comment.
Eelsewhere we have export type TimeMs = number; (that's for duration). So a S or Ms postfix may be useful.
It might be worth putting both this and the existing TimeMs into a common time type file (even outside /charts). The latter could perhaps be renamed DurationMs.
packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts
Outdated
Show resolved
Hide resolved
…astic-charts into 2021_07_14-heatmap_snap_time
Chromium doesn't support zone parameter
| const leftIndex = | ||
| typeof startValue === 'number' ? bisectLeft(xValues as any, startValue) : xValues.indexOf(startValue); | ||
| const rightIndex = | ||
| typeof endValue === 'number' ? bisectLeft(xValues as any, endValue) : xValues.indexOf(endValue) + 1; |
There was a problem hiding this comment.
The type of the 1st and 2nd params of bisectLeft need to match, and we're typeguarding the 2nd param to number, so it's slightly tighter to say as number[]. Though it's not truthful, maybe we could brainstorm so I can learn about whether OrdinalDomain needs to be (number | string)[] and can't be number[] | string[] eg. by coercing numbers to strings if at least one element is a string. Though there's still the lack of type coherence between xValues and startValue - probably they correlate strongly (otherwise we can't do as any or as number[] here) but this seems to be lost on TS
There was a problem hiding this comment.
If the type of bisectLeft is incorrect or too restrictive (we can't overrule it as it's beyond what the D3 TS API guarantees, but iirc these are 3rd party post hoc libs, not canonical Bostock contract) then maybe it'd be better to factor it out into our bisectLeft utility with the desired types, so we can easily change from D3 if needed. I think we could already use the also logarithmically bisecting monotonicHillClimb instead of d3.bisectLeft
There was a problem hiding this comment.
There is one main reason for having (number | string)[] as OrdinalDomain: we like to preserve the original type, coming from the user data, to return the original values on the interaction callbacks (brushing or clicking)
We can probably force it to be number[] | string[] but I don't have strong opinion here
There was a problem hiding this comment.
Some more info may strengthen the case for string[] | number[]:
D3 bisectLeft, and generally, logarithmic search requires total order, which we likely ensure by sorting the values beforehand(*).
Out of the properties of total order, transitivity is most important: if a <= b and b <= c then a <= c must be true (it's not necessarily true for partial orders, eg. when a number is defined non-comparable to a string).
(*) The mechanism doesn't matter, eg. [].sort also relies on a predicate plus array which together guarantee a total order, otherwise the order is ill defined: EcmaScript requires transitivity for predictable results: If a <CF b and b <CF c, then a <CF c (transitivity of <CF).
Sorting (number | string)[] with a < predicate doesn't lead to total order. Example:
a = '2'
b = 2.5
c = '10'
a < b: true
b < c: true
a < c: false
So a sensible option is to string-compare all elements if at least one element in the array is not a number, which is the default sort predicate (and number-compare for all-numeric arrays, which is not the default sort predicate). Instead of checking for this at the place of sorting, we may as well come clean and convert upfront. As you suggested Marco, the original value can still be retained. The effect of the conversion though is that a user-supplied [5, '5'] will map to ['5', '5'] so an equally good method is, documenting in the API, perhaps even enforcing via TS, that the domain values be homogeneous.
There can of course be other sorting rules, eg. all strings are moved to the end (tiebreaker rule for when a string and a number are compared), and within both the strings and numbers, there's full order
monfera
left a comment
There was a problem hiding this comment.
LGTM in general, there's an as yet unexpected mock change, and maybe we could do the bisecting differently. Glad to add a bisectLeft-like thin wrapper of the lower level monotonicHillClimb function we already have for logarithmic search in an array
| switch (interval.unit) { | ||
| case 'minute': | ||
| case 'm': | ||
| return 'minutes'; | ||
| case 'hour': | ||
| case 'h': | ||
| return 'hour'; | ||
| case 'day': | ||
| case 'd': | ||
| return 'day'; | ||
| case 'week': | ||
| case 'w': | ||
| return 'week'; | ||
| case 'month': | ||
| case 'M': | ||
| return 'month'; | ||
| case 'quarter': | ||
| case 'q': | ||
| return 'quarter'; | ||
| case 'year': | ||
| case 'y': | ||
| return 'year'; |
There was a problem hiding this comment.
As it's quite a long switch, requiring up to 14 comparisons, it'd be a bit nicer if these were in a map, eg.
const esCalendarIntervalsToMoment = {
minute: 'minutes',
m: 'minutes',
hour: 'hour',
h: 'hour',
...
}
...
const whatever = esCalendarIntervalsToMoment[interval.unit];
nickofthyme
left a comment
There was a problem hiding this comment.
LGTM, still not entirely sure what the end game is having multiple time libraries.
| default: | ||
| return 'hour'; | ||
| } |
There was a problem hiding this comment.
nit
| default: | |
| return 'hour'; | |
| } | |
| case 'hour': | |
| case 'h': | |
| default: | |
| return 'hour'; | |
| } |
There was a problem hiding this comment.
I've update the logic to use a map instead of a switch as proposed by robert
| // NOTE: to switch implementation just change the imported file (moment,luxon) | ||
| import { | ||
| addTimeToObj, | ||
| timeObjToUnixTimestamp, | ||
| startTimeOfObj, | ||
| endTimeOfObj, | ||
| timeObjFromAny, | ||
| timeObjToUTCOffset, | ||
| subtractTimeToObj, | ||
| formatTimeObj, | ||
| diffTimeObjs, | ||
| } from './moment'; |
There was a problem hiding this comment.
I'm really not sure why this is needed as this is not configurable from outside of charts.
As it is we use moment across the charts code so are you thinking of being able to swap time libraries entirely? In that case we could have optional dependencies on moment and luxon so the user can switch between them.
If not is this to only be used as a utility to handle different time types?
There was a problem hiding this comment.
This was done in the hope we can switch soon and easily to luxon or to Temporal https://tc39.es/proposal-temporal/docs/index.html
It really depends on Kibana right now, so extracting everything like that can help us migrating easily
…astic-charts into 2021_07_14-heatmap_snap_time
# [33.2.0](v33.1.0...v33.2.0) (2021-08-06) ### Bug Fixes * heatmap snap domain to interval ([#1253](#1253)) ([b439182](b439182)), closes [#1165](#1165) * hex colors to allow alpha channel ([#1274](#1274)) ([03b4f42](03b4f42)) ### Features * **bullet:** the tooltip shows up around the drawn part of the chart only ([#1278](#1278)) ([a96cbb4](a96cbb4)) * **legend:** multiline labels with maxLines option ([#1285](#1285)) ([e0eb096](e0eb096))
Summary
Heatmaps with a time scale on the X-axis now adjust the rendered time range to fully cover the edges when a custom domain is used.
We also took this opportunity to clean and abstract the code used for computing and handling date and time.
The library is now able to abstract from the underlying implementation library (moment or luxon at the moment), allowing us to experiment and work with diverse libraries removing some tech debt.
Jul-20-2021.16-14-24.mp4
Details
I've abstracted some time logic as asked by @monfera taking inspiration(copying) from what was done in the timeslip code.
I've also introduced some specific Elasticsearch types like the CalendarInterval and FixedInterval. I've made them ES specific as the concept and the snapping logic applies the same strategy as the one described in https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html
Issues
fix #1165
Checklist
packages/charts/src/index.ts(and stories only import from../srcexcept for test data & storybook)