Skip to content

Commit eeb52e7

Browse files
committed
feat: Added support for Nativescript ObservableArray.
1 parent 0d8c546 commit eeb52e7

File tree

7 files changed

+120
-35
lines changed

7 files changed

+120
-35
lines changed

demo/app/examples/BarChart.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Color } from '@nativescript/core';
1+
import { Color, ObservableArray } from '@nativescript/core';
22
import { BarChart } from '@nativescript-community/ui-chart/charts';
33
import { BarData } from '@nativescript-community/ui-chart/data/BarData';
44
import { BarDataSet } from '@nativescript-community/ui-chart/data/BarDataSet';
@@ -39,9 +39,7 @@ export function onChartLoaded(args) {
3939

4040
//const icon = ImageSource.fromFileOrResourceSync('~/assets/star.png');
4141

42-
const data = new Array(5).fill(0).map(function (v, i) {
43-
return { index: i, value: Math.random() * 1 };
44-
});
42+
const data = new ObservableArray();
4543

4644
const sets = [];
4745
const set = new BarDataSet(data, 'Dataset Label', 'index', 'value');
@@ -57,6 +55,12 @@ export function onChartLoaded(args) {
5755

5856
// Set data
5957
chart.setData(bd);
58+
59+
// Add data anytime later
60+
const temp = new Array(5).fill(0).map(function (v, i) {
61+
return { index: i, value: Math.random() * 1 };
62+
});
63+
data.push(temp);
6064
}
6165

6266
export function redraw(args) {

src/charting/charts/Chart.ts

+65-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IDataSet } from '../interfaces/datasets/IDataSet';
2+
import { DataSet } from '../data/DataSet';
23
import { Entry } from '../data/Entry';
34
import { ChartData } from '../data/ChartData';
45
import { ChartInterface } from '../interfaces/dataprovider/ChartInterface';
@@ -20,7 +21,8 @@ import { ChartAnimator, EasingFunction } from '../animation/ChartAnimator';
2021
import { ViewPortJob } from '../jobs/ViewPortJob';
2122
import { ChartTouchListener } from '../listener/ChartTouchListener';
2223
import { layout } from '@nativescript/core/utils/utils';
23-
import { EventData } from '@nativescript/core';
24+
import { ChangedData, EventData, ObservableArray } from '@nativescript/core';
25+
import { addWeakEventListener, removeWeakEventListener } from '@nativescript/core/ui/core/weak-event-listener';
2426

2527
const LOG_TAG = 'NSChart';
2628

@@ -225,29 +227,54 @@ export abstract class Chart<U extends Entry, D extends IDataSet<U>, T extends Ch
225227
* @param data
226228
*/
227229
public setData(data: T) {
230+
const oldData = this.mData;
228231
this.mData = data;
229232
this.mOffsetsCalculated = false;
230233

234+
// If data was replaced, clear old listeners
235+
if (oldData && oldData !== data) {
236+
this.clearObservableListeners(oldData);
237+
}
238+
231239
if (data == null) {
232240
return;
233241
}
234242

235-
// calculate how many digits are needed
243+
// Calculate how many digits are needed
236244
this.setupDefaultFormatter(data.getYMin(), data.getYMax());
237245

238246
for (const set of this.mData.getDataSets()) {
239-
if (set.needsFormatter() || set.getValueFormatter() === this.mDefaultValueFormatter) set.setValueFormatter(this.mDefaultValueFormatter);
247+
const values = set.getValues();
248+
if (values instanceof ObservableArray) {
249+
removeWeakEventListener(values, ObservableArray.changeEvent, this.onObservableDataSetChanged, this);
250+
addWeakEventListener(values, ObservableArray.changeEvent, this.onObservableDataSetChanged, this);
251+
}
252+
253+
if (set.needsFormatter() || set.getValueFormatter() === this.mDefaultValueFormatter) {
254+
set.setValueFormatter(this.mDefaultValueFormatter);
255+
}
240256
}
241257

242258
// let the chart know there is new data
243259
this.notifyDataSetChanged();
244260
}
245261

262+
protected clearObservableListeners(data: T) {
263+
for (const set of data.getDataSets()) {
264+
const values = set.getValues();
265+
if (values instanceof ObservableArray) {
266+
removeWeakEventListener(values, ObservableArray.changeEvent, this.onObservableDataSetChanged, this);
267+
}
268+
}
269+
}
270+
246271
/**
247272
* Clears the chart from all data (sets it to null) and refreshes it (by
248273
* calling invalidate()).
249274
*/
250275
public clear() {
276+
this.clearObservableListeners(this.mData);
277+
251278
this.mData = null;
252279
this.mOffsetsCalculated = false;
253280
this.mIndicesToHighlight = null;
@@ -260,6 +287,7 @@ export abstract class Chart<U extends Entry, D extends IDataSet<U>, T extends Ch
260287
* chart by calling invalidate().
261288
*/
262289
public clearValues() {
290+
this.clearObservableListeners(this.mData);
263291
this.mData.clearValues();
264292
this.invalidate();
265293
}
@@ -278,6 +306,40 @@ export abstract class Chart<U extends Entry, D extends IDataSet<U>, T extends Ch
278306
}
279307
}
280308

309+
protected onObservableDataSetChanged(args: ChangedData<any>) {
310+
const observable = args.object;
311+
const data = this.mData;
312+
313+
if (data) {
314+
let changed = false;
315+
// Observable may be used by multiple datasets
316+
for (const set of data.getDataSets()) {
317+
if (set.getValues() === observable) {
318+
if (!changed) {
319+
changed = true;
320+
}
321+
set.init();
322+
}
323+
}
324+
325+
// If datasets actually had changes, notify chart
326+
if (changed) {
327+
data.notifyDataChanged();
328+
// Recalculate how many digits are needed
329+
this.setupDefaultFormatter(data.getYMin(), data.getYMax());
330+
331+
// Update chart
332+
this.notifyDataSetChanged();
333+
} else {
334+
// Observable is no longer used by any dataset of this chart, so remove listener
335+
removeWeakEventListener(observable, ObservableArray.changeEvent, this.onObservableDataSetChanged, this);
336+
}
337+
} else {
338+
// Observable is no longer used by any dataset of this chart, so remove listener
339+
removeWeakEventListener(observable, ObservableArray.changeEvent, this.onObservableDataSetChanged, this);
340+
}
341+
}
342+
281343
/**
282344
* Lets the chart know its underlying data has changed and performs all
283345
* necessary recalculations. It is crucial that this method is called

src/charting/data/BaseDataSet.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ObservableArray } from '@nativescript/core';
12
import { Entry } from './Entry';
23
import { IDataSet } from '../interfaces/datasets/IDataSet';
34
import { LegendForm } from '../components/Legend';
@@ -14,19 +15,21 @@ import { Rounding } from './DataSet';
1415
* provided by the IDataSet interface.
1516
*/
1617
export abstract class BaseDataSet<T extends Entry> implements IDataSet<T> {
18+
abstract init();
1719
abstract getYMin(): number;
1820
abstract getYMax(): number;
1921
abstract getXMin(): number;
2022
abstract getXMax(): number;
2123
abstract calcMinMaxYRange(fromX: number, toX: number);
22-
abstract getEntriesForXValue(xValue: number): T[];
24+
abstract getEntriesForXValue(xValue: number): T[] | ObservableArray<T>;
2325
abstract getEntriesAndIndexesForXValue(xValue: number): { entry: T; index: number }[];
2426
abstract getEntryIndexForXValue(xValue: number, closestToY: number, rounding?: Rounding): number;
2527
abstract getEntryIndex(e: T): number;
2628
abstract addEntry(e: T): boolean;
2729
abstract addEntryOrdered(e: T);
2830
abstract clear();
2931
abstract getEntryCount(): number;
32+
abstract getValues(): T[] | ObservableArray<T>;
3033
abstract getEntryForIndex(index: number): T;
3134
abstract getEntryForXValue(xValue: number, closestToY: number, rounding?: Rounding): T;
3235
abstract getEntryAndIndexForXValue(xValue: number, closestToY: number, rounding?: Rounding): { entry: T; index: number };

src/charting/data/DataSet.ts

+23-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { ObservableArray } from '@nativescript/core';
12
import { Entry } from './Entry';
23
import { BaseDataSet } from './BaseDataSet';
34
import { getEntryXValue } from './BaseEntry';
5+
import { Utils } from '../utils/Utils';
46

57
/**
68
* Determines how to round DataSet index values for
@@ -24,7 +26,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
2426
/**
2527
* the entries that this DataSet represents / holds together
2628
*/
27-
protected mValues: T[] = null;
29+
protected mValues: T[] | ObservableArray<T> = null;
2830

2931
/**
3032
* maximum y-value in the value array
@@ -68,7 +70,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
6870

6971
if (this.mValues.length > 0) {
7072
for (let index = 0, e: T; index < this.mValues.length; index++) {
71-
e = this.mValues[index];
73+
e = this.getEntryForIndex(index);
7274
this.initEntryData(e);
7375
this.calcMinMaxForEntry(e, index);
7476
}
@@ -83,7 +85,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
8385
if (this.mValues == null || this.mValues.length === 0) return;
8486

8587
for (let index = 0, e: T; index < this.mValues.length; index++) {
86-
e = this.mValues[index];
88+
e = this.getEntryForIndex(index);
8789
this.calcMinMaxForEntry(e, index);
8890
}
8991
}
@@ -99,7 +101,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
99101

100102
for (let i = indexFrom; i <= indexTo; i++) {
101103
// only recalculate y
102-
this.calcMinMaxY(this.mValues[i]);
104+
this.calcMinMaxY(this.getEntryForIndex(i));
103105
}
104106
}
105107

@@ -125,7 +127,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
125127
this.mXMin = Infinity;
126128

127129
for (let index = 0, e: T; index < this.mValues.length; index++) {
128-
e = this.mValues[index];
130+
e = this.getEntryForIndex(index);
129131
this.calcMinMaxForEntry(e, index);
130132
}
131133
} else {
@@ -194,7 +196,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
194196
}
195197

196198
let addedIndex = this.mValues.length;
197-
if (this.mValues.length > 0 && this.xProperty && this.mValues[this.mValues.length - 1] > e[this.xProperty]) {
199+
if (this.mValues.length > 0 && this.xProperty && this.getEntryForIndex(this.mValues.length - 1) > e[this.xProperty]) {
198200
addedIndex = this.getEntryIndexForXValue(e[this.xProperty], e[this.yProperty], Rounding.UP);
199201
this.mValues.splice(addedIndex, 0, e);
200202
} else {
@@ -245,17 +247,17 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
245247

246248
public getEntryForXValue(xValue, closestToY, rounding = Rounding.CLOSEST): T {
247249
const index = this.getEntryIndexForXValue(xValue, closestToY, rounding);
248-
if (index > -1) return this.getInternalValues()[index];
250+
if (index > -1) return this.getEntryForIndex(index);
249251
return null;
250252
}
251253
public getEntryAndIndexForXValue(xValue, closestToY, rounding = Rounding.CLOSEST): { entry: T; index: number } {
252254
const index = this.getEntryIndexForXValue(xValue, closestToY, rounding);
253-
if (index > -1) return { entry: this.getInternalValues()[index], index };
255+
if (index > -1) return { entry: this.getEntryForIndex(index), index };
254256
return null;
255257
}
256258

257259
public getEntryForIndex(index) {
258-
return this.getInternalValues()[index];
260+
return this.getInternalValues() instanceof ObservableArray ? (this.getInternalValues() as ObservableArray<T>).getItem(index) : this.getInternalValues()[index];
259261
}
260262

261263
public getEntryIndexForXValue(xValue, closestToY, rounding) {
@@ -270,8 +272,8 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
270272
let m: number, e: T, e1: T;
271273
while (low < high) {
272274
m = Math.floor((low + high) / 2);
273-
e = values[m];
274-
e1 = values[m + 1];
275+
e = Utils.getArrayItem(values, m);
276+
e1 = Utils.getArrayItem(values, m + 1);
275277
const d1 = getEntryXValue(e, xKey, m) - xValue,
276278
d2 = getEntryXValue(e1, xKey, m + 1) - xValue,
277279
ad1 = Math.abs(d1),
@@ -301,7 +303,7 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
301303
}
302304

303305
if (closest !== -1) {
304-
let e = values[closest];
306+
let e = Utils.getArrayItem(values, closest);
305307
const closestXValue = getEntryXValue(e, xKey, closest);
306308
if (rounding === Rounding.UP) {
307309
// If rounding up, and found x-value is lower than specified x, and we can go upper...
@@ -317,19 +319,19 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
317319

318320
// Search by closest to y-value
319321
if (!isNaN(closestToY)) {
320-
e = values[closest - 1];
322+
e = Utils.getArrayItem(values, closest - 1);
321323
xValue = getEntryXValue(e, xKey, closest - 1);
322324
while (closest > 0 && xValue === closestXValue) closest -= 1;
323325

324-
let closestYValue = values[closest][yKey];
326+
let closestYValue = Utils.getArrayItem(values, closest)[yKey];
325327
let closestYIndex = closest;
326328

327329
// eslint-disable-next-line no-constant-condition
328330
while (true) {
329331
closest += 1;
330332
if (closest >= values.length) break;
331333

332-
e = values[closest];
334+
e = Utils.getArrayItem(values, closest);
333335
xValue = getEntryXValue(e, xKey, closest);
334336

335337
if (xValue !== closestXValue) break;
@@ -358,17 +360,17 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
358360
let m: number, e: T, e1: T, mXValue;
359361
while (low <= high) {
360362
m = Math.floor((high + low) / 2);
361-
e = values[m];
363+
e = Utils.getArrayItem(values, m);
362364
mXValue = getEntryXValue(e, xKey, m);
363365
// if we have a match
364366
if (xValue === mXValue) {
365-
while (m > 0 && getEntryXValue(values[m - 1], xKey, m - 1) === xValue) m--;
367+
while (m > 0 && getEntryXValue(Utils.getArrayItem(values, m - 1), xKey, m - 1) === xValue) m--;
366368

367369
high = values.length;
368370

369371
// loop over all "equal" entries
370372
for (; m < high; m++) {
371-
e = values[m];
373+
e = Utils.getArrayItem(values, m);
372374
mXValue = getEntryXValue(e, xKey, m);
373375
if (mXValue === xValue) {
374376
entries.push(e);
@@ -397,17 +399,17 @@ export abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
397399
let entry: T, mXValue;
398400
while (low <= high) {
399401
let m = Math.floor((high + low) / 2);
400-
entry = values[m];
402+
entry = Utils.getArrayItem(values, m);
401403
mXValue = getEntryXValue(entry, xKey, m);
402404
// if we have a match
403405
if (xValue === mXValue) {
404-
while (m > 0 && getEntryXValue(values[m - 1], xKey, m - 1) === xValue) m--;
406+
while (m > 0 && getEntryXValue(Utils.getArrayItem(values, m - 1), xKey, m - 1) === xValue) m--;
405407

406408
high = values.length;
407409

408410
// loop over all "equal" entries
409411
for (; m < high; m++) {
410-
entry = values[m];
412+
entry = Utils.getArrayItem(values, m);
411413
mXValue = getEntryXValue(entry, xKey, m);
412414
if (mXValue === xValue) {
413415
entries.push({ entry, index: m });

src/charting/data/RadarDataSet.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ export class RadarDataSet extends LineRadarDataSet<RadarEntry> implements IRadar
1919
protected mHighlightCircleOuterRadius = 4.0;
2020
protected mHighlightCircleStrokeWidth = 2.0;
2121

22-
2322
constructor(yVals: RadarEntry, label: string, yProperty?) {
24-
super(yVals, label,null, yProperty);
23+
super(yVals, label, null, yProperty);
2524
this.init();
2625
}
2726

0 commit comments

Comments
 (0)