From 618dc0adaa9aa2b2fe7a568a6ced003da2b84b41 Mon Sep 17 00:00:00 2001 From: Deepak Kumar Date: Sun, 31 Mar 2024 10:39:04 +0530 Subject: [PATCH] Externalizing Crossfilter specific behavior --- spec/pie-chart-spec.js | 11 ++++++-- src/data/c-f-filter-handler.ts | 37 ++++----------------------- src/data/c-f-multi-adapter.ts | 3 +-- src/data/c-f-simple-adapter.ts | 20 ++++++++------- src/data/cf-helper.ts | 46 ++++++++++++++++++++++++++++++++++ src/data/i-c-f-helper.ts | 7 ++++++ src/data/index.ts | 2 ++ 7 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 src/data/cf-helper.ts create mode 100644 src/data/i-c-f-helper.ts diff --git a/spec/pie-chart-spec.js b/spec/pie-chart-spec.js index fcb1ff7df..d90176cef 100644 --- a/spec/pie-chart-spec.js +++ b/spec/pie-chart-spec.js @@ -384,6 +384,13 @@ describe('dc.pieChart', () => { }); describe('comparing crossfilter and chart ordering', () => { let crossfilterOrder, crossfilterTop2; + const prepData = raw => { + return raw.map(g => { + delete g._value; + return g + }); + }; + beforeEach(() => { countryChart = buildCountryChart('country-chart'); countryChart.innerRadius(innerRadius); @@ -398,7 +405,7 @@ describe('dc.pieChart', () => { }); describe('with ordering and capping not set', () => { it('should match the crossfilter top 2', () => { - expect(countryChart.data()).toEqual(crossfilterTop2); + expect(prepData(countryChart.data())).toEqual(crossfilterTop2); }); }); describe('with ordering by key', () => { @@ -406,7 +413,7 @@ describe('dc.pieChart', () => { countryChart.ordering(kv => kv.key).redraw(); }); it('should should match crossfilter top(2)', () => { - expect(countryChart.data()).toEqual(crossfilterOrder); + expect(prepData(countryChart.data())).toEqual(crossfilterOrder); }); describe('with cap(1)', () => { beforeEach(() => { diff --git a/src/data/c-f-filter-handler.ts b/src/data/c-f-filter-handler.ts index 20d7e0ae2..bb4ef3d9b 100644 --- a/src/data/c-f-filter-handler.ts +++ b/src/data/c-f-filter-handler.ts @@ -1,5 +1,7 @@ import { MinimalCFDimension } from '../core/types.js'; import { FilterStorageHelper, IFilterStorageConf } from './filter-storage-helper.js'; +import { cfHelper } from './cf-helper.js'; +import { ICFHelper } from './i-c-f-helper.js'; export interface ICFFilterHandlerConf extends IFilterStorageConf { dimension?: MinimalCFDimension; @@ -8,6 +10,7 @@ export interface ICFFilterHandlerConf extends IFilterStorageConf { export class CFFilterHandler extends FilterStorageHelper { protected _conf: ICFFilterHandlerConf; + protected helper: ICFHelper = cfHelper; constructor(conf: ICFFilterHandlerConf = {}) { super({ @@ -24,40 +27,10 @@ export class CFFilterHandler extends FilterStorageHelper { } protected _storageKey(): any { - if (this._conf.shareFilters) { - return this._conf.dimension; - } else { - return this; - } + return this.helper.storageKey(this); } public applyFilters() { - if (!(this._conf.dimension && this._conf.dimension.filter)) { - return; - } - - if (this.filters.length === 0) { - this._conf.dimension.filter(null); - } else if (this.filters.length === 1 && !this.filters[0].isFiltered) { - // single value and not a function-based filter - this._conf.dimension.filterExact(this.filters[0]); - } else if (this.filters.length === 1 && this.filters[0].filterType === 'RangedFilter') { - // single range-based filter - this._conf.dimension.filterRange(this.filters[0]); - } else { - this._conf.dimension.filterFunction(d => { - for (let i = 0; i < this.filters.length; i++) { - const filter = this.filters[i]; - if (filter.isFiltered) { - if (filter.isFiltered(d)) { - return true; - } - } else if (filter <= d && filter >= d) { - return true; - } - } - return false; - }); - } + this.helper.applyFilters(this._conf.dimension, this.filters); } } diff --git a/src/data/c-f-multi-adapter.ts b/src/data/c-f-multi-adapter.ts index 982c60c8f..749b22e6c 100644 --- a/src/data/c-f-multi-adapter.ts +++ b/src/data/c-f-multi-adapter.ts @@ -35,8 +35,7 @@ export class CFMultiAdapter extends CFSimpleAdapter { // Two level defensive copy return this.layers().map(layer => { const valueAccessor = layer.valueAccessor || this._conf.valueAccessor; - // Two level defensive copy - const rawData = layer.group.all().map(val => ({ ...val, _value: valueAccessor(val) })); + const rawData = this._getData(this._conf.dimension, layer.group, valueAccessor); return { name: layer.name, rawData }; }); diff --git a/src/data/c-f-simple-adapter.ts b/src/data/c-f-simple-adapter.ts index d20e682db..4a507ac37 100644 --- a/src/data/c-f-simple-adapter.ts +++ b/src/data/c-f-simple-adapter.ts @@ -29,15 +29,17 @@ export class CFSimpleAdapter extends CFFilterHandler { // TODO: better typing public data(): any { - const entities = this._conf.group.all(); - - // create a two level deep copy defensively - entities.map(val => ({ ...val })); - - entities.forEach(e => { - e._value = this._conf.valueAccessor(e); - }); + return this._getData(this._conf.dimension, this._conf.group, this._conf.valueAccessor); + } - return entities; + protected _getData( + dimension: any, + group: MinimalCFGroup, + valueAccessor: (d: any, i?: number) => any + ) { + // create a two-level deep copy defensively + return this.helper + .getGroupings(dimension, group) + .map(grp => ({ ...grp, _value: valueAccessor(grp) })); } } diff --git a/src/data/cf-helper.ts b/src/data/cf-helper.ts new file mode 100644 index 000000000..3f93cef06 --- /dev/null +++ b/src/data/cf-helper.ts @@ -0,0 +1,46 @@ +import { MinimalCFDimension, MinimalCFGroup } from '../core/index.js'; +import { ICFHelper } from './i-c-f-helper.js'; + +export const cfHelper: ICFHelper = { + applyFilters: (dimension: MinimalCFDimension, filters: any[]) => { + if (!(dimension && dimension.filter)) { + return; + } + if (filters.length === 0) { + dimension.filter(null); + } else if (filters.length === 1 && !filters[0].isFiltered) { + // single value and not a function-based filter + dimension.filterExact(filters[0]); + } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') { + // single range-based filter + dimension.filterRange(filters[0]); + } else { + dimension.filterFunction(d => { + for (let i = 0; i < filters.length; i++) { + const filter = filters[i]; + if (filter.isFiltered) { + if (filter.isFiltered(d)) { + return true; + } + } else if (filter <= d && filter >= d) { + return true; + } + } + return false; + }); + } + }, + + storageKey: (provider: any) => { + const { shareFilters, dimension } = provider.conf(); + if (shareFilters) { + return dimension; + } else { + return provider; + } + }, + + getGroupings: (dimension: any, group: MinimalCFGroup) => { + return group.all(); + }, +}; diff --git a/src/data/i-c-f-helper.ts b/src/data/i-c-f-helper.ts new file mode 100644 index 000000000..f5d2709d7 --- /dev/null +++ b/src/data/i-c-f-helper.ts @@ -0,0 +1,7 @@ +import { CFGrouping, MinimalCFDimension, MinimalCFGroup } from '../core/index.js'; + +export interface ICFHelper { + applyFilters(dimension: MinimalCFDimension, filters: any[]): void; + storageKey(provider: any): any; + getGroupings(dimension: any, group: MinimalCFGroup): ReadonlyArray; +} diff --git a/src/data/index.ts b/src/data/index.ts index 9f0e5a2e0..d84207de9 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -4,3 +4,5 @@ export * from './c-f-multi-adapter.js'; export * from './c-f-simple-adapter.js'; export * from './filter-handler.js'; export * from './filter-storage-helper.js'; +export * from './i-c-f-helper.js'; +export * from './cf-helper.js';