Skip to content

Commit

Permalink
[fix] Updated plot when changing cross filters (#2801)
Browse files Browse the repository at this point in the history
* [fix] Updated plot when changing cross filters

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
  • Loading branch information
igorDykhta authored Dec 6, 2024
1 parent b4dfa2f commit d50bbc8
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 193 deletions.
1 change: 1 addition & 0 deletions src/types/reducers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export type LineChart = {
aggregation: string;
interval: string;
yAxis: string;
bins?: Bins;
};

type FilterViewType = 'side' | 'enlarged' | 'minified';
Expand Down
41 changes: 25 additions & 16 deletions src/utils/src/plot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright contributors to the kepler.gl project

import {bisectLeft, bisector, extent, histogram as d3Histogram, ticks} from 'd3-array';
import isEqual from 'lodash.isequal';
import {getFilterMappedValue, getInitialInterval, intervalToFunction} from './time';
import moment from 'moment';
import {
Expand Down Expand Up @@ -303,14 +304,18 @@ const getAgregationAccessor = (field, dataContainer: DataContainerInterface, fie
return i => field.valueAccessor({index: i});
};

export const getValueAggrFunc = (field, aggregation, dataset) => {
export const getValueAggrFunc = (
field: Field | string | null,
aggregation: string,
dataset: KeplerTableModel<any, any>
): ((bin: Bin) => number) => {
const {dataContainer, fields} = dataset;
return field && aggregation
? bin =>
aggregate(
bin.indexes,
getAgregationType(field, aggregation),
// @ts-ignore
// @ts-expect-error can return {getNumerator, getDenominator}
getAgregationAccessor(field, dataContainer, fields)
)
: bin => bin.count;
Expand All @@ -328,7 +333,7 @@ function getDelta(
bins: LineDatum[],
y: number,
interval: PlotType['interval']
): Partial<LineDatum> {
): Partial<LineDatum> & {delta: 'last'; pct: number | null} {
// if (WOW[interval]) return getWow(bins, y, interval);
const lastBin = bins[bins.length - 1];

Expand All @@ -351,27 +356,28 @@ export function getPctChange(y, y0) {
* @param filter
*/
export function getLineChart(datasets: Datasets, filter: Filter): LineChart {
const {dataId, yAxis, plotType} = filter;
const {dataId, yAxis, plotType, lineChart} = filter;
const {aggregation, interval} = plotType;
const seriesDataId = dataId[0];
const bins = (filter as TimeRangeFilter).timeBins?.[seriesDataId]?.[interval];

if (
filter.lineChart &&
filter.lineChart.aggregation === aggregation &&
filter.lineChart.interval === interval &&
filter.lineChart.yAxis === yAxis?.name
lineChart &&
lineChart.aggregation === aggregation &&
lineChart.interval === interval &&
lineChart.yAxis === yAxis?.name &&
// we need to make sure we validate bins because of cross filter data changes
isEqual(bins, lineChart?.bins)
) {
// don't update lineChart if plotType hasn't change
return filter.lineChart;
return lineChart;
}

const seriesDataId = dataId[0];
const dataset = datasets[seriesDataId];
const getYValue = getValueAggrFunc(yAxis, aggregation, dataset);

// @ts-expect-error do we expect TimeRangeFilter here?
const bins = filter.timeBins[seriesDataId][interval];
const init = [];
const series = bins.reduce((accu, bin, i) => {
const init: LineDatum[] = [];
const series = (bins || []).reduce((accu, bin, i) => {
const y = getYValue(bin);
const delta = getDelta(accu, y, interval);
accu.push({
Expand All @@ -383,7 +389,7 @@ export function getLineChart(datasets: Datasets, filter: Filter): LineChart {
}, init);

const yDomain = extent<{y: any}>(series, d => d.y);
const xDomain = [bins[0].x0, bins[bins.length - 1].x1];
const xDomain = bins ? [bins[0].x0, bins[bins.length - 1].x1] : [];

// treat missing data as another series
const split = splitSeries(series);
Expand All @@ -404,7 +410,9 @@ export function getLineChart(datasets: Datasets, filter: Filter): LineChart {
allTime: {
title: `All Time Average`,
value: aggregate(series, AGGREGATION_TYPES.average, d => d.y)
}
},
// @ts-expect-error bins is Bins[], not a Bins map. Refactor to use correct types.
bins
};
}

Expand Down Expand Up @@ -560,6 +568,7 @@ export function updateTimeFilterPlotType(
if (plotType.type === PLOT_TYPES.histogram) {
// Histogram is calculated and memoized in the chart itself
} else if (plotType.type === PLOT_TYPES.lineChart) {
// we should be able to move this into its own component so react will do the shallow comparison for us.
nextFilter = {
...nextFilter,
lineChart: getLineChart(datasets, nextFilter)
Expand Down
3 changes: 2 additions & 1 deletion src/utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {Filter, Field, FilterDatasetOpt} from '@kepler.gl/types';

import {DataContainerInterface} from './data-container-interface';

export interface KeplerTableModel<K, L> {
export interface KeplerTableModel<K, L, F extends Field = any> {
id: string;
fields: F[];
getColumnFieldIdx(columnName: string): number;
filterTable(filters: Filter[], layers: L[], opt?: FilterDatasetOpt): K;
getColumnFilterProps(columnName: string): Field['filterProps'] | null | undefined;
Expand Down
12 changes: 12 additions & 0 deletions test/helpers/comparison-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ export function cmpFilters(t, expectedFilter, actualFilter, opt = {}, idx = '',
`${name}.filter.${key} should be the same`
);
break;
case 'lineChart': {
const {bins: actualBins, ...actualLineChart} = actualFilter[key];
const {bins: expectedBins, ...expectedLineChart} = expectedFilter[key];

t.deepEqual(
actualLineChart,
expectedLineChart,
`${name}.idx:${idx} | ${actualFilter.type} filter ${actualFilter.name} ${key} should be correct`
);
cmpBins(t, actualBins, expectedBins);
break;
}
default:
if (key !== 'id' || opt.id) {
// test everything except id, which is auto generated
Expand Down
Loading

0 comments on commit d50bbc8

Please sign in to comment.