@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spider-analyzer/timeline",
3
- "version": "5.0.5",
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: PropTypes.instanceOf(moment).isRequired,
213
- end: PropTypes.instanceOf(moment).isRequired,
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
- if (shouldReload && intervalMs) {
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
- if (s.isActive) {
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
- getItems(props, s.domain, big);
255
- patchState(computeMarginAndWidth(props));
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 } = prepareVerticalScale();
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: PropTypes.instanceOf(moment).isRequired,
110
- end: PropTypes.instanceOf(moment).isRequired,
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: PropTypes.instanceOf(moment).isRequired,
108
- end: PropTypes.instanceOf(moment).isRequired,
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,
@@ -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: PropTypes.instanceOf(moment).isRequired,
54
- end: PropTypes.instanceOf(moment).isRequired,
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: PropTypes.instanceOf(moment).isRequired,
76
- end: PropTypes.instanceOf(moment).isRequired,
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: PropTypes.instanceOf(moment).isRequired,
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: PropTypes.instanceOf(moment),
73
- max: PropTypes.instanceOf(moment),
72
+ min: momentType,
73
+ max: momentType,
74
74
  origin: PropTypes.shape({
75
75
  x: PropTypes.number.isRequired,
76
76
  y: PropTypes.number.isRequired