• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 12604159536

03 Jan 2025 09:26PM UTC coverage: 66.843% (-0.5%) from 67.344%
12604159536

Pull #2892

github

web-flow
Merge 894aa31a5 into 0b67c5409
Pull Request #2892: [fix] Prevent infinite useEffects loop

5959 of 10355 branches covered (57.55%)

Branch coverage included in aggregate %.

13 of 16 new or added lines in 1 file covered. (81.25%)

484 existing lines in 25 files now uncovered.

12215 of 16834 relevant lines covered (72.56%)

89.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.0
/src/components/src/common/range-plot.tsx
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import React, {useCallback, useEffect, useMemo, useRef, useState, CSSProperties} from 'react';
5
import styled, {withTheme} from 'styled-components';
6
import RangeBrushFactory, {OnBrush, RangeBrushProps} from './range-brush';
7
import HistogramPlotFactory from './histogram-plot';
8
import LineChartFactory, {HoverDP} from './line-chart';
9
import {hasMobileWidth, isTest} from '@kepler.gl/utils';
10
import {PLOT_TYPES} from '@kepler.gl/constants';
11
import LoadingSpinner from './loading-spinner';
12
import {breakPointValues} from '@kepler.gl/styles';
13
import {LineChart as LineChartType, Filter, Bins} from '@kepler.gl/types';
14
import {Datasets} from '@kepler.gl/table';
15

16
const StyledRangePlot = styled.div`
7✔
17
  margin-bottom: ${props => props.theme.sliderBarHeight}px;
19✔
18
  display: flex;
19
  position: relative;
20
`;
21

22
interface RangePlotProps {
23
  onBrush: OnBrush;
24
  range: number[];
25
  value: number[];
26
  width: number;
27
  plotType: {
28
    [key: string]: any;
29
  };
30
  lineChart?: LineChartType;
31
  bins?: Bins;
32

33
  isEnlarged?: boolean;
34
  isRanged?: boolean;
35
  theme: any;
36
  timeFormat?: string;
37
  timezone?: string | null;
38
  playbackControlWidth?: number;
39

40
  animationWindow?: string;
41
  filter?: Filter;
42
  datasets?: Datasets;
43

44
  invertTrendColor?: boolean;
45

46
  style: CSSProperties;
47
}
48

49
type WithPlotLoadingProps = RangePlotProps &
50
  Partial<RangeBrushProps> & {
51
    setFilterPlot: any;
52
  };
53

54
RangePlotFactory.deps = [RangeBrushFactory, HistogramPlotFactory, LineChartFactory];
7✔
55

56
const isHistogramPlot = plotType => plotType?.type === PLOT_TYPES.histogram;
8✔
57
const isLineChart = plotType => plotType?.type === PLOT_TYPES.lineChart;
25✔
58
const hasHistogram = (plotType, bins) => isHistogramPlot(plotType) && bins;
7!
59
const hasLineChart = (plotType, lineChart) => isLineChart(plotType) && lineChart;
7!
60

61
const LOADING_SPINNER_CONTAINER_STYLE = {
7✔
62
  display: 'flex',
63
  alignItems: 'center',
64
  justifyContent: 'center',
65
  width: '100%'
66
};
67

68
export default function RangePlotFactory(
69
  RangeBrush: ReturnType<typeof RangeBrushFactory>,
70
  HistogramPlot: ReturnType<typeof HistogramPlotFactory>,
71
  LineChartPlot: ReturnType<typeof LineChartFactory>
72
) {
73
  const RangePlot = ({
15✔
74
    bins,
75
    onBrush,
76
    range,
77
    value,
78
    width,
79
    plotType,
80
    lineChart,
81
    isEnlarged,
82
    isRanged,
83
    theme,
84
    ...chartProps
85
  }: RangePlotProps & Partial<RangeBrushProps>) => {
86
    const groupColors = useMemo(() => {
17✔
87
      const dataIds = bins ? Object.keys(bins) : [];
8✔
88
      return plotType.colorsByDataId
8!
89
        ? dataIds.reduce((acc, dataId) => {
90
            acc[dataId] = plotType.colorsByDataId[dataId];
×
91
            return acc;
×
92
          }, {})
93
        : null;
94
    }, [bins, plotType.colorsByDataId]);
95

96
    const [brushing, setBrushing] = useState(false);
17✔
97
    const [hoveredDP, onMouseMove] = useState<HoverDP | null>(null);
17✔
98
    const [enableChartHover, setEnableChartHover] = useState(false);
17✔
99
    const height = isEnlarged
17!
100
      ? hasMobileWidth(breakPointValues)
17!
101
        ? theme.rangePlotHLargePalm
102
        : theme.rangePlotHLarge
103
      : theme.rangePlotH;
104

105
    const onBrushStart = useCallback(() => {
17✔
106
      setBrushing(true);
×
107
      onMouseMove(null);
×
108
      setEnableChartHover(false);
×
109
    }, [setBrushing, onMouseMove, setEnableChartHover]);
110

111
    const onBrushEnd = useCallback(() => {
17✔
112
      setBrushing(false);
×
113
      setEnableChartHover(true);
×
114
    }, [setBrushing, setEnableChartHover]);
115

116
    const onMouseoverHandle = useCallback(() => {
17✔
117
      onMouseMove(null);
×
118
      setEnableChartHover(false);
×
119
    }, [onMouseMove, setEnableChartHover]);
120

121
    const onMouseoutHandle = useCallback(() => {
17✔
122
      setEnableChartHover(true);
×
123
    }, [setEnableChartHover]);
124

125
    // JsDom have limited support for SVG, d3 will fail
126
    const brushComponent = isTest() ? null : (
17!
127
      <RangeBrush
128
        onBrush={onBrush}
129
        onBrushStart={onBrushStart}
130
        onBrushEnd={onBrushEnd}
131
        range={range}
132
        value={value}
133
        width={width}
134
        height={height}
135
        isRanged={isRanged}
136
        onMouseoverHandle={onMouseoverHandle}
137
        onMouseoutHandle={onMouseoutHandle}
138
        {...chartProps}
139
      />
140
    );
141

142
    const commonProps = {
17✔
143
      width,
144
      value,
145
      height,
146
      margin: isEnlarged ? theme.rangePlotMarginLarge : theme.rangePlotMargin,
17!
147
      brushComponent,
148
      brushing,
149
      isEnlarged,
150
      enableChartHover,
151
      onMouseMove,
152
      hoveredDP,
153
      isRanged,
154
      onBrush,
155
      ...chartProps
156
    };
157

158
    return isLineChart(plotType) && lineChart ? (
17!
159
      <LineChartPlot lineChart={lineChart} {...commonProps} />
160
    ) : (
161
      <HistogramPlot
162
        histogramsByGroup={bins}
163
        colorsByGroup={groupColors}
164
        range={range}
165
        {...commonProps}
166
      />
167
    );
168
  };
169

170
  const RangePlotWithTheme = withTheme(RangePlot);
15✔
171

172
  // a container to render spinner or message when the data is too big
173
  // to generate a plot
174
  const WithPlotLoading = ({
15✔
175
    lineChart,
176
    plotType,
177
    bins,
178
    setFilterPlot,
179
    isEnlarged,
180
    theme,
181
    ...otherProps
182
  }: WithPlotLoadingProps) => {
183
    const [isLoading, setIsLoading] = useState(false);
19✔
184
    const isChangingRef = useRef(false);
19✔
185

186
    useEffect(() => {
19✔
187
      if (isChangingRef.current) {
8!
NEW
188
        if (hasHistogram(plotType, bins)) {
×
189
          // Bins are loaded
NEW
190
          isChangingRef.current = false;
×
191
        }
192
      } else {
193
        if (!plotType || (isHistogramPlot(plotType) && !bins)) {
8✔
194
          // load histogram
195
          setIsLoading(true);
1✔
196
          setFilterPlot({plotType: {type: PLOT_TYPES.histogram}});
1✔
197
          isChangingRef.current = true;
1✔
198
        }
199
      }
200
    }, [bins, plotType, setFilterPlot]);
201

202
    useEffect(() => {
19✔
203
      if (isChangingRef.current) {
8✔
204
        if (hasLineChart(plotType, lineChart)) {
1!
205
          // Line chart is loaded
NEW
206
          isChangingRef.current = false;
×
207
        }
208
      } else {
209
        if (isLineChart(plotType) && !lineChart) {
7✔
210
          // load line chart
211
          setIsLoading(true);
1✔
212
          setFilterPlot({plotType: {type: PLOT_TYPES.lineChart}});
1✔
213
          isChangingRef.current = true;
1✔
214
        }
215
      }
216
    }, [lineChart, plotType, setFilterPlot]);
217

218
    const rangePlotStyle = useMemo(
19✔
219
      () => ({
8✔
220
        height: `${
221
          isEnlarged
8!
222
            ? hasMobileWidth(breakPointValues)
8!
223
              ? theme.rangePlotContainerHLargePalm
224
              : theme.rangePlotContainerHLarge
225
            : theme.rangePlotContainerH
226
        }px`
227
      }),
228
      [isEnlarged, theme]
229
    );
230

231
    return (
19✔
232
      <StyledRangePlot style={rangePlotStyle} className="kg-range-slider__plot">
233
        {isLoading ? (
19✔
234
          <div style={LOADING_SPINNER_CONTAINER_STYLE}>
235
            <LoadingSpinner borderColor="transparent" size={40} />
236
          </div>
237
        ) : (
238
          <RangePlotWithTheme
239
            lineChart={lineChart}
240
            bins={bins}
241
            plotType={plotType}
242
            isEnlarged={isEnlarged}
243
            theme={theme}
244
            {...otherProps}
245
          />
246
        )}
247
      </StyledRangePlot>
248
    );
249
  };
250

251
  return withTheme(WithPlotLoading);
15✔
252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc