-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Maps] timeslider #96791
[Maps] timeslider #96791
Conversation
Pinging @elastic/kibana-gis (Team:Geo) |
please ignore that I assigned and then unassigned myself to this. I was just looking at it and accidentally clicked assign 🤦 😆 |
Just saw the demo video - nice work. |
@mdefazio I'm considering adding an active state. The first step was to improve the toolbar buttons here: #96913 (comment). These buttons had a lot of issues. Like, no hover states. Some buttons are not coming from EUI but from mapbox. So I tried to make both look similar. Now all have a hover state and similar styles. Then the idea is to introduce an active state for these types of buttons. The "drawing layers" buttons are also going to need. Also we have this open issue in EUI: elastic/eui#4730. Should we create a common toolbar button? 🤔 |
One other note which you may have also considered - these sorts of time scrubbers often include a small date viz drawn to scale e.g sparkline underneath the slider to allow users to see the peaks and troughs at various points. This allows users to jump directly to the periods of interest e.g. to the peak congestion and do so without scrolling through the less interesting stages trying to gauge growth from one frame to the next. |
Thanks @markharwood for all your feedback, We have different milestones. And in a future version, we considering adding a data viz. The first version is the basic one. You can find the milestones here: https://www.figma.com/proto/5QGj3sKULvEloOpVJ9YpmC/Maps-Time-Slider?page-id=31%3A277&node-id=102%3A72643&viewport=148%2C1557%2C0.5&scaling=min-zoom3
I'll take a look if there's a better option. What icon would you recommend? |
I'm trying to figuring out why the EuiDualRange doesn't fill 100% of the width. I run some experiments here: https://codesandbox.io/s/holy-rgb-rgjg2?file=/index.js. As we can see the top timeslider gets the full width and the one from the bottom doesn't. The one from the bottom uses the values calculated from It seems that the numbers being used for each tick value are not EuiDualRange friendly. @nreese, is there any possibility to use more friendly values? |
What do you mean by "more friendly values"? Can you provide some examples of what works better then the current ticks? |
You can see my experiments here: https://codesandbox.io/s/holy-rgb-rgjg2?file=/index.js. I also talked with @chandlerprall and he said is going to look into this use case. |
@elasticmachine merge upstream |
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.
really great PR.
Most comments are pretty high level for now:
- split-up timeslider in standalone UX component to make it more cut-and-pastable
- can we keep syncLayerWithMb synchronous?
- can we remove timeslice-awareness from sources?
- worth optimizing prefetching for ES-sources?
More details in line.
one bug I think:
Adding a filter (e.g. by clicking on a tooltip value) and then changing the timeslice causes the filter to get removed from the app.
@@ -77,7 +78,7 @@ export interface ILayer { | |||
ownsMbLayerId(mbLayerId: string): boolean; | |||
ownsMbSourceId(mbSourceId: string): boolean; | |||
canShowTooltip(): boolean; | |||
syncLayerWithMB(mbMap: MbMap): void; | |||
syncLayerWithMB(mbMap: MbMap, timeslice?: Timeslice): Promise<void>; |
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'm wondering if there is a way we can avoid this. Making the sync-operation async causes this asynciness to cascade. It's added complexity in the rendering, and not 100% sure if we're introducing issues.
e.g. the async should be explicitly handled here
kibana/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx
Lines 133 to 135 in c1c13c8
this._syncMbMapWithLayerList(); | |
} | |
this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap); |
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.
a similar comment wrt adding timeslice
as optional param. All the other state propagates with search-filters, timeslice being the exception.
Right now, this is because of supporting masking, so it makes sense.
But could we keep track of timeslice state separately? e.g. it's not the only piece of state that is async but needed in styling. E.g. styleMeta is managed async too, but read-out synchronously when styles update.
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 main question here imho would be: Can we make timeslice
not special, and treat it like other parts of the global state required by layers.
@@ -0,0 +1,42 @@ | |||
/* |
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.
proposing to refactor this according to the RFC.
Split up timeslider in a standalone react-component #98355, so it can be more portable and move it to maps/public/timeslider
.
Then, create a wrapper (e..g connected_component like here) that integrates it with the redux-store.
return false; | ||
} | ||
|
||
if (prevMeta.timeslice !== undefined) { |
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.
Wondering if we it's worth thinking about this slightly differently. The masking-optimization takes effects when users narrow the timeslice. This implicitly works because the timeslider initializers with the entire range selected. However, it stops working when a user narrows the slider, and then adds a filter. This newly filtered data will be bracketed to the timeslice, not the timerange.
Could we prefetch the data still?
Similar logic exits in blended-layer, which precedes its data fetch with a count. So if the timeslider is active, could/should we do something similar for document layers.
^ this will be more difficult for mvt layers, where we cannot do a prefetch-check in the same way.
return indexPattern.timeFieldName ? indexPattern.timeFieldName : null; | ||
} | ||
|
||
canMaskTimeslice(prevMeta: DataMeta, timeslice?: Timeslice): boolean { |
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.
Is there a way we can move the majority of this check to the layer-level, and have sources indicate more something like 'canPrefetchData`?
- metadata for mvt-sources about 'completeness' will be loaded quite differently, and probably not be part of the layer-state, at least in how we're thinking about mvt-metadata right now: cf. [Maps] Auto generate legends and styles from mvt data #94811
- already we need to introduce some "hacks" for a layer backfilling the metadata https://github.com/elastic/kibana/pull/96791/files#diff-e38a737f9e12768aa864d38710d91b7b809413a9c55d7d299e5595ef86198300R340
- "masking" seems a very style-centric operation, which I'm not sure we want to introduce into the source. In other words, can we remove all knowledge about timeslices from sources? (e.g. it's basically the same as
isTimeAware()
, so maybe just changegetTimesliceMaskFieldName
togetTimeFieldName
).
^ fwiw - it's hard for me now to image how #94811 and this PR will connect cleanly.
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.
It looks good. But there are a few things that we discussed that we need to clarify/implement.
1 - When we open the timeslider for the first time, the range should be set to the first time window instead of all the time extent. @kmartastic can you confirm this?
2 - When the range slider uses days, I don't think we need hours, minutes, seconds, and milliseconds. @nreese do you think we can reduce this just to use hours and minutes?
Yes. My opinion is that is the better UX. |
I like the idea of showing a simple summary at all times and high-precision while dragging the slider or during playback. What do you think? @miukimiu @nreese |
I still think the display needs to contain the complete details of what can be selected. Even when the slider ticks show days, the thumbs can select time down to the milliseconds. Users need to know if the thumbs are at "jan 1st 00:00:00" or at "jan 1st 05:30:00". Those two values are not the same and users need to see that difference. Maybe the precision can only be displayed while the user is dragging the control? |
@miukimiu |
If we're keeping all the details is better to always show all numbers. Just to show the details while dragging would introduce other issues. Like the time on the right side moving to the right when the precision numbers from the left side appear. @kmartastic I'll try your suggestions to better accommodate the time. 👍🏽 |
💚 Build SucceededMetrics [docs]Module Count
Async chunks
Page load bundle
History
To update your PR or re-run it, just comment with: |
showTicks={true} | ||
min={this.state.min} | ||
max={this.state.max} | ||
step={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.
Instead of step={1}
shouldn't we be using instead step={this.state.range}
? So that users could only move the thumbs to the ticks?
If they can move the thumbs to values in between ticks, this can happen:
Screen+Recording+2021-05-06+at+04.36+PM.mov
@nreese and @thompsongl my assumption is that the step should be step={this.state.range}
but when I try that the code fails with:
Uncaught Error: The value of 1619740800000 is not included in the possible
sequence provided by the step of 86400000
at EuiRangeTrack.validateValueIsInStep (range_track.js:84)
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'll leave it up to the Maps folks to decide how/if they want to limit selected ranges, but I have been working through step locking when it comes to the draggable highlight.
Especially when the step ticks are as far apart as they are in that video, it's very easy to get into interactions that feel off. That is, a small drag either results in no movement or movement that feels extreme and out of sync with the cursor.
Still working on it, but I think it'll be debounced so that the step only increments when you've dragged 50% of the step range.
but when I try that the code fails with
Could be something with the getTicks
function? I'd probably need to pull down the branch to see how this works.
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.
shouldn't we be using instead step={this.state.range}? So that users could only move the thumbs to the ticks?
I don't think so. The ticks are not very granular. Users will want very precise control over the selected time slice, down to minute or second or even millisecond depending on the use case. I have seen analysts working with time slider tools like these in past jobs and the time slice they want is often based on the data. They may want to narrow the time slice to show just a single document and flip back and forth between steps to play the change over and over.
Replacing with #99661. @thomasneirynck found some edge causes with client-side masking around adding filters that are going to take some thought to iron out. Decided its best to get a shippable MVP merged without client-side masking. Client-side masking can be added in future iterations. |
Implements #27714, fixes #53246
Timeslider MVP
This PR adds a timeslider to maps. Manipulating the time slider will set a new piece of redux state, timeslice, that will be used by layers to only show data for that timeslice. Sources that are aggregation based must re-fetch for the selected timeslice. Document based sources will have to re-fetch for the selected timeslice if they contain incomplete results. If the document source has complete data, then mapbox filtering will be used to only show documents in the timeslice on the client without any additional network traffic.