Skip to content

Commit cb95a75

Browse files
authored
fix(heatmap): pick correct brush end value (opensearch-project#1230)
fix opensearch-project#1229
1 parent 775dc98 commit cb95a75

File tree

3 files changed

+84
-34
lines changed

3 files changed

+84
-34
lines changed

packages/osd-charts/packages/charts/src/chart_types/heatmap/layout/viewmodel/viewmodel.ts

+5-20
Original file line numberDiff line numberDiff line change
@@ -275,25 +275,10 @@ export function shapeViewModel(
275275
const startY = yInvertedScale(clamp(topLeft[1], 0, currentGridHeight - 1));
276276
const endY = yInvertedScale(clamp(bottomRight[1], 0, currentGridHeight - 1));
277277

278-
let allXValuesInRange: Array<NonNullable<PrimitiveValue>> = [];
279-
const invertedXValues: Array<NonNullable<PrimitiveValue>> = [];
280-
281-
if (timeScale && typeof endX === 'number') {
282-
invertedXValues.push(startX);
283-
invertedXValues.push(endX + xDomain.minInterval);
284-
let [startXValue] = invertedXValues;
285-
if (typeof startXValue === 'number') {
286-
while (startXValue < invertedXValues[1]) {
287-
allXValuesInRange.push(startXValue);
288-
startXValue += xDomain.minInterval;
289-
}
290-
}
291-
} else {
292-
allXValuesInRange = getValuesInRange(xValues, startX, endX);
293-
invertedXValues.push(...allXValuesInRange);
294-
}
295-
278+
const allXValuesInRange: Array<NonNullable<PrimitiveValue>> = getValuesInRange(xValues, startX, endX);
296279
const allYValuesInRange: Array<NonNullable<PrimitiveValue>> = getValuesInRange(yValues, startY, endY);
280+
const invertedXValues: Array<NonNullable<PrimitiveValue>> =
281+
timeScale && typeof endX === 'number' ? [startX, endX + xDomain.minInterval] : [...allXValuesInRange];
297282

298283
const cells: Cell[] = [];
299284

@@ -325,7 +310,7 @@ export function shapeViewModel(
325310

326311
// find X coordinated based on the time range
327312
const leftIndex = typeof startValue === 'number' ? bisectLeft(xValues, startValue) : xValues.indexOf(startValue);
328-
const rightIndex = typeof endValue === 'number' ? bisectLeft(xValues, endValue) : xValues.indexOf(endValue);
313+
const rightIndex = typeof endValue === 'number' ? bisectLeft(xValues, endValue) : xValues.indexOf(endValue) + 1;
329314

330315
const isRightOutOfRange = rightIndex > xValues.length - 1 || rightIndex < 0;
331316
const isLeftOutOfRange = leftIndex > xValues.length - 1 || leftIndex < 0;
@@ -340,7 +325,7 @@ export function shapeViewModel(
340325
const xStart = chartDimensions.left + startFromScale;
341326

342327
// extend the range in case the right boundary has been selected
343-
const width = endFromScale - startFromScale + cellWidth; // (isRightOutOfRange || isLeftOutOfRange ? cellWidth : 0);
328+
const width = endFromScale - startFromScale + (isRightOutOfRange || isLeftOutOfRange ? cellWidth : 0);
344329

345330
// resolve Y coordinated making sure the order is correct
346331
const { y: yStart, totalHeight } = y

packages/osd-charts/packages/charts/src/chart_types/heatmap/state/selectors/get_brushed_highlighted_shapes.test.ts

+78-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* under the License.
1818
*/
1919

20+
import { DateTime } from 'luxon';
2021
import { Store } from 'redux';
2122

2223
import { MockGlobalSpec, MockSeriesSpec } from '../../../../mocks/specs/specs';
@@ -26,7 +27,7 @@ import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions
2627
import { GlobalChartState } from '../../../../state/chart_state';
2728
import { createOnBrushEndCaller } from './on_brush_end_caller';
2829

29-
describe('Heatmap brush', () => {
30+
describe('Categorical heatmap brush', () => {
3031
let store: Store<GlobalChartState>;
3132
let onBrushEndMock = jest.fn();
3233

@@ -87,3 +88,79 @@ describe('Heatmap brush', () => {
8788
expect(brushEvent.y).toEqual(['ya', 'yb', 'yc']);
8889
});
8990
});
91+
describe('Temporal heatmap brush', () => {
92+
let store: Store<GlobalChartState>;
93+
let onBrushEndMock = jest.fn();
94+
const start = DateTime.fromISO('2021-07-01T00:00:00.000Z');
95+
beforeEach(() => {
96+
store = MockStore.default({ width: 300, height: 300, top: 0, left: 0 }, 'chartId');
97+
onBrushEndMock = jest.fn();
98+
MockStore.addSpecs(
99+
[
100+
MockGlobalSpec.settingsNoMargins(),
101+
MockSeriesSpec.heatmap({
102+
xScaleType: ScaleType.Time,
103+
data: [
104+
{ x: start.toMillis(), y: 'ya', value: 1 },
105+
{ x: start.plus({ days: 1 }).toMillis(), y: 'ya', value: 2 },
106+
{ x: start.plus({ days: 2 }).toMillis(), y: 'ya', value: 3 },
107+
{ x: start.toMillis(), y: 'yb', value: 4 },
108+
{ x: start.plus({ days: 1 }).toMillis(), y: 'yb', value: 5 },
109+
{ x: start.plus({ days: 2 }).toMillis(), y: 'yb', value: 6 },
110+
{ x: start.toMillis(), y: 'yc', value: 7 },
111+
{ x: start.plus({ days: 1 }).toMillis(), y: 'yc', value: 8 },
112+
{ x: start.plus({ days: 2 }).toMillis(), y: 'yc', value: 9 },
113+
],
114+
config: {
115+
grid: {
116+
cellHeight: {
117+
max: 'fill',
118+
},
119+
cellWidth: {
120+
max: 'fill',
121+
},
122+
},
123+
xAxisLabel: {
124+
visible: false,
125+
},
126+
yAxisLabel: {
127+
visible: false,
128+
},
129+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
130+
onBrushEnd: onBrushEndMock,
131+
},
132+
}),
133+
],
134+
store,
135+
);
136+
});
137+
138+
it('should brush on the x scale + minInterval', () => {
139+
const caller = createOnBrushEndCaller();
140+
store.dispatch(onPointerMove({ x: 50, y: 50 }, 0));
141+
store.dispatch(onMouseDown({ x: 50, y: 50 }, 100));
142+
store.dispatch(onPointerMove({ x: 250, y: 250 }, 200));
143+
store.dispatch(onMouseUp({ x: 250, y: 250 }, 300));
144+
caller(store.getState());
145+
expect(onBrushEndMock).toBeCalledTimes(1);
146+
const brushEvent = onBrushEndMock.mock.calls[0][0];
147+
expect(brushEvent.cells).toHaveLength(6);
148+
// it covers from the beginning of the cell to the end of the next cell
149+
expect(brushEvent.x).toEqual([start.toMillis(), start.plus({ days: 2 }).toMillis()]);
150+
expect(brushEvent.y).toEqual(['ya', 'yb', 'yc']);
151+
});
152+
it('should brush on the x scale + minInterval on a single cell', () => {
153+
const caller = createOnBrushEndCaller();
154+
store.dispatch(onPointerMove({ x: 50, y: 50 }, 0));
155+
store.dispatch(onMouseDown({ x: 50, y: 50 }, 100));
156+
store.dispatch(onPointerMove({ x: 60, y: 60 }, 200));
157+
store.dispatch(onMouseUp({ x: 60, y: 60 }, 300));
158+
caller(store.getState());
159+
expect(onBrushEndMock).toBeCalledTimes(1);
160+
const brushEvent = onBrushEndMock.mock.calls[0][0];
161+
expect(brushEvent.cells).toHaveLength(1);
162+
// it covers from the beginning of the cell to the end of the next cell
163+
expect(brushEvent.x).toEqual([start.toMillis(), start.plus({ days: 1 }).toMillis()]);
164+
expect(brushEvent.y).toEqual(['ya']);
165+
});
166+
});

packages/osd-charts/packages/charts/src/chart_types/heatmap/state/selectors/get_picked_cells.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,6 @@ export const getPickedCells = createCustomCachedSelector(
3030
return null;
3131
}
3232

33-
const {
34-
start: {
35-
position: { x: startX, y: startY },
36-
},
37-
end: {
38-
position: { x: endX, y: endY },
39-
},
40-
} = dragState;
41-
42-
return geoms.pickDragArea([
43-
{ x: startX, y: startY },
44-
{ x: endX, y: endY },
45-
]);
33+
return geoms.pickDragArea([dragState.start.position, dragState.end.position]);
4634
},
4735
);

0 commit comments

Comments
 (0)