FieldValues: Use plain arrays instead of Vector (part 1 of 2) (#66187)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Ryan McKinley 2023-04-14 05:36:53 -07:00 committed by GitHub
parent 4f5b80095e
commit b8188eead4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 531 additions and 292 deletions

View File

@ -285,6 +285,9 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"] [0, 0, 0, "Unexpected any. Specify a different type.", "3"]
], ],
"packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"packages/grafana-data/src/transformations/transformers/merge.ts:5381": [ "packages/grafana-data/src/transformations/transformers/merge.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
@ -484,8 +487,17 @@ exports[`better eslint`] = {
], ],
"packages/grafana-data/src/types/vector.ts:5381": [ "packages/grafana-data/src/types/vector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"] [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Do not use any type assertions.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"],
[0, 0, 0, "Unexpected any. Specify a different type.", "11"]
], ],
"packages/grafana-data/src/utils/OptionsUIBuilders.ts:5381": [ "packages/grafana-data/src/utils/OptionsUIBuilders.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
@ -648,10 +660,13 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "2"] [0, 0, 0, "Do not use any type assertions.", "2"]
], ],
"packages/grafana-data/src/vector/ArrayVector.ts:5381": [ "packages/grafana-data/src/vector/ArrayVector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
], ],
"packages/grafana-data/src/vector/CircularVector.ts:5381": [ "packages/grafana-data/src/vector/CircularVector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
], ],
"packages/grafana-data/src/vector/ConstantVector.ts:5381": [ "packages/grafana-data/src/vector/ConstantVector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
@ -661,7 +676,16 @@ exports[`better eslint`] = {
], ],
"packages/grafana-data/src/vector/FunctionalVector.ts:5381": [ "packages/grafana-data/src/vector/FunctionalVector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"] [0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"],
[0, 0, 0, "Unexpected any. Specify a different type.", "10"]
], ],
"packages/grafana-data/src/vector/SortedVector.ts:5381": [ "packages/grafana-data/src/vector/SortedVector.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]

View File

@ -1,4 +1,4 @@
import { Vector, QueryResultMeta } from '../types'; import { QueryResultMeta } from '../types';
import { Field, FieldType, DataFrame } from '../types/dataFrame'; import { Field, FieldType, DataFrame } from '../types/dataFrame';
import { FunctionalVector } from '../vector/FunctionalVector'; import { FunctionalVector } from '../vector/FunctionalVector';
import { vectorToArray } from '../vector/vectorToArray'; import { vectorToArray } from '../vector/vectorToArray';
@ -10,10 +10,12 @@ export type ValueConverter<T = any> = (val: unknown) => T;
const NOOP: ValueConverter = (v) => v; const NOOP: ValueConverter = (v) => v;
class ArrayPropertyVector<T = any> implements Vector<T> { class ArrayPropertyVector<T = any> extends FunctionalVector<T> {
converter = NOOP; converter = NOOP;
constructor(private source: any[], private prop: string) {} constructor(private source: any[], private prop: string) {
super();
}
get length(): number { get length(): number {
return this.source.length; return this.source.length;

View File

@ -235,7 +235,7 @@ describe('DataFrame JSON', () => {
}, },
}; };
expect(dataFrameToJSON(inputFrame)).toStrictEqual(expectedJSON); expect(dataFrameToJSON(inputFrame)).toEqual(expectedJSON);
}); });
}); });
}); });

View File

@ -2,20 +2,20 @@ import { isString } from 'lodash';
import { QueryResultMeta } from '../types/data'; import { QueryResultMeta } from '../types/data';
import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame'; import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame';
import { MutableVector, Vector } from '../types/vector'; import { Vector } from '../types/vector';
import { makeFieldParser } from '../utils/fieldParser'; import { makeFieldParser } from '../utils/fieldParser';
import { ArrayVector } from '../vector/ArrayVector'; import { ArrayVector } from '../vector/ArrayVector';
import { FunctionalVector } from '../vector/FunctionalVector'; import { FunctionalVector } from '../vector/FunctionalVector';
import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame'; import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame';
export type MutableField<T = any> = Field<T, MutableVector<T>>; export type MutableField<T = any> = Field<T>;
type MutableVectorCreator = (buffer?: any[]) => MutableVector; type MutableVectorCreator = (buffer?: any[]) => Vector;
export const MISSING_VALUE = undefined; // Treated as connected in new graph panel export const MISSING_VALUE = undefined; // Treated as connected in new graph panel
export class MutableDataFrame<T = any> extends FunctionalVector<T> implements DataFrame, MutableVector<T> { export class MutableDataFrame<T = any> extends FunctionalVector<T> implements DataFrame {
name?: string; name?: string;
refId?: string; refId?: string;
meta?: QueryResultMeta; meta?: QueryResultMeta;
@ -148,15 +148,6 @@ export class MutableDataFrame<T = any> extends FunctionalVector<T> implements Da
} }
} }
/**
* Reverse all values
*/
reverse() {
for (const f of this.fields) {
f.values.reverse();
}
}
private parsers: Map<Field, (v: string) => any> | undefined = undefined; private parsers: Map<Field, (v: string) => any> | undefined = undefined;
/** /**
@ -210,10 +201,25 @@ export class MutableDataFrame<T = any> extends FunctionalVector<T> implements Da
} }
} }
/** support standard array push syntax */
push(...vals: T[]): number {
for (const v of vals) {
this.add(v);
}
return this.length;
}
reverse() {
for (const field of this.fields) {
field.values.reverse();
}
return this;
}
/** /**
* Add values from an object to corresponding fields. Similar to appendRow but does not create new fields. * Add values from an object to corresponding fields. Similar to appendRow but does not create new fields.
*/ */
add(value: T) { add(value: T): void {
// Will add one value for every field // Will add one value for every field
const obj = value as any; const obj = value as any;
for (const field of this.fields) { for (const field of this.fields) {

View File

@ -332,7 +332,7 @@ describe('align frames', () => {
const v = new ArrayVector([null, null, null, 4, null]); const v = new ArrayVector([null, null, null, 4, null]);
expect(isLikelyAscendingVector(v)).toBeTruthy(); expect(isLikelyAscendingVector(v)).toBeTruthy();
expect(isLikelyAscendingVector(new ArrayVector([4]))).toBeTruthy(); expect(isLikelyAscendingVector(new ArrayVector([4]))).toBeTruthy();
expect(isLikelyAscendingVector(new ArrayVector([]))).toBeTruthy(); expect(isLikelyAscendingVector(new ArrayVector([] as any[]))).toBeTruthy();
}); });
it('middle values', () => { it('middle values', () => {

View File

@ -418,7 +418,7 @@ describe('Reducer Transformer', () => {
"CA", "CA",
"NY", "NY",
"CA", "CA",
, undefined,
], ],
}, },
{ {
@ -426,8 +426,8 @@ describe('Reducer Transformer', () => {
"name": "country", "name": "country",
"type": "string", "type": "string",
"values": [ "values": [
, undefined,
, undefined,
"USA", "USA",
"USA", "USA",
], ],

View File

@ -136,7 +136,13 @@ export interface Field<T = any, V = Vector<T>> {
* Meta info about how field and how to display it * Meta info about how field and how to display it
*/ */
config: FieldConfig; config: FieldConfig;
values: V; // The raw field values
/**
* The raw field values
* In Grafana 10, this accepts both simple arrays and the Vector interface
* In Grafana 11, the Vector interface will be removed
*/
values: V | T[];
/** /**
* When type === FieldType.Time, this can optionally store * When type === FieldType.Time, this can optionally store

View File

@ -1,35 +1,99 @@
export interface Vector<T = any> { declare global {
interface Array<T> {
/** @deprecated this only exists to help migrate Vector to Array */
get(idx: number): T;
/** @deprecated this only exists to help migrate Vector to Array */
set(idx: number, value: T): void;
/** @deprecated this only exists to help migrate Vector to Array */
add(value: T): void;
/** @deprecated this only exists to help migrate Vector to Array */
toArray(): T[];
}
}
// JS original sin
// this if condition is because Jest will re-exec this block multiple times (in a browser this only runs once)
if (!Object.getOwnPropertyDescriptor(Array.prototype, 'toArray')) {
Object.defineProperties(Array.prototype, {
get: {
value: function (idx: number): any {
return (this as any)[idx];
},
writable: false,
enumerable: false,
configurable: false,
},
set: {
value: function (idx: number, value: any) {
(this as any)[idx] = value;
},
writable: false,
enumerable: false,
configurable: false,
},
add: {
value: function (value: any) {
(this as any).push(value);
},
writable: false,
enumerable: false,
configurable: false,
},
toArray: {
value: function () {
return this;
},
writable: false,
enumerable: false,
configurable: false,
},
});
}
/** @deprecated use a simple Array<T> */
export interface Vector<T = any> extends Array<T> {
length: number; length: number;
/** /**
* Access the value by index (Like an array) * Access the value by index (Like an array)
*
* @deprecated use a simple Array<T>
*/ */
get(index: number): T; get(index: number): T;
/**
* Set a value
*
* @deprecated use a simple Array<T>
*/
set: (index: number, value: T) => void;
/**
* Adds the value to the vector
* Same as Array.push()
*
* @deprecated use a simple Array<T>
*/
add: (value: T) => void;
/** /**
* Get the results as an array. * Get the results as an array.
*
* @deprecated use a simple Array<T>
*/ */
toArray(): T[]; toArray(): T[];
} }
/** /**
* Apache arrow vectors are Read/Write * Apache arrow vectors are Read/Write
*
* @deprecated -- this is now part of the base Vector interface
*/ */
export interface ReadWriteVector<T = any> extends Vector<T> { export interface ReadWriteVector<T = any> extends Vector<T> {}
set: (index: number, value: T) => void;
}
/** /**
* Vector with standard manipulation functions * Vector with standard manipulation functions
*
* @deprecated -- this is now part of the base Vector interface
*/ */
export interface MutableVector<T = any> extends ReadWriteVector<T> { export interface MutableVector<T = any> extends ReadWriteVector<T> {}
/**
* Adds the value to the vector
*/
add: (value: T) => void;
/**
* modifies the vector so it is now the opposite order
*/
reverse: () => void;
}

View File

@ -1,5 +1,6 @@
import { Vector } from '../types/vector'; import { Vector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray'; import { vectorToArray } from './vectorToArray';
interface AppendedVectorInfo<T> { interface AppendedVectorInfo<T> {
@ -13,11 +14,12 @@ interface AppendedVectorInfo<T> {
* RAM -- rather than allocate a new array the size of all previous arrays, this just * RAM -- rather than allocate a new array the size of all previous arrays, this just
* points the correct index to their original array values * points the correct index to their original array values
*/ */
export class AppendedVectors<T = any> implements Vector<T> { export class AppendedVectors<T = any> extends FunctionalVector<T> {
length = 0; length = 0;
source: Array<AppendedVectorInfo<T>> = []; source: Array<AppendedVectorInfo<T>> = [];
constructor(startAt = 0) { constructor(startAt = 0) {
super();
this.length = startAt; this.length = startAt;
} }

View File

@ -0,0 +1,41 @@
import { Field, FieldType } from '../types';
import { ArrayVector } from './ArrayVector';
describe('ArrayVector', () => {
it('should init 150k with 65k Array.push() chonking', () => {
const arr = Array.from({ length: 150e3 }, (v, i) => i);
const av = new ArrayVector(arr);
expect(av.toArray()).toEqual(arr);
});
it('should support add and push', () => {
const av = new ArrayVector<number>();
av.add(1);
av.push(2);
av.push(3, 4);
expect(av.toArray()).toEqual([1, 2, 3, 4]);
});
it('typescript should not re-define the ArrayVector<T> based on input to the constructor', () => {
const field: Field<number> = {
name: 'test',
config: {},
type: FieldType.number,
values: new ArrayVector(), // this defaults to `new ArrayVector<any>()`
};
expect(field).toBeDefined();
// Before collapsing Vector, ReadWriteVector, and MutableVector these all worked fine
field.values = new ArrayVector();
field.values = new ArrayVector(undefined);
field.values = new ArrayVector([1, 2, 3]);
field.values = new ArrayVector([]);
field.values = new ArrayVector([1, undefined]);
field.values = new ArrayVector([null]);
field.values = new ArrayVector(['a', 'b', 'c']);
expect(field.values.length).toBe(3);
});
});

View File

@ -1,43 +1,38 @@
import { MutableVector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
/** /**
* @public * @public
*
* @deprecated use a simple Array<T>
*/ */
export class ArrayVector<T = any> extends FunctionalVector<T> implements MutableVector<T> { export class ArrayVector<T = any> extends Array<T> {
buffer: T[]; get buffer() {
return this;
}
constructor(buffer?: T[]) { set buffer(values: any[]) {
this.length = 0;
const len = values?.length;
if (len) {
let chonkSize = 65e3;
let numChonks = Math.ceil(len / chonkSize);
for (let chonkIdx = 0; chonkIdx < numChonks; chonkIdx++) {
this.push.apply(this, values.slice(chonkIdx * chonkSize, (chonkIdx + 1) * chonkSize));
}
}
}
/**
* This any type is here to make the change type changes in v10 non breaking for plugins.
* Before you could technically assign field.values any typed ArrayVector no matter what the Field<T> T type was.
*/
constructor(buffer?: any[]) {
super(); super();
this.buffer = buffer ? buffer : []; this.buffer = buffer ?? [];
}
get length() {
return this.buffer.length;
}
add(value: T) {
this.buffer.push(value);
}
get(index: number): T {
return this.buffer[index];
}
set(index: number, value: T) {
this.buffer[index] = value;
}
reverse() {
this.buffer.reverse();
}
toArray(): T[] {
return this.buffer;
} }
toJSON(): T[] { toJSON(): T[] {
return this.buffer; return [...this]; // copy to avoid circular reference (only for jest)
} }
} }

View File

@ -1,13 +1,16 @@
import { Vector } from '../types/vector'; import { Vector } from '../types/vector';
import { BinaryOperation } from '../utils/binaryOperators'; import { BinaryOperation } from '../utils/binaryOperators';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray'; import { vectorToArray } from './vectorToArray';
/** /**
* @public * @public
*/ */
export class BinaryOperationVector implements Vector<number> { export class BinaryOperationVector extends FunctionalVector<number> {
constructor(private left: Vector<number>, private right: Vector<number>, private operation: BinaryOperation) {} constructor(private left: Vector<number>, private right: Vector<number>, private operation: BinaryOperation) {
super();
}
get length(): number { get length(): number {
return this.left.length; return this.left.length;

View File

@ -20,6 +20,9 @@ describe('Check Circular Vector', () => {
v.add(8); v.add(8);
expect(v.toArray()).toEqual([6, 7, 8]); expect(v.toArray()).toEqual([6, 7, 8]);
v.push(9, 10);
expect(v.toArray()).toEqual([8, 9, 10]);
}); });
it('should grow buffer until it hits capacity (append)', () => { it('should grow buffer until it hits capacity (append)', () => {

View File

@ -1,7 +1,4 @@
import { MutableVector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector'; import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
interface CircularOptions<T> { interface CircularOptions<T> {
buffer?: T[]; buffer?: T[];
@ -18,7 +15,7 @@ interface CircularOptions<T> {
* *
* @public * @public
*/ */
export class CircularVector<T = any> extends FunctionalVector<T> implements MutableVector<T> { export class CircularVector<T = any> extends FunctionalVector<T> {
private buffer: T[]; private buffer: T[];
private index: number; private index: number;
private capacity: number; private capacity: number;
@ -43,7 +40,7 @@ export class CircularVector<T = any> extends FunctionalVector<T> implements Muta
* * head vs tail * * head vs tail
* * growing buffer vs overwriting values * * growing buffer vs overwriting values
*/ */
private getAddFunction() { private getAddFunction(): (value: T) => void {
// When we are not at capacity, it should actually modify the buffer // When we are not at capacity, it should actually modify the buffer
if (this.capacity > this.buffer.length) { if (this.capacity > this.buffer.length) {
if (this.tail) { if (this.tail) {
@ -114,31 +111,18 @@ export class CircularVector<T = any> extends FunctionalVector<T> implements Muta
} }
reverse() { reverse() {
this.buffer.reverse(); return this.buffer.reverse();
} }
/**
* Add the value to the buffer
*/
add: (value: T) => void;
get(index: number) { get(index: number) {
return this.buffer[(index + this.index) % this.buffer.length]; return this.buffer[(index + this.index) % this.buffer.length];
} }
set(index: number, value: T) { set(index: number, value: any) {
this.buffer[(index + this.index) % this.buffer.length] = value; this.buffer[(index + this.index) % this.buffer.length] = value;
} }
get length() { get length() {
return this.buffer.length; return this.buffer.length;
} }
toArray(): T[] {
return vectorToArray(this);
}
toJSON(): T[] {
return vectorToArray(this);
}
} }

View File

@ -1,10 +1,12 @@
import { Vector } from '../types/vector'; import { FunctionalVector } from './FunctionalVector';
/** /**
* @public * @public
*/ */
export class ConstantVector<T = any> implements Vector<T> { export class ConstantVector<T = any> extends FunctionalVector<T> {
constructor(private value: T, private len: number) {} constructor(private value: T, private len: number) {
super();
}
get length() { get length() {
return this.len; return this.len;

View File

@ -3,7 +3,7 @@ import { Vector } from '../types';
import { vectorToArray } from './vectorToArray'; import { vectorToArray } from './vectorToArray';
/** @public */ /** @public */
export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T> { export abstract class FunctionalVector<T = any> implements Vector<T> {
abstract get length(): number; abstract get length(): number;
abstract get(index: number): T; abstract get(index: number): T;
@ -15,31 +15,175 @@ export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T
} }
} }
set(index: number, value: any): void {
throw 'unsupported operation';
}
add(value: T): void {
throw 'unsupported operation';
}
push(...vals: T[]): number {
for (const v of vals) {
this.add(v);
}
return this.length;
}
// Implement "iterable protocol" // Implement "iterable protocol"
[Symbol.iterator]() { [Symbol.iterator]() {
return this.iterator(); return this.iterator();
} }
forEach(iterator: (row: T) => void) { forEach(iterator: (row: T, index: number, array: T[]) => void): void {
return vectorator(this).forEach(iterator); return vectorator(this).forEach(iterator);
} }
map<V>(transform: (item: T, index: number) => V) { map<V>(transform: (item: T, index: number, array: T[]) => V): V[] {
return vectorator(this).map(transform); return vectorator(this).map(transform);
} }
filter(predicate: (item: T) => boolean): T[] { filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] {
return vectorator(this).filter(predicate); return vectorator(this).filter(predicate);
} }
at(index: number): T | undefined {
return this.get(index);
}
toArray(): T[] { toArray(): T[] {
return vectorToArray(this); return vectorToArray(this);
} }
join(separator?: string | undefined): string {
return this.toArray().join(separator);
}
toJSON(): any { toJSON(): any {
return this.toArray(); return this.toArray();
} }
//--------------------------
// Method not implemented
//--------------------------
[n: number]: T;
pop(): T | undefined {
throw new Error('Method not implemented.');
} }
concat(...items: Array<ConcatArray<T>>): T[];
concat(...items: Array<T | ConcatArray<T>>): T[] {
throw new Error('Method not implemented.');
}
reverse(): T[] {
throw new Error('Method not implemented.');
}
shift(): T | undefined {
throw new Error('Method not implemented.');
}
slice(start?: number | undefined, end?: number | undefined): T[] {
throw new Error('Method not implemented.');
}
sort(compareFn?: ((a: T, b: T) => number) | undefined): this {
throw new Error('Method not implemented.');
}
splice(start: number, deleteCount?: number | undefined): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[] {
throw new Error('Method not implemented.');
}
unshift(...items: T[]): number {
throw new Error('Method not implemented.');
}
fill(value: T, start?: number | undefined, end?: number | undefined): this {
throw new Error('Method not implemented.');
}
copyWithin(target: number, start: number, end?: number | undefined): this {
throw new Error('Method not implemented.');
}
[Symbol.unscopables](): {
copyWithin: boolean;
entries: boolean;
fill: boolean;
find: boolean;
findIndex: boolean;
keys: boolean;
values: boolean;
} {
throw new Error('Method not implemented.');
}
//--------------------------------------------------------------------------------
// Delegated Array function -- these will not be efficient :grimmice:
//--------------------------------------------------------------------------------
indexOf(searchElement: T, fromIndex?: number | undefined): number {
return this.toArray().indexOf(searchElement, fromIndex);
}
lastIndexOf(searchElement: T, fromIndex?: number | undefined): number {
return this.toArray().lastIndexOf(searchElement, fromIndex);
}
every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
every(predicate: any, thisArg?: unknown): boolean {
return this.toArray().every(predicate, thisArg);
}
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean {
return this.toArray().some(predicate, thisArg);
}
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
reduce(callbackfn: unknown, initialValue?: unknown): T {
throw new Error('Method not implemented.');
}
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T,
initialValue: T
): T;
reduceRight<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U,
initialValue: U
): U;
reduceRight(callbackfn: unknown, initialValue?: unknown): T {
throw new Error('Method not implemented.');
}
find<S extends T>(
predicate: (this: void, value: T, index: number, obj: T[]) => value is S,
thisArg?: any
): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined {
return this.toArray().find(predicate, thisArg);
}
findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number {
return this.toArray().findIndex(predicate, thisArg);
}
entries(): IterableIterator<[number, T]> {
return this.toArray().entries();
}
keys(): IterableIterator<number> {
return this.toArray().keys();
}
values(): IterableIterator<T> {
return this.toArray().values();
}
includes(searchElement: T, fromIndex?: number | undefined): boolean {
return this.toArray().includes(searchElement, fromIndex);
}
flatMap<U, This = undefined>(
callback: (this: This, value: T, index: number, array: T[]) => U | readonly U[],
thisArg?: This | undefined
): U[] {
return this.toArray().flatMap(callback, thisArg);
}
flat<A, D extends number = 1>(this: A, depth?: D | undefined): Array<FlatArray<A, D>> {
throw new Error('Method not implemented.');
}
}
const emptyarray: any[] = [];
/** /**
* Use functional programming with your vector * Use functional programming with your vector
@ -52,25 +196,26 @@ export function vectorator<T>(vector: Vector<T>) {
} }
}, },
forEach(iterator: (row: T) => void) { forEach(iterator: (row: T, index: number, array: T[]) => void): void {
for (let i = 0; i < vector.length; i++) { for (let i = 0; i < vector.length; i++) {
iterator(vector.get(i)); iterator(vector.get(i), i, emptyarray);
} }
}, },
map<V>(transform: (item: T, index: number) => V) { map<V>(transform: (item: T, index: number, array: T[]) => V): V[] {
const result: V[] = []; const result: V[] = [];
for (let i = 0; i < vector.length; i++) { for (let i = 0; i < vector.length; i++) {
result.push(transform(vector.get(i), i)); result.push(transform(vector.get(i), i, emptyarray));
} }
return result; return result;
}, },
/** Add a predicate where you return true if it should *keep* the value */ /** Add a predicate where you return true if it should *keep* the value */
filter(predicate: (item: T) => boolean): T[] { filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] {
const result: T[] = []; const result: T[] = [];
let count = 0;
for (const val of this) { for (const val of this) {
if (predicate(val)) { if (predicate(val, count++, emptyarray)) {
result.push(val); result.push(val);
} }
} }

View File

@ -6,6 +6,8 @@ import { FunctionalVector } from './FunctionalVector';
* IndexVector is a simple vector implementation that returns the index value * IndexVector is a simple vector implementation that returns the index value
* for each element in the vector. It is functionally equivolant a vector backed * for each element in the vector. It is functionally equivolant a vector backed
* by an array with values: `[0,1,2,...,length-1]` * by an array with values: `[0,1,2,...,length-1]`
*
* @deprecated use a simple Arrays
*/ */
export class IndexVector extends FunctionalVector<number> { export class IndexVector extends FunctionalVector<number> {
constructor(private len: number) { constructor(private len: number) {

View File

@ -1,13 +1,16 @@
import { Vector } from '../types'; import { Vector } from '../types';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray'; import { vectorToArray } from './vectorToArray';
/** /**
* RowVector makes the row values look like a vector * RowVector makes the row values look like a vector
* @internal * @internal
*/ */
export class RowVector implements Vector { export class RowVector extends FunctionalVector<number> {
constructor(private columns: Vector[]) {} constructor(private columns: Vector[]) {
super();
}
rowIndex = 0; rowIndex = 0;

View File

@ -1,12 +1,17 @@
import { Vector } from '../types/vector'; import { Vector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray'; import { vectorToArray } from './vectorToArray';
/** /**
* Values are returned in the order defined by the input parameter * Values are returned in the order defined by the input parameter
*
* @deprecated use a simple Arrays
*/ */
export class SortedVector<T = any> implements Vector<T> { export class SortedVector<T = any> extends FunctionalVector<T> {
constructor(private source: Vector<T>, private order: number[]) {} constructor(private source: Vector<T>, private order: number[]) {
super();
}
get length(): number { get length(): number {
return this.source.length; return this.source.length;

View File

@ -1,5 +1,6 @@
import { Vector } from '../types/vector'; import { Vector } from '../types/vector';
/** @deprecated use a simple Arrays */
export function vectorToArray<T>(v: Vector<T>): T[] { export function vectorToArray<T>(v: Vector<T>): T[] {
const arr: T[] = Array(v.length); const arr: T[] = Array(v.length);
for (let i = 0; i < v.length; i++) { for (let i = 0; i < v.length; i++) {

View File

@ -59,9 +59,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df }); const result = applyNullInsertThreshold({ frame: df });
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, null, null, null, null, null, 8]); expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c']);
}); });
test('should insert nulls at +threshold between adjacent > threshold: 2', () => { test('should insert nulls at +threshold between adjacent > threshold: 2', () => {
@ -76,9 +76,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df }); const result = applyNullInsertThreshold({ frame: df });
expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]); expect(result.fields[0].values).toEqual([5, 7, 9, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]); expect(result.fields[1].values).toEqual([4, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']); expect(result.fields[2].values).toEqual(['a', 'b', null, 'c']);
}); });
test('should insert nulls at +interval between adjacent > interval: 1', () => { test('should insert nulls at +interval between adjacent > interval: 1', () => {
@ -93,9 +93,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df }); const result = applyNullInsertThreshold({ frame: df });
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, null, null, null, null, null, 8]); expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, null, null, null, null, null, 'c']); expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c']);
}); });
test('should insert leading null at beginning +interval when timeRange.from.valueOf() exceeds threshold', () => { test('should insert leading null at beginning +interval when timeRange.from.valueOf() exceeds threshold', () => {
@ -115,8 +115,8 @@ describe('nullInsertThreshold Transformer', () => {
refFieldPseudoMax: 13, refFieldPseudoMax: 13,
}); });
expect(result.fields[0].values.toArray()).toStrictEqual([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]); expect(result.fields[0].values).toEqual([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
expect(result.fields[1].values.toArray()).toStrictEqual([ expect(result.fields[1].values).toEqual([
null, null,
null, null,
null, null,
@ -133,7 +133,7 @@ describe('nullInsertThreshold Transformer', () => {
null, null,
8, 8,
]); ]);
expect(result.fields[2].values.toArray()).toStrictEqual([ expect(result.fields[2].values).toEqual([
null, null,
null, null,
null, null,
@ -164,35 +164,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 13 }); const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 13 });
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
expect(result.fields[1].values.toArray()).toStrictEqual([ expect(result.fields[1].values).toEqual([4, null, 6, null, null, null, null, null, null, 8, null, null]);
4, expect(result.fields[2].values).toEqual(['a', null, 'b', null, null, null, null, null, null, 'c', null, null]);
null,
6,
null,
null,
null,
null,
null,
null,
8,
null,
null,
]);
expect(result.fields[2].values.toArray()).toStrictEqual([
'a',
null,
'b',
null,
null,
null,
null,
null,
null,
'c',
null,
null,
]);
// should work for frames with 1 datapoint // should work for frames with 1 datapoint
const df2 = new MutableDataFrame({ const df2 = new MutableDataFrame({
@ -208,9 +182,9 @@ describe('nullInsertThreshold Transformer', () => {
// we get 12 nulls instead of the additional 1 // we get 12 nulls instead of the additional 1
const result2 = applyNullInsertThreshold({ frame: df2, refFieldName: null, refFieldPseudoMax: 2.5 }); const result2 = applyNullInsertThreshold({ frame: df2, refFieldName: null, refFieldPseudoMax: 2.5 });
expect(result2.fields[0].values.toArray()).toStrictEqual([1, 2]); expect(result2.fields[0].values).toEqual([1, 2]);
expect(result2.fields[1].values.toArray()).toStrictEqual([1, null]); expect(result2.fields[1].values).toEqual([1, null]);
expect(result2.fields[2].values.toArray()).toStrictEqual(['a', null]); expect(result2.fields[2].values).toEqual(['a', null]);
}); });
test('should not insert trailing null at end +interval when timeRange.to.valueOf() equals threshold', () => { test('should not insert trailing null at end +interval when timeRange.to.valueOf() equals threshold', () => {
@ -225,9 +199,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 2 }); const result = applyNullInsertThreshold({ frame: df, refFieldName: null, refFieldPseudoMax: 2 });
expect(result.fields[0].values.toArray()).toStrictEqual([1]); expect(result.fields[0].values).toEqual([1]);
expect(result.fields[1].values.toArray()).toStrictEqual([1]); expect(result.fields[1].values).toEqual([1]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a']); expect(result.fields[2].values).toEqual(['a']);
}); });
// TODO: make this work // TODO: make this work
@ -243,9 +217,9 @@ describe('nullInsertThreshold Transformer', () => {
const result = applyNullInsertThreshold({ frame: df }); const result = applyNullInsertThreshold({ frame: df });
expect(result.fields[0].values.toArray()).toStrictEqual([5, 6, 7, 8, 11]); expect(result.fields[0].values).toEqual([5, 6, 7, 8, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]); expect(result.fields[1].values).toEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']); expect(result.fields[2].values).toEqual(['a', null, 'b', null, 'c']);
}); });
test('should noop on 0 datapoints', () => { test('should noop on 0 datapoints', () => {

View File

@ -26,9 +26,9 @@ describe('nullToValue Transformer', () => {
const result = nullToValue(applyNullInsertThreshold({ frame: df })); const result = nullToValue(applyNullInsertThreshold({ frame: df }));
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 0, 6, 0, 0, 0, 0, 0, 0, 8]); expect(result.fields[1].values).toEqual([4, 0, 6, 0, 0, 0, 0, 0, 0, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 0, 'b', 0, 0, 0, 0, 0, 0, 'c']); expect(result.fields[2].values).toEqual(['a', 0, 'b', 0, 0, 0, 0, 0, 0, 'c']);
}); });
test('should change all nulls to configured positive value', () => { test('should change all nulls to configured positive value', () => {
@ -53,9 +53,9 @@ describe('nullToValue Transformer', () => {
const result = nullToValue(applyNullInsertThreshold({ frame: df })); const result = nullToValue(applyNullInsertThreshold({ frame: df }));
expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]); expect(result.fields[0].values).toEqual([5, 7, 9, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, 1, 8]); expect(result.fields[1].values).toEqual([4, 6, 1, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', 1, 'c']); expect(result.fields[2].values).toEqual(['a', 'b', 1, 'c']);
}); });
test('should change all nulls to configured negative value', () => { test('should change all nulls to configured negative value', () => {
@ -70,9 +70,9 @@ describe('nullToValue Transformer', () => {
const result = nullToValue(applyNullInsertThreshold({ frame: df })); const result = nullToValue(applyNullInsertThreshold({ frame: df }));
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); expect(result.fields[0].values).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, -1, 6, -1, -1, -1, -1, -1, -1, 8]); expect(result.fields[1].values).toEqual([4, -1, 6, -1, -1, -1, -1, -1, -1, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', -1, 'b', -1, -1, -1, -1, -1, -1, 'c']); expect(result.fields[2].values).toEqual(['a', -1, 'b', -1, -1, -1, -1, -1, -1, 'c']);
}); });
test('should have no effect without nulls', () => { test('should have no effect without nulls', () => {
@ -87,8 +87,8 @@ describe('nullToValue Transformer', () => {
const result = nullToValue(applyNullInsertThreshold({ frame: df, refFieldName: null })); const result = nullToValue(applyNullInsertThreshold({ frame: df, refFieldName: null }));
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3]); expect(result.fields[0].values).toEqual([1, 2, 3]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, 8]); expect(result.fields[1].values).toEqual([4, 6, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', 'c']); expect(result.fields[2].values).toEqual(['a', 'b', 'c']);
}); });
}); });

View File

@ -8,7 +8,7 @@ import { Gazetteer } from '../gazetteer/gazetteer';
import { decodeGeohash } from './geohash'; import { decodeGeohash } from './geohash';
export function pointFieldFromGeohash(geohash: Field<string>): Field<Point> { export function pointFieldFromGeohash(geohash: Field<string>): Field<Geometry | undefined> {
return { return {
name: geohash.name ?? 'Point', name: geohash.name ?? 'Point',
type: FieldType.geo, type: FieldType.geo,
@ -25,7 +25,7 @@ export function pointFieldFromGeohash(geohash: Field<string>): Field<Point> {
}; };
} }
export function pointFieldFromLonLat(lon: Field, lat: Field): Field<Point> { export function pointFieldFromLonLat(lon: Field, lat: Field): Field<Geometry | undefined> {
const buffer = new Array<Point>(lon.values.length); const buffer = new Array<Point>(lon.values.length);
for (let i = 0; i < lon.values.length; i++) { for (let i = 0; i < lon.values.length; i++) {
const longitude = lon.values.get(i); const longitude = lon.values.get(i);

View File

@ -60,7 +60,7 @@ export function loadGazetteer(path: string, data: any): Gazetteer {
export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: string[] }): Gazetteer { export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: string[] }): Gazetteer {
const keys: Field[] = []; const keys: Field[] = [];
let geo: Field<Geometry> | undefined = undefined; let geo: Field<Geometry | undefined> | undefined = undefined;
let lat: Field | undefined = undefined; let lat: Field | undefined = undefined;
let lng: Field | undefined = undefined; let lng: Field | undefined = undefined;
let geohash: Field | undefined = undefined; let geohash: Field | undefined = undefined;
@ -132,7 +132,7 @@ export function frameAsGazetter(frame: DataFrame, opts: { path: string; keys?: s
isPoint = true; isPoint = true;
} }
} else { } else {
isPoint = geo.values.get(0).getType() === 'Point'; isPoint = geo.values.get(0)?.getType() === 'Point';
} }
const lookup = new Map<string, number>(); const lookup = new Map<string, number>();

View File

@ -44,7 +44,7 @@ export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSour
} }
//eslint-disable-next-line //eslint-disable-next-line
const field = info.field as Field<Point>; const field = info.field as unknown as Field<Point>;
const geometry = new LineString(field.values.toArray().map((p) => p.getCoordinates())) as Geometry; const geometry = new LineString(field.values.toArray().map((p) => p.getCoordinates())) as Geometry;
this.addFeatureInternal( this.addFeatureInternal(
new Feature({ new Feature({

View File

@ -113,7 +113,7 @@ export interface LocationFields {
h3?: Field; h3?: Field;
wkt?: Field; wkt?: Field;
lookup?: Field; lookup?: Field;
geo?: Field<Geometry>; geo?: Field<Geometry | undefined>;
} }
export function getLocationFields(frame: DataFrame, location: LocationFieldMatchers): LocationFields { export function getLocationFields(frame: DataFrame, location: LocationFieldMatchers): LocationFields {

View File

@ -300,17 +300,13 @@ describe('LiveDataStream', () => {
config: {}, config: {},
name: 'time', name: 'time',
type: 'time', type: 'time',
values: { values: [100, 101],
buffer: [100, 101],
},
}, },
{ {
config: {}, config: {},
name: 'b', name: 'b',
type: 'number', type: 'number',
values: { values: [1, 2],
buffer: [1, 2],
},
}, },
]); ]);
expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length); expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length);
@ -529,17 +525,13 @@ describe('LiveDataStream', () => {
config: {}, config: {},
name: 'time', name: 'time',
type: 'time', type: 'time',
values: { values: [100, 101],
buffer: [100, 101],
},
}, },
{ {
config: {}, config: {},
name: 'b', name: 'b',
type: 'number', type: 'number',
values: { values: [1, 2],
buffer: [1, 2],
},
}, },
]); ]);
expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length); expect(deserializedFrame.length).toEqual(dataFrameJsons.schema1().data.values[0].length);

View File

@ -90,7 +90,7 @@ describe('Streaming JSON', () => {
}); });
it('should create frame with schema & data', () => { it('should create frame with schema & data', () => {
expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -127,7 +127,7 @@ describe('Streaming JSON', () => {
}, },
}); });
expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -167,7 +167,7 @@ describe('Streaming JSON', () => {
}, },
}); });
expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -211,7 +211,7 @@ describe('Streaming JSON', () => {
}, },
}); });
expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -263,7 +263,7 @@ describe('Streaming JSON', () => {
}, },
}); });
expect(stream.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(stream.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -558,7 +558,7 @@ describe('Streaming JSON', () => {
[10, 11], [10, 11],
]); ]);
expect(frame.length).toEqual(3); expect(frame.length).toEqual(3);
expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(frame.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -603,7 +603,7 @@ describe('Streaming JSON', () => {
[10, 11], [10, 11],
]); ]);
expect(frame.length).toEqual(2); expect(frame.length).toEqual(2);
expect(frame.fields.map((f) => ({ name: f.name, value: f.values.buffer }))).toMatchInlineSnapshot(` expect(frame.fields.map((f) => ({ name: f.name, value: f.values }))).toMatchInlineSnapshot(`
[ [
{ {
"name": "time", "name": "time",
@ -720,7 +720,7 @@ describe('Streaming JSON', () => {
}, },
}); });
expect(stream.fields.map((f) => ({ name: f.name, labels: f.labels, values: f.values.buffer }))) expect(stream.fields.map((f) => ({ name: f.name, labels: f.labels, values: f.values })))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
[ [
{ {
@ -854,7 +854,7 @@ describe('Streaming JSON', () => {
const getSnapshot = (f: StreamingDataFrame) => { const getSnapshot = (f: StreamingDataFrame) => {
return { return {
values: f.fields[1].values.toArray(), values: f.fields[1].values,
info: f.packetInfo, info: f.packetInfo,
}; };
}; };

View File

@ -1,5 +1,4 @@
import { import {
ArrayVector,
DataFrame, DataFrame,
DataFrameJSON, DataFrameJSON,
decodeFieldValueEntities, decodeFieldValueEntities,
@ -60,7 +59,7 @@ export class StreamingDataFrame implements DataFrame {
refId?: string; refId?: string;
meta: QueryResultMeta = {}; meta: QueryResultMeta = {};
fields: Array<Field<any, ArrayVector<any>>> = []; fields: Field[] = [];
length = 0; length = 0;
private schemaFields: FieldSchema[] = []; private schemaFields: FieldSchema[] = [];
@ -146,11 +145,11 @@ export class StreamingDataFrame implements DataFrame {
...f, ...f,
type: f.type ?? FieldType.other, type: f.type ?? FieldType.other,
config: f.config ?? {}, config: f.config ?? {},
values: Array.isArray(f.values) ? new ArrayVector(f.values) : new ArrayVector(), values: f.values ?? [],
})); }));
assureValuesAreWithinLengthLimit( assureValuesAreWithinLengthLimit(
this.fields.map((f) => f.values.buffer), this.fields.map((f) => f.values),
this.options.maxLength, this.options.maxLength,
this.timeFieldIndex, this.timeFieldIndex,
this.options.maxDelta this.options.maxDelta
@ -256,8 +255,8 @@ export class StreamingDataFrame implements DataFrame {
// transfer old values by type & name, unless we relied on labels to match fields // transfer old values by type & name, unless we relied on labels to match fields
values: isWide values: isWide
? this.fields.find((of) => of.name === f.name && f.type === of.type)?.values ?? ? this.fields.find((of) => of.name === f.name && f.type === of.type)?.values ??
new ArrayVector(Array(this.length).fill(undefined)) Array(this.length).fill(undefined)
: new ArrayVector(), : [],
}; };
}); });
} }
@ -322,7 +321,7 @@ export class StreamingDataFrame implements DataFrame {
name, name,
type, type,
config: {}, config: {},
values: new ArrayVector([]), values: [],
}; };
}); });
} }
@ -336,13 +335,14 @@ export class StreamingDataFrame implements DataFrame {
this.packetInfo.action = StreamingFrameAction.Append; this.packetInfo.action = StreamingFrameAction.Append;
// mutates appended // mutates appended
appended = this.fields.map((f) => f.values.buffer); appended = this.fields.map((f) => f.values);
circPush(appended, values, this.options.maxLength, this.timeFieldIndex, this.options.maxDelta); circPush(appended, values, this.options.maxLength, this.timeFieldIndex, this.options.maxDelta);
} }
appended.forEach((v, i) => { appended.forEach((v, i) => {
const { state, values } = this.fields[i]; const field = this.fields[i];
values.buffer = v; const { state } = field;
field.values = v;
if (state) { if (state) {
state.calcs = undefined; state.calcs = undefined;
} }
@ -369,7 +369,7 @@ export class StreamingDataFrame implements DataFrame {
if (this.options.action === StreamingFrameAction.Append) { if (this.options.action === StreamingFrameAction.Append) {
circPush( circPush(
this.fields.map((f) => f.values.buffer), this.fields.map((f) => f.values),
values, values,
this.options.maxLength, this.options.maxLength,
this.timeFieldIndex, this.timeFieldIndex,
@ -377,19 +377,19 @@ export class StreamingDataFrame implements DataFrame {
); );
} else { } else {
values.forEach((v, i) => { values.forEach((v, i) => {
if (this.fields[i]?.values) { if (this.fields[i]) {
this.fields[i].values.buffer = v; this.fields[i].values = v;
} }
}); });
assureValuesAreWithinLengthLimit( assureValuesAreWithinLengthLimit(
this.fields.map((f) => f.values.buffer), this.fields.map((f) => f.values),
this.options.maxLength, this.options.maxLength,
this.timeFieldIndex, this.timeFieldIndex,
this.options.maxDelta this.options.maxDelta
); );
} }
const newLength = this.fields?.[0]?.values?.buffer?.length; const newLength = this.fields?.[0]?.values.length;
if (newLength !== undefined) { if (newLength !== undefined) {
this.length = newLength; this.length = newLength;
} }
@ -412,7 +412,7 @@ export class StreamingDataFrame implements DataFrame {
getValuesFromLastPacket = (): unknown[][] => getValuesFromLastPacket = (): unknown[][] =>
this.fields.map((f) => { this.fields.map((f) => {
const values = f.values.buffer; const values = f.values;
return values.slice(Math.max(values.length - this.packetInfo.length)); return values.slice(Math.max(values.length - this.packetInfo.length));
}); });
@ -449,7 +449,7 @@ export class StreamingDataFrame implements DataFrame {
...proto, ...proto,
config, config,
labels: parsedLabels, labels: parsedLabels,
values: new ArrayVector(Array(this.length).fill(undefined)), values: Array(this.length).fill(undefined),
}); });
} }
} }

View File

@ -99,25 +99,19 @@ describe('GrafanaLiveService', () => {
config: {}, config: {},
name: 'time', name: 'time',
type: FieldType.time, type: FieldType.time,
values: { values: [100, 101],
buffer: [100, 101],
},
}, },
{ {
config: {}, config: {},
name: 'a', name: 'a',
type: FieldType.string, type: FieldType.string,
values: { values: ['a', 'b'],
buffer: ['a', 'b'],
},
}, },
{ {
config: {}, config: {},
name: 'b', name: 'b',
type: FieldType.number, type: FieldType.number,
values: { values: [1, 2],
buffer: [1, 2],
},
}, },
]); ]);
}); });

View File

@ -1,7 +1,6 @@
import { mergeMap, from } from 'rxjs'; import { mergeMap, from } from 'rxjs';
import { import {
ArrayVector,
DataFrame, DataFrame,
DataTransformerID, DataTransformerID,
Field, Field,
@ -53,12 +52,12 @@ export function addFieldsFromGazetteer(frames: DataFrame[], gaz: Gazetteer, matc
//if the field matches //if the field matches
if (matcher(field, frame, frames)) { if (matcher(field, frame, frames)) {
const values = field.values.toArray(); const values = field.values;
const sub: any[][] = []; const sub: any[][] = [];
for (const f of src) { for (const f of src) {
const buffer = new Array(length); const buffer = new Array(length);
sub.push(buffer); sub.push(buffer);
fields.push({ ...f, values: new ArrayVector(buffer) }); fields.push({ ...f, values: buffer });
} }
// Add all values to the buffer // Add all values to the buffer

View File

@ -15,8 +15,8 @@ describe('timeSeriesTableTransformer', () => {
const result = results[0]; const result = results[0];
expect(result.refId).toBe('A'); expect(result.refId).toBe('A');
expect(result.fields).toHaveLength(3); expect(result.fields).toHaveLength(3);
expect(result.fields[0].values.toArray()).toEqual(['A', 'A', 'A']); expect(result.fields[0].values).toEqual(['A', 'A', 'A']);
expect(result.fields[1].values.toArray()).toEqual(['B', 'C', 'D']); expect(result.fields[1].values).toEqual(['B', 'C', 'D']);
assertDataFrameField(result.fields[2], series); assertDataFrameField(result.fields[2], series);
}); });
@ -33,8 +33,8 @@ describe('timeSeriesTableTransformer', () => {
expect(results[0]).toEqual(series[0]); expect(results[0]).toEqual(series[0]);
expect(results[1].refId).toBe('A'); expect(results[1].refId).toBe('A');
expect(results[1].fields).toHaveLength(3); expect(results[1].fields).toHaveLength(3);
expect(results[1].fields[0].values.toArray()).toEqual(['A', 'A']); expect(results[1].fields[0].values).toEqual(['A', 'A']);
expect(results[1].fields[1].values.toArray()).toEqual(['B', 'C']); expect(results[1].fields[1].values).toEqual(['B', 'C']);
expect(results[2]).toEqual(series[3]); expect(results[2]).toEqual(series[3]);
}); });
@ -51,14 +51,14 @@ describe('timeSeriesTableTransformer', () => {
expect(results).toHaveLength(2); expect(results).toHaveLength(2);
expect(results[0].refId).toBe('A'); expect(results[0].refId).toBe('A');
expect(results[0].fields).toHaveLength(3); expect(results[0].fields).toHaveLength(3);
expect(results[0].fields[0].values.toArray()).toEqual(['A', 'A', 'A']); expect(results[0].fields[0].values).toEqual(['A', 'A', 'A']);
expect(results[0].fields[1].values.toArray()).toEqual(['B', 'C', 'D']); expect(results[0].fields[1].values).toEqual(['B', 'C', 'D']);
assertDataFrameField(results[0].fields[2], series.slice(0, 3)); assertDataFrameField(results[0].fields[2], series.slice(0, 3));
expect(results[1].refId).toBe('B'); expect(results[1].refId).toBe('B');
expect(results[1].fields).toHaveLength(4); expect(results[1].fields).toHaveLength(4);
expect(results[1].fields[0].values.toArray()).toEqual(['B', 'B']); expect(results[1].fields[0].values).toEqual(['B', 'B']);
expect(results[1].fields[1].values.toArray()).toEqual(['F', 'G']); expect(results[1].fields[1].values).toEqual(['F', 'G']);
expect(results[1].fields[2].values.toArray()).toEqual(['A', 'B']); expect(results[1].fields[2].values).toEqual(['A', 'B']);
assertDataFrameField(results[1].fields[3], series.slice(3, 5)); assertDataFrameField(results[1].fields[3], series.slice(3, 5));
}); });
}); });
@ -66,12 +66,12 @@ describe('timeSeriesTableTransformer', () => {
function assertFieldsEqual(field1: Field, field2: Field) { function assertFieldsEqual(field1: Field, field2: Field) {
expect(field1.type).toEqual(field2.type); expect(field1.type).toEqual(field2.type);
expect(field1.name).toEqual(field2.name); expect(field1.name).toEqual(field2.name);
expect(field1.values.toArray()).toEqual(field2.values.toArray()); expect(field1.values).toEqual(field2.values);
expect(field1.labels ?? {}).toEqual(field2.labels ?? {}); expect(field1.labels ?? {}).toEqual(field2.labels ?? {});
} }
function assertDataFrameField(field: Field, matchesFrames: DataFrame[]) { function assertDataFrameField(field: Field, matchesFrames: DataFrame[]) {
const frames: DataFrame[] = field.values.toArray(); const frames: DataFrame[] = field.values;
expect(frames).toHaveLength(matchesFrames.length); expect(frames).toHaveLength(matchesFrames.length);
frames.forEach((frame, idx) => { frames.forEach((frame, idx) => {
const matchingFrame = matchesFrames[idx]; const matchingFrame = matchesFrames[idx];

View File

@ -68,7 +68,11 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp
values: new ArrayVector(), values: new ArrayVector(),
}; };
refId2frameField[refId] = frameField; refId2frameField[refId] = frameField;
const table = new MutableDataFrame();
// NOTE: MutableDataFrame.addField() makes copies, including any .values buffers
// since we do .values.add() later on the *originals*, we pass a custom MutableVectorCreator
// which will re-use the existing empty .values buffer by reference
const table = new MutableDataFrame(undefined, (buffer) => buffer ?? []);
for (const label of Object.values(labelFields)) { for (const label of Object.values(labelFields)) {
table.addField(label); table.addField(label);
} }
@ -81,7 +85,7 @@ export function timeSeriesToTableTransform(options: TimeSeriesTableTransformerOp
const labels = frame.fields[1].labels; const labels = frame.fields[1].labels;
for (const labelKey of Object.keys(labelFields)) { for (const labelKey of Object.keys(labelFields)) {
const labelValue = labels?.[labelKey] ?? null; const labelValue = labels?.[labelKey] ?? null;
labelFields[labelKey].values.add(labelValue); labelFields[labelKey].values.add(labelValue!);
} }
frameField.values.add(frame); frameField.values.add(frame);

View File

@ -371,7 +371,7 @@ describe('ElasticResponse', () => {
expect(getTimeField(frame1).values.get(0)).toBe(1000); expect(getTimeField(frame1).values.get(0)).toBe(1000);
expect(frame2.name).toBe('Average value'); expect(frame2.name).toBe('Average value');
expect(getValueField(frame2).values.toArray()).toStrictEqual([88, 99]); expect(getValueField(frame2).values.toArray()).toEqual([88, 99]);
}); });
}); });
@ -688,20 +688,20 @@ describe('ElasticResponse', () => {
const firstSeries = result.data[0]; const firstSeries = result.data[0];
expect(firstSeries.name).toBe('Top Metrics @value'); expect(firstSeries.name).toBe('Top Metrics @value');
expect(firstSeries.length).toBe(2); expect(firstSeries.length).toBe(2);
expect(getTimeField(firstSeries).values.toArray()).toStrictEqual([ expect(getTimeField(firstSeries).values.toArray()).toEqual([
new Date('2021-01-01T00:00:00.000Z').valueOf(), new Date('2021-01-01T00:00:00.000Z').valueOf(),
new Date('2021-01-01T00:00:10.000Z').valueOf(), new Date('2021-01-01T00:00:10.000Z').valueOf(),
]); ]);
expect(getValueField(firstSeries).values.toArray()).toStrictEqual([1, 1]); expect(getValueField(firstSeries).values.toArray()).toEqual([1, 1]);
const secondSeries = result.data[1]; const secondSeries = result.data[1];
expect(secondSeries.name).toBe('Top Metrics @anotherValue'); expect(secondSeries.name).toBe('Top Metrics @anotherValue');
expect(secondSeries.length).toBe(2); expect(secondSeries.length).toBe(2);
expect(getTimeField(secondSeries).values.toArray()).toStrictEqual([ expect(getTimeField(secondSeries).values.toArray()).toEqual([
new Date('2021-01-01T00:00:00.000Z').valueOf(), new Date('2021-01-01T00:00:00.000Z').valueOf(),
new Date('2021-01-01T00:00:10.000Z').valueOf(), new Date('2021-01-01T00:00:10.000Z').valueOf(),
]); ]);
expect(getValueField(secondSeries).values.toArray()).toStrictEqual([2, 2]); expect(getValueField(secondSeries).values.toArray()).toEqual([2, 2]);
}); });
}); });
@ -814,9 +814,9 @@ describe('ElasticResponse', () => {
const { fields } = result.data[0]; const { fields } = result.data[0];
expect(fields.length).toBe(2); expect(fields.length).toBe(2);
expect(fields[0].name).toBe('bytes'); expect(fields[0].name).toBe('bytes');
expect(fields[0].config).toStrictEqual({ filterable: true }); expect(fields[0].config).toEqual({ filterable: true });
expect(fields[1].name).toBe('Count'); expect(fields[1].name).toBe('Count');
expect(fields[1].config).toStrictEqual({}); expect(fields[1].config).toEqual({});
}); });
}); });
@ -989,9 +989,9 @@ describe('ElasticResponse', () => {
const field2 = result.data[0].fields[1]; const field2 = result.data[0].fields[1];
const field3 = result.data[0].fields[2]; const field3 = result.data[0].fields[2];
expect(field1.values.toArray()).toStrictEqual(['server-1', 'server-2']); expect(field1.values).toEqual(['server-1', 'server-2']);
expect(field2.values.toArray()).toStrictEqual([1000, 2000]); expect(field2.values).toEqual([1000, 2000]);
expect(field3.values.toArray()).toStrictEqual([369, 200]); expect(field3.values).toEqual([369, 200]);
}); });
}); });
@ -1044,9 +1044,9 @@ describe('ElasticResponse', () => {
expect(field2.name).toBe('p75 value'); expect(field2.name).toBe('p75 value');
expect(field3.name).toBe('p90 value'); expect(field3.name).toBe('p90 value');
expect(field1.values.toArray()).toStrictEqual(['id1', 'id2']); expect(field1.values.toArray()).toEqual(['id1', 'id2']);
expect(field2.values.toArray()).toStrictEqual([3.3, 2.3]); expect(field2.values.toArray()).toEqual([3.3, 2.3]);
expect(field3.values.toArray()).toStrictEqual([5.5, 4.5]); expect(field3.values.toArray()).toEqual([5.5, 4.5]);
}); });
}); });
@ -1088,9 +1088,9 @@ describe('ElasticResponse', () => {
it('should include field in metric name', () => { it('should include field in metric name', () => {
expect(result.data[0].length).toBe(1); expect(result.data[0].length).toBe(1);
expect(result.data[0].fields.length).toBe(3); expect(result.data[0].fields.length).toBe(3);
expect(result.data[0].fields[0].values.toArray()).toStrictEqual(['server-1']); expect(result.data[0].fields[0].values.toArray()).toEqual(['server-1']);
expect(result.data[0].fields[1].values.toArray()).toStrictEqual([1000]); expect(result.data[0].fields[1].values.toArray()).toEqual([1000]);
expect(result.data[0].fields[2].values.toArray()).toStrictEqual([3000]); expect(result.data[0].fields[2].values.toArray()).toEqual([3000]);
}); });
}); });
@ -1286,11 +1286,11 @@ describe('ElasticResponse', () => {
expect(frame.length).toBe(2); expect(frame.length).toBe(2);
const { fields } = frame; const { fields } = frame;
expect(fields.length).toBe(5); expect(fields.length).toBe(5);
expect(fields[0].values.toArray()).toStrictEqual([1000, 2000]); expect(fields[0].values.toArray()).toEqual([1000, 2000]);
expect(fields[1].values.toArray()).toStrictEqual([2, 3]); expect(fields[1].values.toArray()).toEqual([2, 3]);
expect(fields[2].values.toArray()).toStrictEqual([3, 4]); expect(fields[2].values.toArray()).toEqual([3, 4]);
expect(fields[3].values.toArray()).toStrictEqual([6, 12]); expect(fields[3].values.toArray()).toEqual([6, 12]);
expect(fields[4].values.toArray()).toStrictEqual([24, 48]); expect(fields[4].values.toArray()).toEqual([24, 48]);
}); });
}); });

View File

@ -220,7 +220,7 @@ function cloneDataFrame(frame: DataQueryResponseData): DataQueryResponseData {
...frame, ...frame,
fields: frame.fields.map((field: Field<unknown, ArrayVector>) => ({ fields: frame.fields.map((field: Field<unknown, ArrayVector>) => ({
...field, ...field,
values: new ArrayVector(field.values.buffer), values: new ArrayVector(field.values.toArray()),
})), })),
}; };
} }

View File

@ -141,9 +141,7 @@ describe('PostgreSQLDatasource', () => {
entities: {}, entities: {},
name: 'time', name: 'time',
type: 'time', type: 'time',
values: { values: [1599643351085],
buffer: [1599643351085],
},
}, },
{ {
config: {}, config: {},
@ -153,9 +151,7 @@ describe('PostgreSQLDatasource', () => {
}, },
name: 'metric', name: 'metric',
type: 'number', type: 'number',
values: { values: [30.226249741223704],
buffer: [30.226249741223704],
},
}, },
], ],
length: 1, length: 1,
@ -237,27 +233,21 @@ describe('PostgreSQLDatasource', () => {
entities: {}, entities: {},
name: 'time', name: 'time',
type: 'time', type: 'time',
values: { values: [1599643351085],
buffer: [1599643351085],
},
}, },
{ {
config: {}, config: {},
entities: {}, entities: {},
name: 'metric', name: 'metric',
type: 'string', type: 'string',
values: { values: ['America'],
buffer: ['America'],
},
}, },
{ {
config: {}, config: {},
entities: {}, entities: {},
name: 'value', name: 'value',
type: 'number', type: 'number',
values: { values: [30.226249741223704],
buffer: [30.226249741223704],
},
}, },
], ],
length: 1, length: 1,

View File

@ -124,14 +124,12 @@ export function prepareGraphableFields(
...field, ...field,
config, config,
type: FieldType.number, type: FieldType.number,
values: new ArrayVector( values: field.values.map((v) => {
field.values.toArray().map((v) => {
if (v == null) { if (v == null) {
return v; return v;
} }
return Boolean(v) ? 1 : 0; return Boolean(v) ? 1 : 0;
}) }),
),
}; };
if (!isBooleanUnit(config.unit)) { if (!isBooleanUnit(config.unit)) {