@spider-analyzer/timeline 5.0.5 → 5.0.8
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.
- package/CHANGELOG.md +34 -0
- package/dist/index.js +44 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +44 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Cursor.jsx +3 -3
- package/src/TimeLine.tsx +29 -9
- package/src/cursorElements/CursorSelection.jsx +3 -3
- package/src/cursorElements/DragOverlay.jsx +3 -3
- package/src/moment-shim.ts +25 -0
- package/src/timeLineElements/HistoToolTip.jsx +3 -3
- package/src/timeLineElements/Histogram.jsx +3 -3
- package/src/timeLineElements/QualityLine.jsx +2 -2
- package/src/timeLineElements/XAxis.jsx +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spider-analyzer/timeline",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.8",
|
|
4
4
|
"description": "React graphical component to display metric over time with a time selection feature.",
|
|
5
5
|
"author": "Thibaut Raballand <spider.analyzer@gmail.com> (https://spider-analyzer.io)",
|
|
6
6
|
"license": "MIT",
|
package/src/Cursor.jsx
CHANGED
|
@@ -12,7 +12,7 @@ import RightHandle from './cursorElements/RightHandle';
|
|
|
12
12
|
import ZoomIn from './cursorElements/ZoomIn';
|
|
13
13
|
import LeftToolTip from './cursorElements/LeftToolTip';
|
|
14
14
|
import RightToolTip from './cursorElements/RightToolTip';
|
|
15
|
-
import moment from './moment-shim';
|
|
15
|
+
import moment, { momentType } from './moment-shim';
|
|
16
16
|
|
|
17
17
|
export default function Cursor({
|
|
18
18
|
overlayWidth, overlayHeight,
|
|
@@ -209,8 +209,8 @@ Cursor.propTypes = {
|
|
|
209
209
|
overlayHeight: PropTypes.number.isRequired,
|
|
210
210
|
overlayWidth: PropTypes.number.isRequired,
|
|
211
211
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
212
|
-
start:
|
|
213
|
-
end:
|
|
212
|
+
start: momentType.isRequired,
|
|
213
|
+
end: momentType.isRequired,
|
|
214
214
|
x1: PropTypes.number,
|
|
215
215
|
x2: PropTypes.number,
|
|
216
216
|
metrics: PropTypes.arrayOf(PropTypes.number).isRequired,
|
package/src/TimeLine.tsx
CHANGED
|
@@ -207,7 +207,13 @@ const TimeLineInner = forwardRef<TimeLineHandle, any>(function TimeLine(props, r
|
|
|
207
207
|
|
|
208
208
|
patchState({ histoWidth, isActive: true, ticks });
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
// Gate the fetch on a usable histoWidth. Before the first valid
|
|
211
|
+
// width is measured (React mount, ResizeObserver, consumer layout)
|
|
212
|
+
// histoWidth can be <= 0 — in that case we'd otherwise hit the
|
|
213
|
+
// domainSpan/1 fallback and ask the backend for smallestResolution
|
|
214
|
+
// / tickCount=1 granularity over the whole domain. Wait for the
|
|
215
|
+
// width-change effect to re-run getItems with a real width.
|
|
216
|
+
if (shouldReload && intervalMs && histoWidth > 0) {
|
|
211
217
|
widthOfLastUpdateRef.current = p.width;
|
|
212
218
|
patchState({ waitForLoad: true });
|
|
213
219
|
p.onLoadHisto(intervalMs, domain.min, domain.max);
|
|
@@ -247,14 +253,19 @@ const TimeLineInner = forwardRef<TimeLineHandle, any>(function TimeLine(props, r
|
|
|
247
253
|
return;
|
|
248
254
|
}
|
|
249
255
|
const s = stateRef.current;
|
|
250
|
-
|
|
256
|
+
patchState(computeMarginAndWidth(props));
|
|
257
|
+
// Re-run getItems whenever width/margin changes AND we have a
|
|
258
|
+
// domain. Was previously guarded on s.isActive which meant the
|
|
259
|
+
// first getItems call (with an as-yet unknown width) locked us
|
|
260
|
+
// into a bad intervalMs: it set isActive=true but skipped the
|
|
261
|
+
// fetch (histoWidth <= 0 gate), and this effect wouldn't re-fetch
|
|
262
|
+
// because isActive was now true but big=false.
|
|
263
|
+
if (s.domain) {
|
|
251
264
|
const big =
|
|
252
265
|
widthOfLastUpdateRef.current !== null
|
|
253
266
|
&& Math.abs((props.width ?? 0) - (widthOfLastUpdateRef.current ?? 0)) > 30;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
} else {
|
|
257
|
-
patchState(computeMarginAndWidth(props));
|
|
267
|
+
const shouldReload = !s.isActive || big || widthOfLastUpdateRef.current === null;
|
|
268
|
+
getItems(props, s.domain, shouldReload);
|
|
258
269
|
}
|
|
259
270
|
|
|
260
271
|
if (stateRef.current.domain) {
|
|
@@ -747,15 +758,24 @@ const TimeLineInner = forwardRef<TimeLineHandle, any>(function TimeLine(props, r
|
|
|
747
758
|
const { isActive, domain, histoWidth, margin, start, stop, maxZoom, minZoom,
|
|
748
759
|
maxTimespan, barHovered, tooltipVisible, ticks, dragging } = state;
|
|
749
760
|
|
|
761
|
+
// Memoize the expensive per-render bits so cursor drag (which flips
|
|
762
|
+
// only state.start/state.stop 60x/sec) doesn't rebuild the histogram
|
|
763
|
+
// bar list on every event. Under the Luxon-backed shim, rebuilding
|
|
764
|
+
// ~550 bars costs ~150 ms/render — enough to tank drag framerate.
|
|
765
|
+
const verticalScaleData = useMemo(() => prepareVerticalScale(), [prepareVerticalScale]);
|
|
766
|
+
const items = useMemo(
|
|
767
|
+
() => prepareHistogram({ domain, verticalScale: verticalScaleData.verticalScale }),
|
|
768
|
+
[prepareHistogram, domain, verticalScaleData, histoWidth],
|
|
769
|
+
);
|
|
770
|
+
itemsRef.current = items;
|
|
771
|
+
|
|
750
772
|
const xAxisHeight = xAxisProp.height || xAxisDefault.height;
|
|
751
773
|
const pointZero = { x: 0, y: height - margin.bottom - xAxisHeight };
|
|
752
774
|
const originCursor = { x: 0, y: margin.top - 5 };
|
|
753
775
|
|
|
754
776
|
if (!isFunction(xAxisRef.current)) return null;
|
|
755
777
|
|
|
756
|
-
const { verticalScale, verticalMarks, maxHeight } =
|
|
757
|
-
const items = prepareHistogram({ domain, verticalScale });
|
|
758
|
-
itemsRef.current = items;
|
|
778
|
+
const { verticalScale, verticalMarks, maxHeight } = verticalScaleData;
|
|
759
779
|
|
|
760
780
|
return (
|
|
761
781
|
<svg
|
|
@@ -5,7 +5,7 @@ import {pointer, select} from 'd3-selection';
|
|
|
5
5
|
import {drag} from 'd3-drag';
|
|
6
6
|
|
|
7
7
|
import clsx from 'clsx';
|
|
8
|
-
import moment from '../moment-shim';
|
|
8
|
+
import moment, { momentType } from '../moment-shim';
|
|
9
9
|
import {handleHistoOver, handleHistoOut} from './handleHistoHovering';
|
|
10
10
|
|
|
11
11
|
|
|
@@ -106,8 +106,8 @@ CursorSelection.propTypes = {
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
109
|
-
start:
|
|
110
|
-
end:
|
|
109
|
+
start: momentType.isRequired,
|
|
110
|
+
end: momentType.isRequired,
|
|
111
111
|
x1: PropTypes.number,
|
|
112
112
|
x2: PropTypes.number,
|
|
113
113
|
metrics: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
@@ -2,7 +2,7 @@ import React, {forwardRef, useCallback, useEffect, useRef, useState} from 'react
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import {pointer, select} from 'd3-selection';
|
|
4
4
|
import {drag} from 'd3-drag';
|
|
5
|
-
import moment from '../moment-shim';
|
|
5
|
+
import moment, { momentType } from '../moment-shim';
|
|
6
6
|
|
|
7
7
|
import {handleHistoOver, handleHistoOut} from './handleHistoHovering';
|
|
8
8
|
|
|
@@ -104,8 +104,8 @@ DragOverlay.propTypes = {
|
|
|
104
104
|
marginBottom: PropTypes.number,
|
|
105
105
|
|
|
106
106
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
107
|
-
start:
|
|
108
|
-
end:
|
|
107
|
+
start: momentType.isRequired,
|
|
108
|
+
end: momentType.isRequired,
|
|
109
109
|
x1: PropTypes.number,
|
|
110
110
|
x2: PropTypes.number,
|
|
111
111
|
metrics: PropTypes.arrayOf(PropTypes.number).isRequired,
|
package/src/moment-shim.ts
CHANGED
|
@@ -165,5 +165,30 @@ moment.tz = (x: any, zone: string): MomentLike => {
|
|
|
165
165
|
return new MomentLike(base.setZone(zone));
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
// PropTypes helper — the library's internal .jsx components use
|
|
169
|
+
// `PropTypes.instanceOf(moment)` which doesn't work against the shim
|
|
170
|
+
// (moment is the factory function, not a class). Use this instead:
|
|
171
|
+
// time: momentType
|
|
172
|
+
// time: momentType.isRequired
|
|
173
|
+
function validate(props: Record<string, any>, propName: string, componentName: string): Error | null {
|
|
174
|
+
const v = props[propName];
|
|
175
|
+
if (v == null) return null;
|
|
176
|
+
if (moment.isMoment(v)) return null;
|
|
177
|
+
return new Error(
|
|
178
|
+
`Invalid prop \`${propName}\` supplied to \`${componentName}\`, expected a moment-like object.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const required = (props: Record<string, any>, propName: string, componentName: string): Error | null => {
|
|
182
|
+
const v = props[propName];
|
|
183
|
+
if (v == null) {
|
|
184
|
+
return new Error(
|
|
185
|
+
`The prop \`${propName}\` is marked as required in \`${componentName}\`, but its value is \`${v}\`.`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return validate(props, propName, componentName);
|
|
189
|
+
};
|
|
190
|
+
export const momentType: any = validate;
|
|
191
|
+
momentType.isRequired = required;
|
|
192
|
+
|
|
168
193
|
export default moment;
|
|
169
194
|
export type Moment = MomentLike;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../styles";
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
|
-
import moment from '../moment-shim';
|
|
4
|
+
import moment, { momentType } from '../moment-shim';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
|
|
@@ -50,8 +50,8 @@ export default function HistoTooltip({metricsDefinition, item, onFormatTimeToolT
|
|
|
50
50
|
HistoTooltip.propTypes = {
|
|
51
51
|
classes: PropTypes.object,
|
|
52
52
|
item: PropTypes.shape({
|
|
53
|
-
start:
|
|
54
|
-
end:
|
|
53
|
+
start: momentType.isRequired,
|
|
54
|
+
end: momentType.isRequired,
|
|
55
55
|
x1: PropTypes.number,
|
|
56
56
|
x2: PropTypes.number,
|
|
57
57
|
metrics: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../styles";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import moment from '../moment-shim';
|
|
3
|
+
import moment, { momentType } from '../moment-shim';
|
|
4
4
|
import { sum as _sum } from '../utils';
|
|
5
5
|
import PropTypes from 'prop-types';
|
|
6
6
|
import clsx from 'clsx';
|
|
@@ -72,8 +72,8 @@ export default function Histogram({
|
|
|
72
72
|
Histogram.propTypes = {
|
|
73
73
|
classes: PropTypes.object,
|
|
74
74
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
75
|
-
start:
|
|
76
|
-
end:
|
|
75
|
+
start: momentType.isRequired,
|
|
76
|
+
end: momentType.isRequired,
|
|
77
77
|
x1: PropTypes.number,
|
|
78
78
|
x2: PropTypes.number,
|
|
79
79
|
metrics: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
|
|
6
|
-
import moment from '../moment-shim';
|
|
6
|
+
import moment, { momentType } from '../moment-shim';
|
|
7
7
|
import ToolTip from '../ToolTip';
|
|
8
8
|
|
|
9
9
|
const qualityHeight = 3;
|
|
@@ -64,7 +64,7 @@ QualityLine.propTypes = {
|
|
|
64
64
|
xAxis: PropTypes.func.isRequired,
|
|
65
65
|
quality: PropTypes.shape({
|
|
66
66
|
items: PropTypes.arrayOf(PropTypes.shape({
|
|
67
|
-
time:
|
|
67
|
+
time: momentType.isRequired,
|
|
68
68
|
quality: PropTypes.number.isRequired,
|
|
69
69
|
tip: PropTypes.node
|
|
70
70
|
})),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import moment from '../moment-shim';
|
|
2
|
+
import moment, { momentType } from '../moment-shim';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import {timeDay} from 'd3-time';
|
|
5
5
|
import clsx from 'clsx';
|
|
@@ -69,8 +69,8 @@ export default function XAxis({min, max, origin, axisWidth, marks, xAxis, classe
|
|
|
69
69
|
XAxis.propTypes = {
|
|
70
70
|
axisWidth:PropTypes.number.isRequired,
|
|
71
71
|
classes: PropTypes.object,
|
|
72
|
-
min:
|
|
73
|
-
max:
|
|
72
|
+
min: momentType,
|
|
73
|
+
max: momentType,
|
|
74
74
|
origin: PropTypes.shape({
|
|
75
75
|
x: PropTypes.number.isRequired,
|
|
76
76
|
y: PropTypes.number.isRequired
|