/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* global Float32Array */ import SeriesData from '@/src/data/SeriesData'; import Model from '@/src/model/Model'; import { createSourceFromSeriesDataOption, Source, createSource } from '@/src/data/Source'; import { OptionDataItemObject, OptionDataValue, SOURCE_FORMAT_ARRAY_ROWS, SOURCE_FORMAT_ORIGINAL } from '@/src/util/types'; import SeriesDimensionDefine from '@/src/data/SeriesDimensionDefine'; import OrdinalMeta from '@/src/data/OrdinalMeta'; import DataStore from '@/src/data/DataStore'; import { DefaultDataProvider } from '@/src/data/helper/dataProvider'; import { SeriesDataSchema } from '@/src/data/helper/SeriesDataSchema'; const ID_PREFIX = 'e\0\0'; const NAME_REPEAT_PREFIX = '__ec__'; describe('SeriesData', function () { describe('Data Manipulation', function () { it('initData 1d', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([10, 20, 30]); expect(data.get('x', 0)).toEqual(10); expect(data.get('x', 1)).toEqual(20); expect(data.get('x', 2)).toEqual(30); expect(data.get('y', 1)).toEqual(20); }); it('initData 2d', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([[10, 15], [20, 25], [30, 35]]); expect(data.get('x', 1)).toEqual(20); expect(data.get('y', 1)).toEqual(25); }); it('initData 2d yx', function () { const data = new SeriesData(['y', 'x'], new Model()); data.initData([[10, 15], [20, 25], [30, 35]]); expect(data.get('x', 1)).toEqual(25); expect(data.get('y', 1)).toEqual(20); }); it('Data with option 1d', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([ 1, { value: 2, somProp: 'foo' } as OptionDataItemObject ]); expect(data.getItemModel(1).get('somProp' as any)).toEqual('foo'); expect(data.getItemModel(0).get('somProp' as any)).toBeNull(); }); it('Empty data', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([1, '-']); expect(data.get('y', 1)).toBeNaN(); }); it('getRawValue', function () { const data1 = new SeriesData(['x', 'y'], new Model()); // here construct a new data2 because if we only use one data // to call initData() twice, data._chunkCount will be accumulated // to 1 instead of 0. const data2 = new SeriesData(['x', 'y'], new Model()); data1.initData([1, 2, 3]); expect(data1.getItemModel(1).option).toEqual(2); data2.initData([[10, 15], [20, 25], [30, 35]]); expect(data2.getItemModel(1).option).toEqual([20, 25]); }); it('indexOfRawIndex', function () { const data = new SeriesData(['x'], new Model()); data.initData([]); expect(data.indexOfRawIndex(1)).toEqual(-1); const data1 = new SeriesData(['x'], new Model()); data1.initData([0]); expect(data1.indexOfRawIndex(0)).toEqual(0); expect(data1.indexOfRawIndex(1)).toEqual(-1); const data2 = new SeriesData(['x'], new Model()); data2.initData([0, 1, 2, 3]); expect(data2.indexOfRawIndex(1)).toEqual(1); expect(data2.indexOfRawIndex(2)).toEqual(2); expect(data2.indexOfRawIndex(5)).toEqual(-1); const data3 = new SeriesData(['x'], new Model()); data3.initData([0, 1, 2, 3, 4]); expect(data3.indexOfRawIndex(2)).toEqual(2); expect(data3.indexOfRawIndex(3)).toEqual(3); expect(data3.indexOfRawIndex(5)).toEqual(-1); data3.filterSelf(function (idx) { return idx >= 2; }); expect(data3.indexOfRawIndex(2)).toEqual(0); }); it('getDataExtent', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([1, 2, 3]); expect(data.getDataExtent('x')).toEqual([1, 3]); expect(data.getDataExtent('y')).toEqual([1, 3]); }); it('Data types', function () { const data = new SeriesData([{ name: 'x', type: 'int' }, { name: 'y', type: 'float' }], new Model()); data.initData([[1.1, 1.1]]); expect(data.get('x', 0)).toEqual(1); expect(data.get('y', 0)).toBeCloseTo(1.1, 5); }); it('map', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([[10, 15], [20, 25], [30, 35]]); expect(data.map(['x', 'y'], function (x: number, y: number) { return [x + 2, y + 2]; }).mapArray('x', function (x) { return x; })).toEqual([12, 22, 32]); }); it('mapArray', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([[10, 15], [20, 25], [30, 35]]); expect(data.mapArray(['x', 'y'], function (x, y) { return [x, y]; })).toEqual([[10, 15], [20, 25], [30, 35]]); }); it('filterSelf', function () { const data = new SeriesData(['x', 'y'], new Model()); data.initData([[10, 15], [20, 25], [30, 35]]); expect(data.filterSelf(['x', 'y'], function (x, y) { return x < 30 && x > 10; }).mapArray('x', function (x) { return x; })).toEqual([20]); }); it('dataProvider', function () { const data = new SeriesData(['x', 'y'], new Model()); const typedArray = new Float32Array([10, 10, 20, 20]); const source = createSourceFromSeriesDataOption(typedArray); data.initData({ count: function (): number { return typedArray.length / 2; }, getItem: function (idx: number): number[] { return [typedArray[idx * 2], typedArray[idx * 2 + 1]]; }, getSource: function (): Source { return source; } }); expect(data.mapArray(['x', 'y'], function (x, y) { return [x, y]; })).toEqual([[10, 10], [20, 20]]); expect(data.getRawDataItem(0)).toEqual([10, 10]); expect(data.getItemModel(0).option).toEqual([10, 10]); }); }); describe('Data store', function () { it('should guess ordinal correctly', function () { const source = createSource([['A', 15], ['B', 25], ['C', 35]], { dimensions: ['A', 'B'], seriesLayoutBy: null, sourceHeader: false }, SOURCE_FORMAT_ORIGINAL); expect(source.dimensionsDefine[0].type).toEqual('ordinal'); }); function createStore() { const provider = new DefaultDataProvider([['A', 15], ['B', 25], ['C', 35]]); const store = new DataStore(); store.initData(provider, [{type: 'ordinal'}, {type: 'float'}]); return store; } it('SeriesData can still get other dims value from store when only part of dims are given.', function () { const source = createSource( [['A', 15, 20, 'cat'], ['B', 25, 30, 'mouse'], ['C', 35, 40, 'dog']], { dimensions: null, seriesLayoutBy: null, sourceHeader: false }, SOURCE_FORMAT_ARRAY_ROWS ); const store = new DataStore(); store.initData(new DefaultDataProvider(source), [ {type: 'ordinal'}, {type: 'float'}, {type: 'float'}, {type: 'ordinal'} ]); const schema = new SeriesDataSchema({ source: source, dimensions: [ { type: 'float', name: 'dim1', storeDimIndex: 1 }, { type: 'ordinal', name: 'dim3', storeDimIndex: 3 } ], fullDimensionCount: 2, dimensionOmitted: true }); const data = new SeriesData(schema, null); data.initData(store); // Store should be the same. expect(data.getStore()).toBe(store); // Get self dim expect(data.get('dim1', 0)).toEqual(15); expect(data.get('dim1', 1)).toEqual(25); // Get other dim expect(data.getStore().get(0, 0)).toEqual('A'); expect(data.getStore().get(0, 1)).toEqual('B'); expect(data.getStore().get(2, 0)).toEqual(20); expect(data.getStore().get(2, 1)).toEqual(30); // Get all expect(data.getValues(['dim3', 'dim1'], 0)).toEqual(['cat', 15]); expect(data.getValues(1)).toEqual(['B', 25, 30, 'mouse']); }); it('SeriesData#cloneShallow should share store', function () { const store = createStore(); const dims = [{ type: 'float', name: 'dim2' }]; const data = new SeriesData(dims, null); data.initData(store); const data2 = data.cloneShallow(); expect(data2.getStore()).toBe(data.getStore()); }); }); describe('Data read', function () { it('indicesOfNearest', function () { const data = new SeriesData(['value'], new Model()); // ---- index: 0 1 2 3 4 5 6 7 data.initData([10, 20, 30, 35, 40, 40, 35, 50]); expect(data.indicesOfNearest('value', 24.5)).toEqual([1]); expect(data.indicesOfNearest('value', 25)).toEqual([1]); expect(data.indicesOfNearest('value', 25.5)).toEqual([2]); expect(data.indicesOfNearest('value', 25.5)).toEqual([2]); expect(data.indicesOfNearest('value', 41)).toEqual([4, 5]); expect(data.indicesOfNearest('value', 39)).toEqual([4, 5]); expect(data.indicesOfNearest('value', 41)).toEqual([4, 5]); expect(data.indicesOfNearest('value', 36)).toEqual([3, 6]); expect(data.indicesOfNearest('value', 50.6, 0.5)).toEqual([]); expect(data.indicesOfNearest('value', 50.5, 0.5)).toEqual([7]); }); }); describe('id_and_name', function () { function makeOneByOneChecker(list: SeriesData) { let getIdDataIndex = 0; let getNameDataIndex = 0; return { nextIdEqualsTo: function (expectedId: string): void { expect(list.getId(getIdDataIndex)).toEqual(expectedId); getIdDataIndex++; }, nextNameEqualsTo: function (expectedName: string): void { expect(list.getName(getNameDataIndex)).toEqual(expectedName); getNameDataIndex++; }, currGetIdDataIndex: function (): number { return getIdDataIndex; }, currGetNameDataIndex: function (): number { return getNameDataIndex; } }; } describe('only_name_declared', function () { function doChecks(list: SeriesData) { const oneByOne = makeOneByOneChecker(list); oneByOne.nextIdEqualsTo('a'); oneByOne.nextIdEqualsTo('b'); oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}2`); oneByOne.nextIdEqualsTo('c'); oneByOne.nextIdEqualsTo(`${ID_PREFIX}4`); oneByOne.nextIdEqualsTo(`c${NAME_REPEAT_PREFIX}2`); oneByOne.nextIdEqualsTo('d'); oneByOne.nextIdEqualsTo(`c${NAME_REPEAT_PREFIX}3`); oneByOne.nextNameEqualsTo('a'); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('c'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('c'); oneByOne.nextNameEqualsTo('d'); oneByOne.nextNameEqualsTo('c'); } it('sourceFormatOriginal', function () { const list = new SeriesData(['x', 'y'], new Model()); list.initData([ { value: 10, name: 'a' }, { value: 20, name: 'b' }, { value: 30, name: 'b' }, { value: 40, name: 'c' }, { value: 50 }, // name not declared { value: 60, name: 'c' }, { value: 70, name: 'd' }, { value: 80, name: 'c' } ]); doChecks(list); }); it('sourceFormatArrayRows', function () { const list = new SeriesData( [ 'x', { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } ], new Model() ); const source = createSource( [ [ 10, 'a' ], [ 20, 'b' ], [ 30, 'b' ], [ 40, 'c' ], [ 50, null ], [ 60, 'c' ], [ 70, 'd' ], [ 80, 'c' ] ], { seriesLayoutBy: 'column', sourceHeader: 0, dimensions: null }, SOURCE_FORMAT_ARRAY_ROWS ); list.initData(source); doChecks(list); }); }); describe('id_name_declared_sourceFormat_original', function () { it('sourceFormatOriginal', function () { const list = new SeriesData(['x'], new Model()); const oneByOne = makeOneByOneChecker(list); list.initData([ { value: 0, id: 'myId_10' }, { value: 10, id: 555 }, // numeric id. { value: 20, id: '666%' }, { value: 30, id: 'myId_good', name: 'b' }, { value: 40, name: 'b' }, { value: 50, id: null }, { value: 60, id: undefined }, { value: 70, id: NaN }, { value: 80, id: '' }, { value: 90, name: 'b' }, { value: 100 }, { value: 110, id: 'myId_better' }, { value: 120, id: 'myId_better' } // duplicated id. ]); oneByOne.nextIdEqualsTo('myId_10'); oneByOne.nextIdEqualsTo('555'); oneByOne.nextIdEqualsTo('666%'); oneByOne.nextIdEqualsTo('myId_good'); oneByOne.nextIdEqualsTo('b'); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo('NaN'); oneByOne.nextIdEqualsTo(''); oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}2`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo('myId_better'); oneByOne.nextIdEqualsTo('myId_better'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); list.appendData([ { value: 200, id: 'myId_best' }, { value: 210, id: 999 }, // numeric id. { value: 220, id: '777px' }, { value: 230, name: 'b' }, { value: 240 } ]); oneByOne.nextIdEqualsTo('myId_best'); oneByOne.nextIdEqualsTo('999'); oneByOne.nextIdEqualsTo('777px'); oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}3`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); list.appendValues([], ['b', 'c', null]); oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}4`); oneByOne.nextIdEqualsTo('c'); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('c'); oneByOne.nextNameEqualsTo(''); }); }); describe('id_name_declared_sourceFormat_arrayRows', function () { it('no_ordinalMeta', function () { testArrayRowsInSource([ { name: 'x', type: 'number' }, { name: 'p', type: 'ordinal', otherDims: { itemId: 0 } }, { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } ]); }); it('has_ordinalMeta', function () { const ordinalMetaP = new OrdinalMeta({ categories: [], needCollect: true, deduplication: true }); const ordinalMetaQ = new OrdinalMeta({ categories: [], needCollect: true, deduplication: true }); testArrayRowsInSource([ { name: 'x', type: 'number' }, { name: 'p', type: 'ordinal', otherDims: { itemId: 0 }, ordinalMeta: ordinalMetaP }, { name: 'q', type: 'ordinal', otherDims: { itemName: 0 }, ordinalMeta: ordinalMetaQ } ]); }); function testArrayRowsInSource(dimensionsInfo: SeriesDimensionDefine[]): void { const list = new SeriesData(dimensionsInfo, new Model()); const oneByOne = makeOneByOneChecker(list); const source = createSource( [ [0, 'myId_10', null], [10, 555, null], // numeric id. [20, '666%', null], [30, 'myId_good', 'b'], [40, null, 'b'], [50, null, null], [60, undefined, null], [70, NaN, null], [80, '', null], [90, null, 'b'], [100, null, null], [110, 'myId_better', null], [120, 'myId_better', null] // duplicated id. ], { seriesLayoutBy: 'column', sourceHeader: 0, dimensions: null }, SOURCE_FORMAT_ARRAY_ROWS ); list.initData(source); oneByOne.nextIdEqualsTo('myId_10'); oneByOne.nextIdEqualsTo('555'); oneByOne.nextIdEqualsTo('666%'); oneByOne.nextIdEqualsTo('myId_good'); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo('NaN'); oneByOne.nextIdEqualsTo(''); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo('myId_better'); oneByOne.nextIdEqualsTo('myId_better'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); list.appendData([ [ 200, 'myId_best', null ], [ 210, 999, null ], // numeric id. [ 220, '777px', null], [ 230, null, 'b' ], [ 240, null, null ] ]); oneByOne.nextIdEqualsTo('myId_best'); oneByOne.nextIdEqualsTo('999'); oneByOne.nextIdEqualsTo('777px'); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo(''); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo(''); list.appendValues([], ['b', 'c', null]); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); oneByOne.nextNameEqualsTo('b'); oneByOne.nextNameEqualsTo('c'); oneByOne.nextNameEqualsTo(''); } }); }); });