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

keplergl / kepler.gl / 25638317567

10 May 2026 07:58PM UTC coverage: 58.644% (-0.1%) from 58.748%
25638317567

push

github

web-flow
feat: adjust Y-axis domain to filtered time range (#3419)

* feat: adjust Y-axis domain to filtered time range

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* clamp tooltip position to the chart body

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

---------

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>
Co-authored-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

7035 of 14401 branches covered (48.85%)

Branch coverage included in aggregate %.

1 of 22 new or added lines in 2 files covered. (4.55%)

10 existing lines in 2 files now uncovered.

14333 of 22036 relevant lines covered (65.04%)

79.44 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} range={range} yAxisAutoRange={plotType.yAxisAutoRange} {...commonProps} />
160
    ) : (
161
      <HistogramPlot
162
        histogramsByGroup={bins}
163
        colorsByGroup={groupColors}
164
        range={range}
165
        {...commonProps}
166
      />
167
    );
168
  };
169

170
  const RangePlotWithTheme = withTheme(RangePlot) as React.FC<
171
    RangePlotProps & Partial<RangeBrushProps>
172
  >;
173

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

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

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

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

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

253
  return withTheme(WithPlotLoading) as React.FC<Omit<WithPlotLoadingProps, 'theme'>>;
254
}
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