@spider-analyzer/timeline 4.0.3 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +80 -1
  2. package/README.md +275 -637
  3. package/dist/index.d.mts +132 -0
  4. package/dist/index.d.ts +132 -0
  5. package/dist/index.js +2913 -22
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +2906 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/timeline.css +139 -0
  10. package/package.json +52 -15
  11. package/src/Cursor.jsx +5 -13
  12. package/src/TimeLine.tsx +994 -0
  13. package/src/TimeLineResizer.jsx +2 -8
  14. package/src/ToolTip.jsx +7 -7
  15. package/src/cursorElements/CursorIcon.jsx +6 -29
  16. package/src/cursorElements/CursorSelection.jsx +4 -19
  17. package/src/cursorElements/DragOverlay.jsx +2 -12
  18. package/src/cursorElements/LeftHandle.jsx +3 -19
  19. package/src/cursorElements/LeftToolTip.jsx +2 -7
  20. package/src/cursorElements/RightHandle.jsx +3 -19
  21. package/src/cursorElements/RightToolTip.jsx +4 -13
  22. package/src/cursorElements/ZoomIn.jsx +5 -25
  23. package/src/cursorElements/ZoomOut.jsx +4 -21
  24. package/src/cursorElements/utils.js +1 -1
  25. package/src/index.js +6 -0
  26. package/src/index.ts +158 -0
  27. package/src/moment-shim.ts +169 -0
  28. package/src/styles.ts +15 -0
  29. package/src/time.ts +52 -0
  30. package/src/timeLineElements/Button.jsx +5 -30
  31. package/src/timeLineElements/HistoToolTip.jsx +3 -17
  32. package/src/timeLineElements/Histogram.jsx +4 -16
  33. package/src/timeLineElements/Legend.jsx +2 -16
  34. package/src/timeLineElements/QualityLine.jsx +4 -11
  35. package/src/timeLineElements/Tools.jsx +1 -1
  36. package/src/timeLineElements/XAxis.jsx +5 -8
  37. package/src/timeLineElements/XGrid.jsx +3 -7
  38. package/src/timeLineElements/YAxis.jsx +4 -7
  39. package/src/timeLineElements/YGrid.jsx +2 -6
  40. package/src/timeLineElements/axesStyles.jsx +0 -49
  41. package/src/timeline.css +139 -0
  42. package/src/utils.ts +60 -0
  43. package/.babelrc +0 -8
  44. package/.gitlab-ci.yml +0 -27
  45. package/Makefile +0 -20
  46. package/dist/Cursor.js +0 -290
  47. package/dist/TimeLine.js +0 -1177
  48. package/dist/TimeLineResizer.js +0 -70
  49. package/dist/ToolTip.js +0 -43
  50. package/dist/cursorElements/CursorIcon.js +0 -98
  51. package/dist/cursorElements/CursorSelection.js +0 -179
  52. package/dist/cursorElements/DragOverlay.js +0 -168
  53. package/dist/cursorElements/LeftHandle.js +0 -95
  54. package/dist/cursorElements/LeftToolTip.js +0 -70
  55. package/dist/cursorElements/RightHandle.js +0 -95
  56. package/dist/cursorElements/RightToolTip.js +0 -75
  57. package/dist/cursorElements/ZoomIn.js +0 -93
  58. package/dist/cursorElements/ZoomOut.js +0 -67
  59. package/dist/cursorElements/commonStyles.js +0 -28
  60. package/dist/cursorElements/handleHistoHovering.js +0 -79
  61. package/dist/cursorElements/utils.js +0 -30
  62. package/dist/theme.js +0 -59
  63. package/dist/timeLineElements/Button.js +0 -101
  64. package/dist/timeLineElements/HistoToolTip.js +0 -78
  65. package/dist/timeLineElements/Histogram.js +0 -110
  66. package/dist/timeLineElements/Legend.js +0 -70
  67. package/dist/timeLineElements/QualityLine.js +0 -81
  68. package/dist/timeLineElements/Tools.js +0 -115
  69. package/dist/timeLineElements/XAxis.js +0 -76
  70. package/dist/timeLineElements/XGrid.js +0 -47
  71. package/dist/timeLineElements/YAxis.js +0 -60
  72. package/dist/timeLineElements/YGrid.js +0 -46
  73. package/dist/timeLineElements/axesStyles.js +0 -57
  74. package/src/TimeLine.jsx +0 -1163
  75. package/src/cursorElements/commonStyles.js +0 -21
package/README.md CHANGED
@@ -1,739 +1,377 @@
1
1
  # TimeLine
2
2
 
3
- React graphical component to display metric over time with a time selection feature.
3
+ React graphical component to display metrics over time with an interactive
4
+ time-selection cursor, zoom, pan, and an optional quality line.
5
+
4
6
  - Drag & pan to shift time
5
- - Scroll to zoom time resolution
6
- - Drag to move, resize or redraw time selection
7
- - Fine tuning for intuitive use
8
- - Customizable styling and tools
7
+ - Scroll to zoom
8
+ - Drag to move, resize or redraw the time selection
9
+ - Built-in zoom-out undo (no host-managed stack required)
10
+ - Themable through plain CSS classes — no CSS-in-JS peer dep
9
11
 
10
12
  Live example: https://timeline.oss.spider-analyzer.io
11
13
 
12
14
  ![alt text][example]
13
-
14
15
  ![alt text][styled]
15
16
 
16
17
  ## Content
18
+
17
19
  - [Features](#features)
18
- * [Displaying metrics](#displaying-metrics)
19
- * [Quality line](#quality-line)
20
- * [Selecting time](#selecting-time)
21
- * [Zooming](#zooming)
22
- * [Dragging the domain](#dragging-the-domain)
23
- * [Buttons](#buttons)
24
- * [Limits when refreshing](#limits-when-refreshing)
25
20
  - [Design considerations](#design-considerations)
26
- - [Integration](#integration)
21
+ - [Installation](#installation)
22
+ - [Integration example](#integration-example)
27
23
  - [Props](#props)
28
- * [className](#classname)
29
- * [classes](#classes)
30
- * [rcToolTipPrefixCls](#rctooltipprefixcls)
31
- * [margin](#margin)
32
- * [xAxis](#xaxis)
33
- * [yAxis](#yaxis)
34
- * [timeSpan](#timespan)
35
- * [histo](#histo)
36
- * [showHistoToolTip](#showhistotooltip)
37
- * [HistoToolTip](#HistoToolTip)
38
- * [quality](#quality)
39
- * [qualityScale](#qualityscale)
40
- * [zoomOutFactor](#zoomoutfactor)
41
- * [domains](#domains)
42
- * [maxDomain](#maxdomain)
43
- * [metricsDefinition](#metricsdefinition)
44
- * [biggestVisibleDomain](#biggestvisibledomain)
45
- * [biggestTimeSpan](#biggesttimespan)
46
- * [smallestResolution](#smallestresolution)
47
- * [labels](#labels)
48
- * [showLegend](#showlegend)
49
- * [tools](#tools)
50
- * [fetchWhileSliding](#fetchwhilesliding)
51
- * [selectBarOnClick](#selectbaronclick)
52
- - [Actions](#actions)
53
- * [onLoadDefaultDomain()](#onloaddefaultdomain)
54
- * [onLoadHisto(intervalMs: number, start: Moment, end: Moment)](#onloadhistointervalms-number-start-moment-end-moment)
55
- * [onCustomRange(start: Moment, stop: Moment)](#oncustomrangestart-moment-stop-moment)
56
- * [onShowMessage(msg: string)](#onshowmessagemsg-string)
57
- * [onUpdateDomains(domains: arrayOf({min: Moment, max: Moment}))](#onupdatedomainsdomains-arrayofmin-moment-max-moment)
58
- * [onResetTime()](#onresettime)
59
- * [onFormatTimeToolTips(time: Moment) :string](#onformattimetooltipstime-moment-string)
60
- * [onFormatTimeLegend(time: Date) :string](#onformattimelegendtime-date-string)
61
- * [onFormatMetricLegend(value: number) :string](#onformatmetriclegendvalue-number-string)
62
- - [Public functions](#public-functions)
63
- * [zoomIn()](#zoomin)
64
- * [zoomOut()](#zoomout)
65
- * [shiftTimeLine(number)](#shifttimelinenumber)
24
+ - [Callbacks](#callbacks)
25
+ - [Imperative API](#imperative-api)
26
+ - [Styling](#styling)
27
+ - [Migrating from v4](#migrating-from-v4)
66
28
  - [Testing / Dev](#testing--dev)
67
29
  - [Dependencies](#dependencies)
68
- - [ToDo](#todo)
69
- - [Trello dashboard](#trello-dashboard)
70
30
 
71
31
  ## Features
72
32
 
73
33
  ### Displaying metrics
74
- - TimeLine displays the evolution of metric(s) through time
75
- - The time displayed has a min and max time, called hereunder a `domain`
76
- - TimeLine can display from 1 to n metrics at once
77
- - Metrics to be displayed are defined by `metricsDefinition` prop.
78
- - When several metrics are displayed, their values are stacked up
79
- - Metrics are stacked in the order of `metricsDefinition.legend[]`
80
- - Colors of the histogram bars are defined in `metricsDefinition.colors[]`
81
- - Order of metrics must be the same in `metricsDefinition.legend[]`, `metricsDefinition.colors[]` and `histo.items[].metrics[]`
82
- - Names of the metrics are defined in `metricsDefinition.legend[]`, and displayed left to the chart
83
- - The maximum value in the domain is displayed at the top of the y axis
84
- - Current time is displayed by a vertical arrow on the x axis
34
+ - TimeLine displays the evolution of metric(s) through time.
35
+ - The time displayed has a min and max time, called hereunder a `domain`.
36
+ - 1 to n stacked metrics per bar — defined by `metricsDefinition`.
37
+ - Colors of the histogram bars are defined in `metricsDefinition.colors[]`.
38
+ - Metric names are displayed to the left of the chart.
39
+ - The maximum value in the visible window is shown at the top of the y-axis.
40
+ - Current time is displayed as a vertical arrow on the x-axis.
85
41
 
86
42
  ### Quality line
87
- - Timeline may display a 'quality' line below the chart showing the quality of the metrics collection
88
- - This line may have a different x-axis granularity as the metrics
89
- - This line takes quality in input by time slot, with the quality being a number, for instance, between 0 and 100%.
90
- - A color scale renders the quality in different color depending on the value
91
- - A custom tip can be displayed for each distinct slot
43
+ - Optional secondary line below the chart showing a per-slot quality value.
44
+ - May have its own x-axis granularity, independent of the histogram.
45
+ - Custom tooltip content per slot.
92
46
 
93
47
  ### Selecting time
94
- A cursor allows to select time in the viewed domain.
95
- - The cursor has handles on its side to resize it by drag and drop
96
- - The cursor is moved backward or forward if the resizing is too small
97
- - The cursor can be moved by drag an drop
98
- - If moved outside the domain, the domain is adjusted (shifted)
99
- - The cursor displays tooltips to show start and stop of time selection
100
- - The cursor can be re-drawn by click and dragging over the chart
101
- - When clicking outside the cursor
102
- - When clicking below the cursor
48
+ - The cursor has handles to resize it via drag-and-drop.
49
+ - If dragged outside the domain, the domain shifts.
50
+ - Cursor can be redrawn by clicking and dragging over the chart.
103
51
 
104
52
  ### Zooming
105
- - TimeLine can be zoomed in (higher time resolution):
106
- - **Scroll down** with the mouse over the graph.
107
- - The TimeLine is zoomed on the x position of the mouse, by a factor 4.
108
- - **Double click** on the time selection cursor.
109
- - The TimeLine is zoomed over the selected area.
110
- - **Click** on the zoom-in icon at the top right corner of the time selection cursor.
111
- - The TimeLine is zoomed over the selected area.
112
- - TimeLine can be zoomed out:
113
- - **Scroll down** with the mouse over the graph.
114
- - **Click** on the zoom-in icon at the right corner of the x axis.
115
- - At start, zooming out is done using `zoomOutFactor`
116
- - Once zoomed in, zooming out reverts the last zoom level
53
+ - **Scroll** zooms on the mouse position (factor 4 on zoom-in).
54
+ - **Double-click** on the cursor — zooms over the selection.
55
+ - **Zoom-in/out icons** on the cursor / x-axis.
56
+ - **Zoom-out undo** zooming out after zooming in returns to the exact
57
+ previous view (internal breadcrumb, transparent to the host).
117
58
 
118
59
  Limits:
119
- - Zoom-in is possible until 15 pixels = `smallestResolution`
120
- - Zoom-out is possible until visible domain on the timeline = `biggestVisibleDomain`
121
-
60
+ - Zoom-in stops at 15 px = `smallestResolution`.
61
+ - Zoom-out stops at `biggestVisibleDomain`.
62
+
122
63
  ### Dragging the domain
123
- Time domain can be dragged forward or backward by pressing ctrl and dragging the mouse on the chart.
124
- - If a `maxDomain` limit is defined, the timeline cannot display dates outside this domain
125
- - The zoom levels above this one are adjusted if the min/max gets beyond their limits. For better U/X.
64
+ - `Ctrl` + drag shifts the visible window.
65
+ - Respects `maxDomain` if set.
126
66
 
127
67
  ### Buttons
128
- - On each side of the x axis, the double arrow icons allow sliding the domain forward or backward
129
- - On the right of the chart, two icons allow to:
130
- - Reset the chart: getting back to initial zoom level.
131
- - Goto now: Move the domain and time selection to select current time.
68
+ - Double arrow icons on either side of the x-axis slide the domain.
69
+ - Right-side icons: reset, goto-now.
132
70
 
133
- ### Limits when refreshing
134
- - Zooming is not possible while a data refresh is in progress. To avoid too many calls to the API at once.
135
- - When resizing the TimeLine, it will ask for data refresh only each 30 pixels resize. Thus avoiding many calls to the API.
71
+ ### Data refresh limits
72
+ - Zoom is disabled while a fetch is in progress.
73
+ - Resize triggers a refresh only every 30 px avoids flooding the API.
136
74
 
137
75
  ## Design considerations
138
- TimeLine is design for integration in time series or operational data reporting and display.
139
- It is perfectly suited for integration aside a grid of records to define the time range of records to display.
140
-
141
- When integrating the TimeLine in your project, you have to define:
142
- - The metrics to display, their colors and legend: `metricsDefinition`
143
- - The default domain to display on load: `domains[0]` loaded by `onLoadDefaultDomain`
144
- - The absolute minimum and maximum time to display (optional): `maxDomain`
145
- - The limit in duration of a visible domain (optional): `biggestVisibleDomain`
146
- - The smallest resolution unit of the time displayed: `smallestResolution`
147
- - What to do when resetting time: `onResetTime`.
148
- - Default expected behavior would be to execute `onLoadDefaultDomain` and reset the domains.
149
- - How to display time:
150
- - On x axis: `onFormatTimeLegend`
151
- - In tooltips: `onFormatTimeToolTips`
152
- - How to display metric value on y axis: `onFormatMetricLegend`
153
- - If selected time should be rounded.
154
-
155
- Default resolution is millisecond, the time may rounded outside TimeLine component to second, minute or so.
156
- - To do this, adjust `onCustomRange`, `onLoadDefaultDomain`, `onLoadHisto` functions, as shown in demo.
157
- - `smallestResolution` and `onFormatTimeToolTips` should be adjusted in consequence.
158
-
159
- You may also redefine labels displayed: `labels`
160
-
161
- ## Integration
162
- Install using npm or your favorite tool.
163
76
 
164
- `npm install --save @spider-analyzer/timeline`
77
+ TimeLine is designed for integration into time-series and operational
78
+ reporting UIs. It pairs naturally with a grid of records, using the
79
+ cursor to select the time range of interest.
165
80
 
166
- Include in your react application.
81
+ When integrating, you define:
167
82
 
168
- **Warning:**
169
- Current version requires some props to be Moment.js objects. So you would need Moment in your own application.
170
- You can reduce webpack bundle when using webpack, as timeline lib is not using any locale of Moment.js.
171
- See: https://github.com/jmblog/how-to-optimize-momentjs-with-webpack.
83
+ - Metrics to display + their colors and legend: `metricsDefinition`.
84
+ - The initial window: return it from `onLoadDefaultDomain`, or pass it
85
+ directly as `domain`.
86
+ - Optional outer bounds: `maxDomain`.
87
+ - Zoom limits: `biggestVisibleDomain`, `smallestResolution`.
88
+ - Reset behavior: `onResetTime` (typically clears `domain` so the
89
+ component re-asks for a default).
90
+ - Formatters for x-axis, tooltips, and metric values.
91
+ - Required time zone (IANA name): `timeZone`.
172
92
 
93
+ ## Installation
173
94
 
174
- ```js
175
- <TimeLine
176
- className={'timeLine'}
177
- timeSpan = {this.state.timeSpan} //start and stop of selection
178
- histo = {{
179
- items: this.state.items, // table of histo columns: [{time: moment, metrics: [metric1 value, metric2 value...], total: sum of metrics}, ...]
180
- intervalMs: this.state.intervalMs //interval duration of column in ms
181
- }}
182
- showHistoToolTip
183
- quality = {{
184
- items: this.state.quality, //table of quality indicators [{time: moment, quality: float [0,1], tip: string}]
185
- intervalMin: this.state.intervalMin //interval duration of column in Min
186
- }}
187
- qualityScale = {
188
- scaleLinear()
189
- .domain([0,1])
190
- .range(['red','green'])
191
- .clamp(true)
192
- } //color scale (optional)
193
- zoomOutFactor = {1.75} //zoom out factor if never zoomed in
194
- domains = {this.state.domains} //array of zoom levels
195
- maxDomain = {this.state.maxDomain} //max zoom level allowed
196
- metricsDefinition = {metricsDefinition}
197
- biggestVisibleDomain = {moment.duration('P1M')} //maximum visible duration, cannot zoom out further
198
- biggestTimeSpan = {moment.duration('P1D')} //maximum duration that can be selected
199
- smallestResolution = {moment.duration('PT1M')} //max zoom level: 15pixels = duration
200
- labels={{
201
- backwardButtonTip: 'Slide into the past'
202
- }}
203
- tools={{
204
- gotoNow: false
205
- }}
206
- showLegend
207
- fetchWhileSliding
208
- selectBarOnClick
209
-
210
- onLoadDefaultDomain = {this.onLoadDefaultDomain} //called on mount to get the default domain
211
- onLoadHisto = {this.onLoadHisto} //called to load items. give the needed interval, computed from props.width, props.domains[0]
212
- onCustomRange = {this.onCustomRange} //called when user has drawn or resized the cursor
213
- onShowMessage = {console.log} //called to display an error message
214
- onUpdateDomains = {this.onUpdateDomains} //called to save domains
215
- onResetTime = {this.onResetTime} //called when user want to reset timeline
216
- onFormatTimeToolTips = {this.onFormatTimeToolTips} //called to display time in tooltips
217
- onFormatTimeLegend = {multiFormat} //called to format x axis legend
218
- onFormatMetricLegend = {formatNumber} //called to format y axis metric legend
219
- />
95
+ ```sh
96
+ npm install --save @spider-analyzer/timeline
220
97
  ```
221
98
 
222
- TimeLine component is a controlled component.
223
-
224
- ## Props
225
- ### className
226
- Main class used in the `<div/>` encapsulating the svg graphic.
227
- The width and height of the chart should be defined there. In % or strict dimensions.
228
- When resizing the window, the chart will adapt.
229
-
230
- ### classes
231
- Allows to override any classes used by the component.
232
-
233
- To be used by a CSS-in-JS solution, are by listing classes from your own CSS.
234
- To find the classes to update... best is to play with developer tools ;)
235
-
236
- ### rcToolTipPrefixCls
237
- Allows changing the class prefix for rc-tooltip
99
+ Import the stylesheet once in your entry:
238
100
 
239
- ### margin
240
- Allows to adjust the margin around the graphic area.
241
-
242
- The metrics legend, and time legend are rendered inside the margin.
243
-
244
- ```js
245
- margin: PropTypes.shape({
246
- left: PropTypes.number,
247
- right: PropTypes.number,
248
- top: PropTypes.number,
249
- bottom: PropTypes.number,
250
- })
251
- ```
252
-
253
- ### xAxis
254
- Allows customising the xAxis (time):
255
- - arrowPath: SVG path of the arrow
256
- - spaceBetweenTicks: How many pixels between two points in the x grid / legend
257
- - barsBetweenTicks: How many histogram bar do you want between ticks
258
- - showGrid: Displays vertical lines for each tick (default: no)
259
- - height: Height of the axis (useful if you change the text style ;))
260
-
261
- spaceBetweenTicks and spaceBetweenTicks are used to compute the resolution 'interval' of the onLoadHisto function.
262
-
263
- ```js
264
- xAxis: PropTypes.shape({
265
- arrowPath: PropTypes.string,
266
- spaceBetweenTicks: PropTypes.number.isRequired,
267
- barsBetweenTicks: PropTypes.number.isRequired,
268
- showGrid: PropTypes.bool,
269
- height: PropTypes.number,
270
- })
271
- ```
272
-
273
- ### yAxis
274
- Allows customising the yAxis (metrics):
275
- - arrowPath: SVG path of the arrow
276
- - spaceBetweenTicks: How many pixels between two points in the x grid / legend
277
- - showGrid: Displays horizontal lines for each tick (default: no)
278
-
279
- ```js
280
- yAxis: PropTypes.shape({
281
- path: PropTypes.string,
282
- spaceBetweenTicks: PropTypes.number.isRequired,
283
- showGrid: PropTypes.bool,
284
- })
285
- ```
286
-
287
- ### timeSpan
288
- Defines the start and stop of the selected time window.
289
- ```js
290
- timeSpan: PropTypes.shape({
291
- start: PropTypes.instanceOf(moment).isRequired,
292
- stop: PropTypes.instanceOf(moment).isRequired
293
- }).isRequired
294
- ```
295
- Ex:
296
101
  ```js
297
- timeSpan = {
298
- start: moment().subtract(1, 'HOUR'),
299
- stop: moment().add(1, 'HOUR'),
300
- }
102
+ import '@spider-analyzer/timeline/dist/timeline.css';
103
+ import '@spider-analyzer/timeline/dist/tipDark.css';
301
104
  ```
302
105
 
303
- ### histo
304
- Provides the data to display.
305
-
306
- /!\ items and intervalMs have to be provided in sync.
307
-
308
- When loading new items when processing [onLoadHisto](#onloadhistointervalms-number-start-moment-end-moment), both intervalMs and items must be
309
- given back together. That's why they are in same prop.
310
- If intervalMs is not consistent with items own duration 'interval', then you'll have an ugly
311
- glitch when zooming out and resizing. And wrong information display when zooming in.
106
+ No moment / MUI / lodash peer deps. `luxon` is declared as a
107
+ dependency but is already on most modern React apps; if not, npm will
108
+ pull it in transitively.
312
109
 
313
- So, please do not keep intervalMs in an intermediate state with a closer update loop
314
- than the loading request itself.
110
+ ## Integration example
315
111
 
316
- ```js
317
- histo: PropTypes.shape({
318
- items: PropTypes.arrayOf(PropTypes.shape({
319
- time: PropTypes.instanceOf(moment).isRequired, //time of histogram bar
320
- metrics: PropTypes.arrayOf(PropTypes.number).isRequired, //array of values
321
- total: PropTypes.number.isRequired, //total of values of the array
322
- })),
323
- intervalMs: PropTypes.number //interval of each bar
324
- }).isRequired
112
+ ```jsx
113
+ <TimeLine
114
+ className="my-timeline"
115
+ timeZone="Europe/Paris" /* required */
116
+ timeSpan={this.state.timeSpan} /* { start: Date, stop: Date } */
117
+ histo={{
118
+ items: this.state.items, /* [{ time: Date, metrics: number[], total: number }] */
119
+ intervalMs: this.state.intervalMs, /* number */
120
+ }}
121
+ showHistoToolTip
122
+ quality={{
123
+ items: this.state.quality, /* [{ time: Date, quality: 0..1, tip?: ReactNode }] */
124
+ intervalMin: this.state.intervalMin,
125
+ }}
126
+ zoomOutFactor={1.75}
127
+ domain={this.state.domain} /* { min: Date, max: Date } | null */
128
+ maxDomain={this.state.maxDomain}
129
+ metricsDefinition={metricsDefinition}
130
+ biggestVisibleDomain={30 * 24 * 60 * 60 * 1000} /* 1 month in ms */
131
+ biggestTimeSpan={24 * 60 * 60 * 1000} /* 1 day in ms */
132
+ smallestResolution={60 * 1000} /* 1 minute in ms */
133
+ labels={{ backwardButtonTip: 'Slide into the past' }}
134
+ tools={{ gotoNow: false }}
135
+ showLegend
136
+ fetchWhileSliding
137
+ selectBarOnClick
138
+
139
+ onLoadDefaultDomain={this.onLoadDefaultDomain} /* returns Domain | Promise<Domain> */
140
+ onLoadHisto={this.onLoadHisto} /* ({ intervalMs, start, end }) */
141
+ onTimeSpanChange={this.onTimeSpanChange} /* ({ start, stop }) */
142
+ onShowMessage={console.log}
143
+ onDomainChange={this.onDomainChange} /* (domain) */
144
+ onResetTime={this.onResetTime}
145
+ onFormatTimeToolTips={this.onFormatTimeToolTips}
146
+ onFormatTimeLegend={multiFormat}
147
+ onFormatMetricLegend={formatNumber}
148
+ />
325
149
  ```
326
150
 
327
- ### showHistoToolTip
328
- When true, timeline will display a tooltip when hover the histogram stacked bars.
329
-
330
- The tooltip lists the time slot and the different metrics values for the bar.
331
- It may be customized by providing a custom `HistoToolTip` component.
151
+ TimeLine is a controlled component.
332
152
 
333
- ```js
334
- showHistoToolTip: PropTypes.bool
335
- ```
336
-
337
- ### HistoToolTip
338
- React component to replace and customize the histogram tooltips content.
153
+ ## Props
339
154
 
340
- Provided props:
341
- ```js
342
- HistoTooltip.propTypes = {
343
- classes: PropTypes.object, // classes you gave in input of TimeLine
344
- item: PropTypes.shape({ // histogram bar
345
- start: PropTypes.instanceOf(moment), // start of the bar
346
- end: PropTypes.instanceOf(moment), // end of the bar
347
- x1: PropTypes.number, // start position in x axis
348
- x2: PropTypes.number, // start position in y axis
349
- metrics: PropTypes.arrayOf(PropTypes.number), // as provided in histo prop
350
- total: PropTypes.number, // total of the metrics bar
351
- }),
352
- metricsDefinition: PropTypes.shape().isRequired, // prop you gave
353
- onFormatTimeToolTips: PropTypes.func.isRequired, // prop you gave
354
- onFormatMetricLegend: PropTypes.func.isRequired, // prop you gave
155
+ | Prop | Type | Required | Notes |
156
+ |------------------------|-------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------------------------|
157
+ | `timeZone` | `string` (IANA) | yes | Threads into the internal tz-aware time engine for formatting. |
158
+ | `domain` | `{ min: Date, max: Date } \| null` | yes | Pass `null` on first render to defer to `onLoadDefaultDomain`. |
159
+ | `timeSpan` | `{ start: Date, stop: Date }` | yes | The cursor selection. |
160
+ | `histo` | `{ items: HistoItem[], intervalMs: number \| null }` | yes | `items[].time` is `Date`. `items` and `intervalMs` MUST be updated atomically. |
161
+ | `metricsDefinition` | `{ count, legends, colors }` | yes | See example below. |
162
+ | `maxDomain` | `{ min?: Date, max?: Date }` | no | Hard outer bounds for pan. |
163
+ | `biggestVisibleDomain` | `number` (ms) | no | Max visible window; caps zoom-out. |
164
+ | `biggestTimeSpan` | `number` (ms) | no | Max selectable cursor span. |
165
+ | `smallestResolution` | `number` (ms) | yes | Floor for zoom-in (15 px == this duration). |
166
+ | `quality` | `{ items: QualityItem[], intervalMin?: number }` | no | Optional quality line below the chart. |
167
+ | `qualityScale` | `(q: number) => string` | no | Color for a quality value. Defaults to a red→green scale. |
168
+ | `zoomOutFactor` | `number` | no | Default 1.25. Used only when the internal breadcrumb is empty. |
169
+ | `showHistoToolTip` | `boolean` | no | |
170
+ | `HistoToolTip` | React component | no | Custom tooltip content — see below. |
171
+ | `className` | `string` | no | Applied to the outer container. |
172
+ | `classes` | `Record<slot, string>` | no | Override per-slot class names. See [Styling](#styling). |
173
+ | `rcToolTipPrefixCls` | `string` | no | Override rc-tooltip class prefix. |
174
+ | `margin` | `{ left?, right?, top?, bottom? }` numbers | no | |
175
+ | `xAxis`, `yAxis` | axis config objects (see below) | no | |
176
+ | `tools` | `{ slideForward?, slideBackward?, resetTimeline?, gotoNow?, cursor?, zoomIn?, zoomOut? }` | no | Toggle individual tools. |
177
+ | `fetchWhileSliding` | `boolean` | no | Refetch while panning. |
178
+ | `selectBarOnClick` | `boolean` | no | Clicking a histogram bar snaps the cursor to it. |
179
+ | `labels` | `Record<string, string>` | no | Translations / message overrides. |
180
+ | `showLegend` | `boolean` | no | Defaults to `true`. |
181
+
182
+ `xAxis` / `yAxis`:
183
+
184
+ ```ts
185
+ xAxis?: {
186
+ arrowPath?: string;
187
+ spaceBetweenTicks: number;
188
+ barsBetweenTicks: number;
189
+ showGrid?: boolean;
190
+ height?: number;
191
+ };
192
+ yAxis?: {
193
+ arrowPath?: string;
194
+ spaceBetweenTicks?: number;
195
+ showGrid?: boolean;
355
196
  };
356
197
  ```
357
198
 
358
- ### quality
359
- Provides the data to display on the quality line.
360
- ```js
361
- quality: PropTypes.shape({
362
- items: PropTypes.arrayOf(PropTypes.shape({
363
- time: PropTypes.instanceOf(moment).isRequired, //time of quality slot
364
- quality: PropTypes.number.isRequired, //quality of the slot
365
- tip: PropTypes.node, //text to display in tooltip - optional
366
- })),
367
- intervalMin: PropTypes.number //duration of each slot (in minutes)
368
- })
369
- ```
370
-
371
- ### qualityScale
372
- Allows to override the color scale for the quality line.
373
- Expects a function converting a quality number into a CSS color.
374
- ```js
375
- qualityScale: PropTypes.func
376
- ```
377
-
378
- ### zoomOutFactor
379
- Allows to override the default zoom factor (1.25) for zooming out
380
- when never zoomed in before.
381
-
382
- Should be > 1 ;)
383
-
384
- ```js
385
- zoomOutFactor: PropTypes.number
386
- ```
387
-
388
- ### domains
389
- Stores/defines the actual zooms levels of the timeline.
390
- ```js
391
- domains: PropTypes.arrayOf(PropTypes.shape({
392
- min: PropTypes.instanceOf(moment).isRequired,
393
- max: PropTypes.instanceOf(moment).isRequired
394
- })).isRequired
395
- ```
396
- - min and max are the visual bounds of the TimeLine
397
- - When zooming in/out, `onUpdateDomains` is called with an update of the domains.
398
- - On mount, the timeline calls `onLoadDefaultDomain` that should be used to define the initial domain.
199
+ `metricsDefinition`:
399
200
 
400
- Ex:
401
201
  ```js
402
- domains = [{
403
- min: moment().subtract(1, 'WEEK').startOf('DAY'),
404
- max: moment().endOf('DAY')
405
- }]
406
- ```
407
-
408
- ### maxDomain
409
- May/should specify a maximum domain that will set min and max bounds when shifting the TimeLine.
410
- ```js
411
- maxDomain: PropTypes.shape({
412
- min: PropTypes.instanceOf(moment).isRequired,
413
- max: PropTypes.instanceOf(moment).isRequired
414
- })
415
- ```
416
- Ex:
417
- ```js
418
- maxDomain = {
419
- min: moment().subtract(2, 'MONTHS').startOf('DAY'),
420
- max: moment().add(1, 'WEEK').endOf('DAY')
202
+ {
203
+ count: 3,
204
+ legends: ['Info', 'Warn', 'Fail'],
205
+ colors: [
206
+ { fill: '#9be18c', stroke: '#5db352', text: '#5db352' },
207
+ { fill: '#f6bc62', stroke: '#e69825', text: '#e69825' },
208
+ { fill: '#ff5d5a', stroke: '#f6251e', text: '#f6251e' },
209
+ ],
421
210
  }
422
211
  ```
423
212
 
424
- ### metricsDefinition
425
- Defines the metrics that will be displayed on the chart: count, legend, formatting
426
- ```js
427
- metricsDefinition: PropTypes.shape({
428
- count: PropTypes.number.isRequired,
429
- legends: PropTypes.arrayOf(PropTypes.string).isRequired,
430
- colors: PropTypes.arrayOf(PropTypes.shape({
431
- fill: PropTypes.string.isRequired,
432
- stroke: PropTypes.string.isRequired,
433
- text: PropTypes.string.isRequired,
434
- })).isRequired
435
- }).isRequired
436
- ```
437
- Ex:
438
- ```js
439
- metricsDefinition = {
440
- count: 3, //Count of metric in the graphic
441
- legends: ['Info', 'Warn', 'Fail'], //Name of the metrics, in order. Will be displayed left of the chart
442
- colors: [{ //Colors of the metrics, in order: fill of bar, stroke of bar, text in legend
443
- fill: '#9be18c',
444
- stroke: '#5db352',
445
- text: '#5db352'
446
- },
447
- {
448
- fill: '#f6bc62',
449
- stroke: '#e69825',
450
- text: '#e69825'
451
- },{
452
- fill: '#ff5d5a',
453
- stroke: '#f6251e',
454
- text: '#f6251e'
455
- }]
456
- }
213
+ Custom `HistoToolTip` props:
214
+
215
+ ```ts
216
+ HistoToolTip.propTypes = {
217
+ classes: object,
218
+ item: {
219
+ start: Date,
220
+ end: Date,
221
+ x1: number,
222
+ x2: number,
223
+ metrics: number[],
224
+ total: number,
225
+ },
226
+ metricsDefinition: object,
227
+ onFormatTimeToolTips: (time: Date) => ReactNode,
228
+ onFormatMetricLegend: (value: number) => string,
229
+ };
457
230
  ```
458
231
 
459
- ### biggestVisibleDomain
460
- Defines the maximum visible duration of a domain, if any.
461
- For instance, allows set a maxDomain of 1 year, but limit visible histogram to a window of 1 month.
462
- Limits the overloading of the aggregation API.
463
- ```js
464
- biggestVisibleDomain: PropTypes.object //expects a Duration created by moment.duration() object
465
- ```
466
- Ex:
467
- ```js
468
- biggestVisibleDomain = moment.duration('P1M')
469
- ```
232
+ ## Callbacks
470
233
 
471
- ### biggestTimeSpan
472
- Defines the maximum duration that can be selected, if any.
473
- ```js
474
- biggestTimeSpan: PropTypes.object //expects a Duration created by moment.duration() object
475
- ```
476
- Ex:
477
- ```js
478
- biggestTimeSpan = moment.duration('P1D')
479
- ```
234
+ ### `onLoadDefaultDomain(): Domain | Promise<Domain> | void`
235
+ Called on mount when `domain` is `null`. Return the default domain
236
+ synchronously or asynchronously; the component seeds its internal stack
237
+ from the returned value and emits `onDomainChange` once resolved.
480
238
 
239
+ ### `onLoadHisto({ intervalMs, start, end }): void`
240
+ Called when the component needs histogram data. Fires on mount (if the
241
+ domain is known), on domain changes, on width changes, and on pan if
242
+ `fetchWhileSliding` is set.
481
243
 
482
- ### smallestResolution
483
- Defines the smallest zoom resolution to display (for 15 pixels).
484
- ```js
485
- smallestResolution: PropTypes.object.isRequired //expects a Duration created by moment.duration() object
486
- ```
487
- Ex:
488
- ```js
489
- smallestResolution = moment.duration('PT1M')
490
- ```
244
+ ### `onTimeSpanChange({ start, stop }): void`
245
+ Called when the user resizes, moves, or redraws the cursor.
491
246
 
492
- ### labels
493
- Overrides labels to display for ToolTips and onShowMessage calls.
494
- Provided for translation.
495
- ```js
496
- labels: PropTypes.shape({
497
- forwardButtonTip: PropTypes.string,
498
- backwardButtonTip: PropTypes.string,
499
- resetButtonTip: PropTypes.string,
500
- gotoNowButtonTip: PropTypes.string,
501
- doubleClickMaxZoomMsg: PropTypes.string,
502
- zoomInWithoutChangingSelectionMsg: PropTypes.string,
503
- zoomSelectionResolutionExtended: PropTypes.string,
504
- maxSelectionMsg: PropTypes.string,
505
- scrollMaxZoomMsg: PropTypes.string,
506
- minZoomMsg: PropTypes.string,
507
- maxDomainMsg: PropTypes.string,
508
- minDomainMsg: PropTypes.string,
509
- gotoCursor: PropTypes.string,
510
- zoomInLabel: PropTypes.string,
511
- zoomOutLabel: PropTypes.string,
512
- })
513
- ```
514
-
515
- Default:
516
- ```js
517
- const defaultLabels = {
518
- forwardButtonTip: 'Slide forward',
519
- backwardButtonTip: 'Slide backward',
520
- resetButtonTip: 'Reset time span',
521
- gotoNowButtonTip: 'Goto Now',
522
- doubleClickMaxZoomMsg: 'Cannot zoom anymore!',
523
- zoomInWithoutChangingSelectionMsg: 'Please change time selection before clicking on zoom ;)',
524
- zoomSelectionResolutionExtended: 'You reached maximum zoom level',
525
- maxSelectionMsg: 'You reached maximum selection',
526
- scrollMaxZoomMsg: 'Cannot zoom anymore!',
527
- minZoomMsg: 'You reached minimum zoom level',
528
- maxDomainMsg: 'You reached maximum visible time',
529
- minDomainMsg: 'You reached minimum visible time',
530
- gotoCursor: 'Goto Cursor',
531
- zoomInLabel: 'Zoom in',
532
- zoomOutLabel: 'Zoom out',
533
- };
534
- ```
247
+ ### `onDomainChange(domain): void`
248
+ Called whenever the visible domain changes zoom in/out, pan, or a
249
+ cursor-triggered edge shift. Host updates its `domain` state.
535
250
 
536
- ### showLegend
537
- Allow deactivating legend display, left to vertical axis
251
+ ### `onResetTime(): void`
252
+ Called when the user clicks the reset tool. Typical handler: clear the
253
+ host domain and let `onLoadDefaultDomain` re-seed.
538
254
 
539
- ```js
540
- showLegend: PropTypes.bool
541
- ```
255
+ ### `onShowMessage(msg: ReactNode): void`
256
+ Called to display an informational or error message (e.g. "reached max
257
+ zoom").
542
258
 
543
- ### tools
544
- Allow deactivating tools.
545
- All are present by default.
259
+ ### `onFormatTimeToolTips(time: Date): ReactNode`
260
+ Formats time for the cursor tooltips. Use your timezone library of
261
+ choice the component passes a raw `Date`.
546
262
 
547
263
  ```js
548
- tools: PropTypes.shape({
549
- slideForward: PropTypes.bool,
550
- slideBackward: PropTypes.bool,
551
- resetTimeline: PropTypes.bool,
552
- gotoNow: PropTypes.bool,
553
- cursor: PropTypes.bool,
554
- zoomIn: PropTypes.bool,
555
- zoomOut: PropTypes.bool,
556
- })
264
+ // With luxon
265
+ onFormatTimeToolTips = (time) =>
266
+ DateTime.fromJSDate(time).toFormat('yyyy-LL-dd HH:mm:ss');
557
267
  ```
558
268
 
559
- ### fetchWhileSliding
560
- Defines if timeline should try to refresh data when sliding domain.
561
- May overload the aggregation API.
562
- ```js
563
- fetchWhileSliding: PropTypes.bool
564
- ```
565
-
566
- ### selectBarOnClick
567
- Defines if clicking on a bar of the histogram automatically switch the time selection to this bar.
568
- ```js
569
- selectBarOnClick: PropTypes.bool
570
- ```
571
-
572
- ## Actions
573
-
574
- ### onLoadDefaultDomain()
575
- Called on mount to get the default domain.
576
- - Expected to initialize the `domains` prop.
577
- - Usually with a single default domain `[{min: moment, max: moment}]`.
578
-
579
- ### onLoadHisto(intervalMs: number, start: Moment, end: Moment)
580
- Called to load items. Give the needed interval, computed from props.width and props.domains[0].
581
- - Expected to update the `histo` prop.
269
+ ### `onFormatTimeLegend(time: Date): string`
270
+ Formats the x-axis tick. `time` is rounded to one of: millisecond,
271
+ second, minute, hour, day, month, year. Return a different string per
272
+ rounded level for readable ticks.
582
273
 
583
- **Called on:**
584
- - mount if domain is set
585
- - domain change
586
- - width change
587
- - sliding if `fetchWhileSliding` prop is set
274
+ ### `onFormatMetricLegend(value: number): string`
275
+ Formats a metric amount shown at the top of the y-axis.
588
276
 
589
- **Parameters:**
590
- - intervalsMs: Number - Milliseconds to use in the aggregation query
591
- - start: Moment - Current domain min
592
- - end: Moment - Current domain max
277
+ ## Imperative API
593
278
 
594
- ### onCustomRange(start: Moment, stop: Moment)
595
- Called when user has drawn or resized the cursor.
596
- - Expected to update the `timeSpan` prop.
279
+ Three methods are exposed on the ref:
597
280
 
598
- ### onShowMessage(msg: string)
599
- Called to display an error message.
281
+ - `zoomIn()` — zooms to the current cursor selection.
282
+ - `zoomOut()` pops the internal breadcrumb; if empty, expands by
283
+ `zoomOutFactor`.
284
+ - `shiftTimeLine(delta)` — animates the domain backward (`>0`) or
285
+ forward (`<0`) by `delta` pixels.
600
286
 
601
- ### onUpdateDomains(domains: arrayOf({min: Moment, max: Moment}))
602
- Called to save domains.
603
- - Expected to save the `domains` prop.
604
-
605
- ### onResetTime()
606
- Called when user want to reset timeline.
607
- - Expected to update the `domains` prop. Usually to a new array with only default domain.
608
- - `domains[0]` is expected to be changed (new object) to trigger a new `onLoadHisto` call.
609
-
610
- ### onFormatTimeToolTips(time: Moment) :string
611
- Called to display time in tooltips.
612
- - Must return a formatted date as string.
613
-
614
- Ex:
615
- ```js
616
- onFormatTimeToolTips = (time) => {
617
- return moment(time).second(0).millisecond(0).format(TIME_FORMAT_TOOLTIP);
618
- };
287
+ ```jsx
288
+ const ref = useRef();
289
+ <TimeLine ref={ref} ... />
290
+ <button onClick={() => ref.current.zoomIn()}>+</button>
619
291
  ```
620
292
 
621
- ### onFormatTimeLegend(time: Date) :string
622
- Called to format the x axis legend. Depending of zoom resolution, the input will be a date rounded to:
623
- - millisecond
624
- - second
625
- - minute
626
- - hour
627
- - day
628
- - month
629
- - year
630
-
293
+ ## Styling
631
294
 
632
- - Must return a formatted date as string.
633
- - Result should be different for each rounded date.
295
+ The library ships a single stylesheet (`dist/timeline.css`) whose rules
296
+ are keyed on `tl-<slot>` class names. Every SVG element also receives
297
+ any additional class you hand it via the `classes` prop — so your rules
298
+ win via CSS specificity + source order.
634
299
 
635
- Example:
636
- ```js
637
- import {timeFormat, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3';
638
-
639
- const formatMillisecond = timeFormat('.%L'), // .456
640
- formatSecond = timeFormat(':%S'), // :43
641
- formatMinute = timeFormat('%H:%M'), // 13:12
642
- formatHour = timeFormat('%H:00'), // 13:00
643
- formatDay = timeFormat('%b %d'), // Nov 02
644
- formatMonth = timeFormat('%b %d'), // Nov 01
645
- formatYear = timeFormat('%Y %b %d') // 2017 Nov 01
646
- ;
647
-
648
- const onFormatTimeLegend = (date) => {
649
- return (timeSecond(date) < date ? formatMillisecond
650
- : timeMinute(date) < date ? formatSecond
651
- : timeHour(date) < date ? formatMinute
652
- : timeDay(date) < date ? formatHour
653
- : timeMonth(date) < date ? formatDay
654
- : timeYear(date) < date ? formatMonth
655
- : formatYear)(date);
656
- };
657
- ```
658
-
659
- ### onFormatMetricLegend(value: number) :string
660
- Called to format metric amount value to display on the top of y axis.
661
-
662
- Example:
663
- ```js
664
- import {formatLocale } from 'd3';
665
-
666
- const locale = formatLocale({
667
- decimal: '.',
668
- thousands: ' ',
669
- grouping: [3],
670
- });
671
-
672
- const onFormatMetricLegend = (number) => {
673
- return locale.format(`,d`)(number);
674
- };
300
+ ```css
301
+ /* my-theme.css */
302
+ .my-cursorArea { fill: #4347fdff; stroke: #4347fdff; fill-opacity: 0.1; }
303
+ .my-cursorLeftHandle,
304
+ .my-cursorRightHandle { stroke: #4347fdff; }
305
+ .my-zoomIn { fill: #4347fdff; }
306
+ .my-verticalAxisText { fill: #949494; }
307
+ .my-legend { transform: translateX(-25px); }
675
308
  ```
676
309
 
677
- ## Public functions
678
- Three functions are exposed on the mounted component (through React Ref):
679
- Other functions are supposed to be private.
680
-
681
- ### zoomIn()
682
- - Zooms the time scale over the selected time frame.
683
- - A message will be sent if zooming is not possible any more (resolution...).
310
+ ```jsx
311
+ import './my-theme.css';
684
312
 
685
- This can be used to externalize ZoomIn button behavior in your app and own U/X.
686
-
687
- ### zoomOut()
688
- - Zooms out the time scale to previous zoom level or from `zoomOutFactor`.
689
- - A message will be sent if zooming out is not possible any more because of `biggestVisibleDomain`.
313
+ <TimeLine
314
+ classes={{
315
+ cursorArea: 'my-cursorArea',
316
+ cursorLeftHandle: 'my-cursorLeftHandle',
317
+ cursorRightHandle:'my-cursorRightHandle',
318
+ zoomIn: 'my-zoomIn',
319
+ verticalAxisText: 'my-verticalAxisText',
320
+ legend: 'my-legend',
321
+ /* any tl-<slot> in timeline.css is overridable */
322
+ }}
323
+ ...
324
+ />
325
+ ```
690
326
 
691
- This can be used to externalize ZoomOut button behavior in your app and own U/X.
327
+ Full list of slots: see `src/timeline.css` in the repo (or inspect a
328
+ rendered timeline — every class starts with `tl-`). The `test/src/App.jsx`
329
+ demo exercises the full set.
692
330
 
693
- ### shiftTimeLine(number)
694
- - Moves gradually the timeline backward (>0) or forward(<0)
331
+ ## Migrating from v4
695
332
 
696
- This can be used to externalize sliding buttons behavior in your app and own U/X.
333
+ See `MIGRATION-v5.md` for a step-by-step guide. TL;DR:
697
334
 
335
+ - moment objects → `Date`; `moment.Duration` → `number` (ms).
336
+ - `domains: Domain[]` → `domain: Domain | null`.
337
+ - `onUpdateDomains` → `onDomainChange`; `onCustomRange` → `onTimeSpanChange`.
338
+ - `onLoadHisto(intervalMs, start, end)` → `onLoadHisto({intervalMs, start, end})`.
339
+ - `onLoadDefaultDomain()` now RETURNS the default (sync or Promise).
340
+ - New required `timeZone` prop.
341
+ - `@material-ui/styles` peer dep gone; style via the `classes` prop
342
+ pointing at your own CSS class names.
698
343
 
699
344
  ## Testing / Dev
700
- You may run the demo in hot reloading mode:
345
+
701
346
  ```bash
702
- #clone the repo
703
347
  git clone https://gitlab.com/TincaTibo/timeline.git
704
- cd timeline/test
705
-
706
- #make a docker image with a demo app
707
- make image (requires docker and npm)
348
+ cd timeline
349
+ npm install
350
+ npm test # Vitest regression suite (12 tests)
351
+ npm run build # tsup -> dist/ (ESM + CJS + .d.ts + timeline.css)
352
+ npm run typecheck # tsc --noEmit
708
353
  ```
709
354
 
710
- Then, run the demo in prod mode
711
- ```
712
- # run the demo in prod mode
713
- make demo
714
- ```
355
+ Demo app (Vite, no Docker required):
715
356
 
716
- Or in dev mode
717
- ```
718
- # run the demo in dev mode (volume mount the dev files for hot reloading)
719
- make start
357
+ ```bash
358
+ cd test
359
+ make start # dev server on :5000 with HMR, aliased to ../src
360
+ make demo # production build + `vite preview`
720
361
  ```
721
- To access it, go to http://localhost:5000 in your browser
722
362
 
723
- ## Dependencies
724
- - React 16
725
- - D3.js
726
- - moment.js
727
- - lodash
728
- - rc-tooltip
363
+ Editing any file under `src/` hot-reloads the demo — no package rebuild.
729
364
 
730
- ## ToDo
731
- - Make input agnostic to moment.js
732
-
733
- ## Trello dashboard
734
- [Timeline React Component](https://trello.com/b/DxjkiuFB/timeline-react-component)
365
+ ## Dependencies
735
366
 
367
+ - React ≥ 16.8 (hooks).
368
+ - d3-{scale,selection,drag,time}
369
+ - luxon (runtime timezone engine — internal; host doesn't use it)
370
+ - rc-tooltip
371
+ - clsx
372
+ - prop-types (dev-time only — types ship via `.d.ts`)
736
373
 
374
+ Removed in v5: `@material-ui/styles`, `moment-timezone`, `lodash-es`.
737
375
 
738
376
  [example]: https://gitlab.com/TincaTibo/timeline/raw/master/img/TimeLine.png "Example"
739
377
  [styled]: https://gitlab.com/TincaTibo/timeline/-/raw/master/img/Styled-Timeline.png "Styled example"