1 | import { __extends } from "tslib";
|
2 | /**
|
3 | * ============================================================================
|
4 | * IMPORTS
|
5 | * ============================================================================
|
6 | * @hidden
|
7 | */
|
8 | import { Container } from "./Container";
|
9 | import { List, ListDisposer } from "./utils/List";
|
10 | import { OrderedListTemplate } from "./utils/SortedList";
|
11 | import { Dictionary } from "./utils/Dictionary";
|
12 | import { Disposer, MultiDisposer } from "./utils/Disposer";
|
13 | import { DataSource } from "./data/DataSource";
|
14 | import { Responsive } from "./utils/Responsive";
|
15 | import { system } from "./System";
|
16 | import { DataItem } from "./DataItem";
|
17 | import { registry } from "./Registry";
|
18 | import * as $math from "./utils/Math";
|
19 | import * as $array from "./utils/Array";
|
20 | import * as $ease from "./utils/Ease";
|
21 | import * as $utils from "./utils/Utils";
|
22 | import * as $iter from "./utils/Iterator";
|
23 | import * as $object from "./utils/Object";
|
24 | import * as $type from "./utils/Type";
|
25 | /**
|
26 | * ============================================================================
|
27 | * MAIN CLASS
|
28 | * ============================================================================
|
29 | * @hidden
|
30 | */
|
31 | /**
|
32 | * A Component represents an independent functional element or control, that
|
33 | * can have it's own behavior, children, data, etc.
|
34 | *
|
35 | * A few examples of a Component: [[Legend]], [[Series]], [[Scrollbar]].
|
36 | *
|
37 | * @see {@link IComponentEvents} for a list of available events
|
38 | * @see {@link IComponentAdapters} for a list of available Adapters
|
39 | * @important
|
40 | */
|
41 | var Component = /** @class */ (function (_super) {
|
42 | __extends(Component, _super);
|
43 | /**
|
44 | * Constructor
|
45 | */
|
46 | function Component() {
|
47 | var _this =
|
48 | // Init
|
49 | _super.call(this) || this;
|
50 | /**
|
51 | * Holds data field names.
|
52 | *
|
53 | * Data fields define connection beween [[DataItem]] and actual properties
|
54 | * in raw data.
|
55 | */
|
56 | _this.dataFields = {};
|
57 | /**
|
58 | * A list of [[DataSource]] definitions of external data source.
|
59 | *
|
60 | * @ignore Exclude from docs
|
61 | */
|
62 | _this._dataSources = {};
|
63 | /**
|
64 | * This is used when only new data is invalidated (if added using `addData`
|
65 | * method).
|
66 | *
|
67 | * @ignore Exclude from docs
|
68 | */
|
69 | _this._parseDataFrom = 0;
|
70 | /**
|
71 | * Holds the disposers for the dataItems and dataUsers
|
72 | *
|
73 | * @ignore Exclude from docs
|
74 | */
|
75 | _this._dataDisposers = [];
|
76 | /**
|
77 | * Currently selected "data set".
|
78 | *
|
79 | * If it's set to `""`, main data set (unaggregated data) is used.
|
80 | */
|
81 | _this._currentDataSetId = "";
|
82 | /**
|
83 | * [_start description]
|
84 | *
|
85 | * @ignore Exclude from docs
|
86 | */
|
87 | _this._start = 0;
|
88 | /**
|
89 | * [_end description]
|
90 | *
|
91 | * @ignore Exclude from docs
|
92 | */
|
93 | _this._end = 1;
|
94 | /**
|
95 | * If set to `true`, changing data range in element will not trigger
|
96 | * `daterangechanged` event.
|
97 | */
|
98 | _this.skipRangeEvent = false;
|
99 | /**
|
100 | * Whenever selected scope changes (chart is zoomed or panned), for example
|
101 | * by interaction from a Scrollbar, or API, a chart needs to reposition
|
102 | * its contents.
|
103 | *
|
104 | * `rangeChangeDuration` influences how this is performed.
|
105 | *
|
106 | * If set to zero (0), the change will happen instantenously.
|
107 | *
|
108 | * If set to non-zero value, the chart will gradually animate into new
|
109 | * position for the set amount of milliseconds.
|
110 | *
|
111 | * @default 0
|
112 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
113 | */
|
114 | _this.rangeChangeDuration = 0;
|
115 | /**
|
116 | * An easing function to use for range change animation.
|
117 | *
|
118 | * @see {@link Ease}
|
119 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
120 | */
|
121 | _this.rangeChangeEasing = $ease.cubicOut;
|
122 | /**
|
123 | * A duration (ms) of each data parsing step. A Component parses its data in
|
124 | * chunks in order to avoid completely freezing the machine when large data
|
125 | * sets are used. This setting will control how many milliseconds should pass
|
126 | * when parsing data until parser stops for a brief moment to let other
|
127 | * processes catch up.
|
128 | */
|
129 | _this.parsingStepDuration = 50;
|
130 | /**
|
131 | * [dataInvalid description]
|
132 | *
|
133 | * @ignore Exclude from docs
|
134 | * @todo Description
|
135 | */
|
136 | _this.dataInvalid = false;
|
137 | /**
|
138 | *
|
139 | * @ignore Exclude from docs
|
140 | */
|
141 | _this.rawDataInvalid = false;
|
142 | /**
|
143 | * [dataRangeInvalid description]
|
144 | *
|
145 | * @ignore Exclude from docs
|
146 | * @todo Description
|
147 | */
|
148 | _this.dataRangeInvalid = false;
|
149 | /**
|
150 | * [dataItemsInvalid description]
|
151 | *
|
152 | * @ignore Exclude from docs
|
153 | * @todo Description
|
154 | */
|
155 | _this.dataItemsInvalid = false;
|
156 | /**
|
157 | * If set to a non-zero number the element will "animate" data values of its
|
158 | * children.
|
159 | *
|
160 | * This will happen on first load and whenever data values change.
|
161 | *
|
162 | * Enabling interpolation will mean that elements will transit smoothly into
|
163 | * new values rather than updating instantly.
|
164 | *
|
165 | * @default 0
|
166 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
167 | */
|
168 | _this.interpolationDuration = 0;
|
169 | /**
|
170 | * An easing function to use for interpolating values when transiting from
|
171 | * one source value to another.
|
172 | *
|
173 | * @default cubicOut
|
174 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
175 | * @see {@link Ease}
|
176 | */
|
177 | _this.interpolationEasing = $ease.cubicOut;
|
178 | /**
|
179 | * Indicates whether transition between data item's values should start and
|
180 | * play out all at once, or with a small delay (as defined by
|
181 | * `sequencedInterpolationDelay`) for each subsequent data item.
|
182 | *
|
183 | * @default true
|
184 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
185 | */
|
186 | _this.sequencedInterpolation = true;
|
187 | /**
|
188 | * A delay (ms) to wait between animating each subsequent data item's
|
189 | * interpolation animation.
|
190 | *
|
191 | * Relative only if `sequencedInterpolation = true`.
|
192 | *
|
193 | * @default 0
|
194 | * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations
|
195 | */
|
196 | _this.sequencedInterpolationDelay = 0;
|
197 | /**
|
198 | * A progress (0-1) for the data validation process.
|
199 | *
|
200 | * @ignore Exclude from docs
|
201 | */
|
202 | _this.dataValidationProgress = 0;
|
203 | _this._addAllDataItems = true;
|
204 | _this._usesData = true;
|
205 | _this.className = "Component";
|
206 | _this.minZoomCount = 1;
|
207 | _this.maxZoomCount = 0;
|
208 | _this._dataItems = new OrderedListTemplate(_this.createDataItem());
|
209 | _this._dataItems.events.on("inserted", _this.handleDataItemAdded, _this, false);
|
210 | _this._dataItems.events.on("removed", _this.handleDataItemRemoved, _this, false);
|
211 | _this._disposers.push(new ListDisposer(_this._dataItems));
|
212 | _this._disposers.push(_this._dataItems.template);
|
213 | _this.invalidateData();
|
214 | // TODO what about remove ?
|
215 | _this.dataUsers.events.on("inserted", _this.handleDataUserAdded, _this, false);
|
216 | // Set up disposers
|
217 | _this._disposers.push(new MultiDisposer(_this._dataDisposers));
|
218 | _this._start = 0;
|
219 | _this._end = 1;
|
220 | _this.maxZoomDeclination = 1;
|
221 | // Apply theme
|
222 | _this.applyTheme();
|
223 | return _this;
|
224 | }
|
225 | /**
|
226 | * Returns a new/empty DataItem of the type appropriate for this object.
|
227 | *
|
228 | * @see {@link DataItem}
|
229 | * @return Data Item
|
230 | */
|
231 | Component.prototype.createDataItem = function () {
|
232 | return new DataItem();
|
233 | };
|
234 | /**
|
235 | * [handleDataUserAdded description]
|
236 | *
|
237 | * @ignore Exclude from docs
|
238 | * @todo Description
|
239 | * @param event Event object
|
240 | */
|
241 | Component.prototype.handleDataUserAdded = function (event) {
|
242 | var dataUser = event.newValue;
|
243 | dataUser.dataProvider = this;
|
244 | };
|
245 | /**
|
246 | * [handleDataItemValueChange description]
|
247 | *
|
248 | * @ignore Exclude from docs
|
249 | * @todo Description
|
250 | */
|
251 | Component.prototype.handleDataItemValueChange = function (dataItem, name) {
|
252 | if (!this.dataItemsInvalid) {
|
253 | this.invalidateDataItems();
|
254 | }
|
255 | };
|
256 | /**
|
257 | * [handleDataItemWorkingValueChange description]
|
258 | *
|
259 | * @ignore Exclude from docs
|
260 | */
|
261 | Component.prototype.handleDataItemWorkingValueChange = function (dataItem, name) {
|
262 | };
|
263 | /**
|
264 | * [handleDataItemWorkingLocationChange description]
|
265 | *
|
266 | * @ignore Exclude from docs
|
267 | */
|
268 | Component.prototype.handleDataItemWorkingLocationChange = function (dataItem, name) {
|
269 | };
|
270 | /**
|
271 | * [handleDataItemCalculatedValueChange description]
|
272 | *
|
273 | * @ignore Exclude from docs
|
274 | */
|
275 | Component.prototype.handleDataItemCalculatedValueChange = function (dataItem, name) {
|
276 | };
|
277 | /**
|
278 | * [handleDataItemPropertyChange description]
|
279 | *
|
280 | * @ignore Exclude from docs
|
281 | */
|
282 | Component.prototype.handleDataItemPropertyChange = function (dataItem, name) {
|
283 | };
|
284 | /**
|
285 | * Populates a [[DataItem]] width data from data source.
|
286 | *
|
287 | * Loops through all the fields and if such a field is found in raw data
|
288 | * object, a corresponding value on passed in `dataItem` is set.
|
289 | *
|
290 | * @ignore Exclude from docs
|
291 | * @param item
|
292 | */
|
293 | Component.prototype.processDataItem = function (dataItem, dataContext) {
|
294 | var _this = this;
|
295 | if (dataItem) {
|
296 | if (!dataContext) {
|
297 | dataContext = {};
|
298 | }
|
299 | // store reference to original data item
|
300 | dataItem.dataContext = dataContext;
|
301 | var hasSomeValues_1 = false;
|
302 | $object.each(this.dataFields, function (key, fieldValue) {
|
303 | var fieldName = key;
|
304 | var value = dataContext[fieldValue];
|
305 | // Apply adapters to a retrieved value
|
306 | if (_this._adapterO) {
|
307 | if (_this._adapterO.isEnabled("dataContextValue")) {
|
308 | value = _this._adapterO.apply("dataContextValue", {
|
309 | field: fieldName,
|
310 | value: value,
|
311 | dataItem: dataItem
|
312 | }).value;
|
313 | }
|
314 | }
|
315 | if ($type.hasValue(value)) {
|
316 | hasSomeValues_1 = true;
|
317 | if (dataItem.hasChildren[fieldName]) {
|
318 | var template = _this.createDataItem();
|
319 | template.copyFrom(_this.mainDataSet.template);
|
320 | var children = new OrderedListTemplate(template);
|
321 | children.events.on("inserted", _this.handleDataItemAdded, _this, false);
|
322 | children.events.on("removed", _this.handleDataItemRemoved, _this, false);
|
323 | _this._dataDisposers.push(new ListDisposer(children));
|
324 | var count = value.length;
|
325 | for (var i = 0; i < count; i++) {
|
326 | var rawDataItem = value[i];
|
327 | var childDataItem = children.create();
|
328 | childDataItem.parent = dataItem;
|
329 | _this.processDataItem(childDataItem, rawDataItem);
|
330 | }
|
331 | var anyDataItem = dataItem;
|
332 | anyDataItem[fieldName] = children;
|
333 | }
|
334 | else {
|
335 | // data is converted to numbers/dates in each dataItem
|
336 | dataItem[fieldName] = value;
|
337 | }
|
338 | }
|
339 | });
|
340 | $object.each(this.propertyFields, function (key, fieldValue) {
|
341 | var f = key;
|
342 | var value = dataContext[fieldValue];
|
343 | if ($type.hasValue(value)) {
|
344 | hasSomeValues_1 = true;
|
345 | dataItem.setProperty(f, value);
|
346 | }
|
347 | });
|
348 | // @todo we might need some flag which would tell whether we should create empty data items or not.
|
349 | if (!this._addAllDataItems && !hasSomeValues_1) {
|
350 | this.mainDataSet.remove(dataItem);
|
351 | }
|
352 | }
|
353 | };
|
354 | /**
|
355 | *
|
356 | * When validating raw data, instead of processing data item, we update it
|
357 | *
|
358 | * @ignore Exclude from docs
|
359 | * @param item
|
360 | */
|
361 | Component.prototype.updateDataItem = function (dataItem) {
|
362 | var _this = this;
|
363 | if (dataItem) {
|
364 | var dataContext_1 = dataItem.dataContext;
|
365 | $object.each(this.dataFields, function (key, fieldValue) {
|
366 | var fieldName = key;
|
367 | var value = dataContext_1[fieldValue];
|
368 | // Apply adapters to a retrieved value
|
369 | if (_this._adapterO) {
|
370 | value = _this._adapterO.apply("dataContextValue", {
|
371 | field: fieldName,
|
372 | value: value,
|
373 | dataItem: dataItem
|
374 | }).value;
|
375 | }
|
376 | if ($type.hasValue(value)) {
|
377 | if (dataItem.hasChildren[fieldName]) {
|
378 | var anyDataItem = dataItem;
|
379 | var children = (anyDataItem[fieldName]);
|
380 | children.each(function (child) {
|
381 | _this.updateDataItem(child);
|
382 | });
|
383 | }
|
384 | else {
|
385 | // data is converted to numbers/dates in each dataItem
|
386 | dataItem[fieldName] = value;
|
387 | }
|
388 | }
|
389 | });
|
390 | $object.each(this.propertyFields, function (key, fieldValue) {
|
391 | var f = key;
|
392 | var value = dataContext_1[fieldValue];
|
393 | if ($type.hasValue(value)) {
|
394 | dataItem.setProperty(f, value);
|
395 | }
|
396 | });
|
397 | }
|
398 | };
|
399 | /**
|
400 | * [validateDataElements description]
|
401 | *
|
402 | * @ignore Exclude from docs
|
403 | * @todo Description
|
404 | */
|
405 | Component.prototype.validateDataElements = function () {
|
406 | var count = this.endIndex;
|
407 | for (var i = this.startIndex; i < count; i++) {
|
408 | var dataItem = this.dataItems.getIndex(i);
|
409 | // TODO is this correct
|
410 | if (dataItem) {
|
411 | this.validateDataElement(dataItem);
|
412 | }
|
413 | }
|
414 | };
|
415 | /**
|
416 | * Validates this element and its related elements.
|
417 | *
|
418 | * @ignore Exclude from docs
|
419 | */
|
420 | Component.prototype.validate = function () {
|
421 | this.validateDataElements();
|
422 | _super.prototype.validate.call(this);
|
423 | };
|
424 | /**
|
425 | * [validateDataElement description]
|
426 | *
|
427 | * @ignore Exclude from docs
|
428 | * @param dataItem [description]
|
429 | */
|
430 | Component.prototype.validateDataElement = function (dataItem) {
|
431 | };
|
432 | /**
|
433 | * Adds one or several (array) of data items to the existing data.
|
434 | *
|
435 | * @param rawDataItem One or many raw data item objects
|
436 | */
|
437 | Component.prototype.addData = function (rawDataItem, removeCount, skipRaw) {
|
438 | var _this = this;
|
439 | // need to check if data is invalid, as addData might be called multiple times
|
440 | if (!this.dataInvalid && this.inited) {
|
441 | this._parseDataFrom = this.data.length; // save length of parsed data
|
442 | }
|
443 | if (!skipRaw) {
|
444 | if (rawDataItem instanceof Array) {
|
445 | // can't use concat because new array is returned
|
446 | $array.each(rawDataItem, function (dataItem) {
|
447 | _this.data.push(dataItem);
|
448 | });
|
449 | }
|
450 | else {
|
451 | this.data.push(rawDataItem); // add to raw data array
|
452 | }
|
453 | }
|
454 | if (this.inited) {
|
455 | this.removeData(removeCount, skipRaw);
|
456 | }
|
457 | else {
|
458 | if ($type.isNumber(removeCount)) {
|
459 | while (removeCount > 0) {
|
460 | this.data.shift();
|
461 | removeCount--;
|
462 | }
|
463 | }
|
464 | }
|
465 | this.invalidateData();
|
466 | };
|
467 | /**
|
468 | * Removes elements from the beginning of data
|
469 | *
|
470 | * @param count number of elements to remove
|
471 | */
|
472 | Component.prototype.removeData = function (count, skipRaw) {
|
473 | if ($type.isNumber(count) && count > 0) {
|
474 | while (count > 0) {
|
475 | var dataItem = this.mainDataSet.getIndex(0);
|
476 | if (dataItem) {
|
477 | this.mainDataSet.remove(dataItem);
|
478 | }
|
479 | this.dataUsers.each(function (dataUser) {
|
480 | if (!dataUser.data || dataUser.data.length == 0) {
|
481 | var dataItem_1 = dataUser.mainDataSet.getIndex(0);
|
482 | if (dataItem_1) {
|
483 | dataUser.mainDataSet.remove(dataItem_1);
|
484 | }
|
485 | }
|
486 | });
|
487 | if (!skipRaw) {
|
488 | this.data.shift();
|
489 | }
|
490 | if (this._parseDataFrom > 0) {
|
491 | this._parseDataFrom--;
|
492 | }
|
493 | count--;
|
494 | }
|
495 | // changed from invalidateData since 4.7.19 to solve #51551
|
496 | this.invalidateDataItems();
|
497 | }
|
498 | };
|
499 | /**
|
500 | * Triggers a data (re)parsing.
|
501 | *
|
502 | * @ignore Exclude from docs
|
503 | */
|
504 | Component.prototype.invalidateData = function () {
|
505 | if (this.disabled || this.isTemplate) {
|
506 | return;
|
507 | }
|
508 | //if(!this.dataInvalid){
|
509 | registry.addToInvalidComponents(this);
|
510 | system.requestFrame();
|
511 | this.dataInvalid = true;
|
512 | $iter.each(this.dataUsers.iterator(), function (x) {
|
513 | x.invalidateDataItems();
|
514 | });
|
515 | //}
|
516 | };
|
517 | /**
|
518 | * [invalidateDataUsers description]
|
519 | *
|
520 | * @ignore Exclude from docs
|
521 | * @todo Description
|
522 | */
|
523 | Component.prototype.invalidateDataUsers = function () {
|
524 | $iter.each(this.dataUsers.iterator(), function (x) {
|
525 | x.invalidate();
|
526 | });
|
527 | };
|
528 | /**
|
529 | * Invalidates data values. When data array is not changed, but values within
|
530 | * it changes, we invalidate data so that component would process changes.
|
531 | *
|
532 | * @ignore Exclude from docs
|
533 | */
|
534 | Component.prototype.invalidateDataItems = function () {
|
535 | if (this.disabled || this.isTemplate) {
|
536 | return;
|
537 | }
|
538 | //if(!this.dataItemsInvalid){
|
539 | $array.move(registry.invalidDataItems, this);
|
540 | system.requestFrame();
|
541 | this.dataItemsInvalid = true;
|
542 | $iter.each(this.dataUsers.iterator(), function (x) {
|
543 | x.invalidateDataItems();
|
544 | });
|
545 | //}
|
546 | };
|
547 | /**
|
548 | * Invalidates data range. This is done when data which must be shown
|
549 | * changes (chart is zoomed for example).
|
550 | *
|
551 | * @ignore Exclude from docs
|
552 | */
|
553 | Component.prototype.invalidateDataRange = function () {
|
554 | if (this.disabled || this.isTemplate) {
|
555 | return;
|
556 | }
|
557 | //if(!this.dataRangeInvalid){
|
558 | this.dataRangeInvalid = true;
|
559 | $array.move(registry.invalidDataRange, this);
|
560 | system.requestFrame();
|
561 | //}
|
562 | };
|
563 | /**
|
564 | * Processes data range.
|
565 | *
|
566 | * @todo Description
|
567 | * @ignore Exclude from docs
|
568 | */
|
569 | Component.prototype.validateDataRange = function () {
|
570 | $array.remove(registry.invalidDataRange, this);
|
571 | this.dataRangeInvalid = false;
|
572 | if (this.startIndex != this._prevStartIndex || this.endIndex != this._prevEndIndex) {
|
573 | this.rangeChangeUpdate();
|
574 | this.appendDataItems();
|
575 | this.invalidate();
|
576 | this.dispatchImmediately("datarangechanged");
|
577 | }
|
578 | };
|
579 | /**
|
580 | * [sliceData description]
|
581 | *
|
582 | * @todo Description
|
583 | * @ignore Exclude from docs
|
584 | */
|
585 | Component.prototype.sliceData = function () {
|
586 | this._workingStartIndex = this.startIndex;
|
587 | this._workingEndIndex = this.endIndex;
|
588 | };
|
589 | /**
|
590 | * [rangeChangeUpdate description]
|
591 | *
|
592 | * @todo Description
|
593 | * @ignore Exclude from docs
|
594 | */
|
595 | Component.prototype.rangeChangeUpdate = function () {
|
596 | this.sliceData();
|
597 | this._prevStartIndex = this.startIndex;
|
598 | this._prevEndIndex = this.endIndex;
|
599 | };
|
600 | /**
|
601 | * [appendDataItems description]
|
602 | *
|
603 | * @todo Description
|
604 | * @ignore Exclude from docs
|
605 | */
|
606 | Component.prototype.appendDataItems = function () {
|
607 | // TODO use an iterator instead
|
608 | var count = this.endIndex;
|
609 | for (var i = this.startIndex; i < count; i++) {
|
610 | // data item
|
611 | var dataItem = this.dataItems.getIndex(i);
|
612 | if (dataItem) {
|
613 | dataItem.__disabled = false;
|
614 | }
|
615 | }
|
616 | for (var i = 0; i < this.startIndex; i++) {
|
617 | var dataItem = this.dataItems.getIndex(i);
|
618 | if (dataItem) {
|
619 | dataItem.__disabled = true;
|
620 | }
|
621 | }
|
622 | for (var i = this.endIndex; i < this.dataItems.length; i++) {
|
623 | var dataItem = this.dataItems.getIndex(i);
|
624 | if (dataItem) {
|
625 | dataItem.__disabled = true;
|
626 | }
|
627 | }
|
628 | };
|
629 | /**
|
630 | * If you want to have a smooth transition from one data values to another, you change your raw data and then you must call this method.
|
631 | * then instead of redrawing everything, the chart will check raw data and smoothly transit from previous to new data
|
632 | */
|
633 | Component.prototype.invalidateRawData = function () {
|
634 | if (this.disabled || this.isTemplate) {
|
635 | return;
|
636 | }
|
637 | //if(!this.rawDataInvalid){
|
638 | $array.move(registry.invalidRawDatas, this);
|
639 | system.requestFrame();
|
640 | this.rawDataInvalid = true;
|
641 | $iter.each(this.dataUsers.iterator(), function (x) {
|
642 | x.invalidateRawData();
|
643 | });
|
644 | //}
|
645 | };
|
646 | /**
|
647 | * @ignore
|
648 | */
|
649 | Component.prototype.validateRawData = function () {
|
650 | var _this = this;
|
651 | $array.remove(registry.invalidRawDatas, this);
|
652 | $iter.each(this.mainDataSet.iterator(), function (dataItem) {
|
653 | if (dataItem) {
|
654 | _this.updateDataItem(dataItem);
|
655 | }
|
656 | });
|
657 | };
|
658 | /**
|
659 | * Destroys this object and all related data.
|
660 | */
|
661 | Component.prototype.dispose = function () {
|
662 | var _this = this;
|
663 | this.mainDataSet.template.clones.clear();
|
664 | $object.each(this._dataSources, function (key, source) {
|
665 | _this.removeDispose(source);
|
666 | });
|
667 | this.disposeData();
|
668 | _super.prototype.dispose.call(this);
|
669 | };
|
670 | /**
|
671 | * @ignore
|
672 | */
|
673 | Component.prototype.disposeData = function () {
|
674 | this.mainDataSet.template.clones.clear();
|
675 | $array.each(this._dataDisposers, function (x) {
|
676 | x.dispose();
|
677 | });
|
678 | // and for all components
|
679 | $iter.each(this.dataUsers.iterator(), function (dataUser) {
|
680 | dataUser.disposeData();
|
681 | });
|
682 | this._dataDisposers.length = 0;
|
683 | this._startIndex = undefined;
|
684 | this._endIndex = undefined;
|
685 | // dispose old
|
686 | this.mainDataSet.clear();
|
687 | this.mainDataSet.template.clones.clear();
|
688 | if (this._dataSets) {
|
689 | this._dataSets.clear();
|
690 | }
|
691 | };
|
692 | Component.prototype.getDataItem = function (dataContext) {
|
693 | return this.mainDataSet.create();
|
694 | };
|
695 | /**
|
696 | * Validates (processes) data.
|
697 | *
|
698 | * @ignore Exclude from docs
|
699 | */
|
700 | Component.prototype.validateData = function () {
|
701 | this.dispatchImmediately("beforedatavalidated");
|
702 | this.dataInvalid = false;
|
703 | registry.removeFromInvalidComponents(this);
|
704 | if (this.__disabled) {
|
705 | return;
|
706 | }
|
707 | this.dataValidationProgress = 0;
|
708 | // need this to slice new data
|
709 | this._prevStartIndex = undefined;
|
710 | this._prevEndIndex = undefined;
|
711 | // todo: this needs some overthinking, maybe some extra settings like zoomOotonDataupdate like in v3 or so. some charts like pie chart probably should act like this always
|
712 | this._startIndex = undefined;
|
713 | this._endIndex = undefined;
|
714 | if (this.dataFields.data && this.dataItem) {
|
715 | var dataContext = this.dataItem.dataContext;
|
716 | this._data = dataContext[this.dataFields.data];
|
717 | }
|
718 | // data items array is reset only if all data is validated, if _parseDataFrom is not 0, we append new data only
|
719 | // check heatmap demo if uncommented
|
720 | // fixed both issues by adding && this.data.length > 0
|
721 | // check adding series example if changed
|
722 | if (this._parseDataFrom === 0 && this.data.length > 0) {
|
723 | this.disposeData();
|
724 | }
|
725 | if (this.data.length > 0) {
|
726 | var preloader = this.preloader;
|
727 | // and for all components
|
728 | $iter.each(this.dataUsers.iterator(), function (dataUser) {
|
729 | // todo: this needs some overthinking, maybe some extra settings like zoomOUtonDataupdate like in v3 or so. some charts like pie chart probably should act like this always
|
730 | dataUser._startIndex = undefined;
|
731 | dataUser._endIndex = undefined;
|
732 | });
|
733 | var counter = 0;
|
734 | var startTime = Date.now();
|
735 | // parse data
|
736 | var i = this._parseDataFrom;
|
737 | var n = this.data.length;
|
738 | var _loop_1 = function () {
|
739 | var rawDataItem = this_1.data[i];
|
740 | if (this_1._usesData) {
|
741 | var dataItem = this_1.getDataItem(rawDataItem);
|
742 | this_1.processDataItem(dataItem, rawDataItem);
|
743 | }
|
744 | this_1.dataUsers.each(function (dataUser) {
|
745 | if (dataUser.data.length == 0) { // checking if data is not set directly
|
746 | var dataUserDataItem = dataUser.getDataItem(rawDataItem);
|
747 | dataUser.processDataItem(dataUserDataItem, rawDataItem);
|
748 | }
|
749 | });
|
750 | counter++;
|
751 | // show preloader if this takes too many time
|
752 | if (counter == 100) { // no need to check it on each data item
|
753 | counter = 0;
|
754 | var elapsed = Date.now() - startTime;
|
755 | if (elapsed > this_1.parsingStepDuration) {
|
756 | if (i < this_1.data.length - 10) {
|
757 | this_1._parseDataFrom = i + 1;
|
758 | // update preloader
|
759 | if (preloader) {
|
760 | if (i / this_1.data.length > 0.5 && !preloader.visible) {
|
761 | // do not start showing
|
762 | }
|
763 | else {
|
764 | preloader.progress = i / this_1.data.length;
|
765 | }
|
766 | }
|
767 | this_1.dataValidationProgress = i / this_1.data.length;
|
768 | i = this_1.data.length; // stops cycle
|
769 | this_1.invalidateData();
|
770 | return { value: void 0 };
|
771 | }
|
772 | }
|
773 | }
|
774 | };
|
775 | var this_1 = this;
|
776 | for (i; i < n; i++) {
|
777 | var state_1 = _loop_1();
|
778 | if (typeof state_1 === "object")
|
779 | return state_1.value;
|
780 | }
|
781 | if (preloader) {
|
782 | preloader.progress = 1;
|
783 | }
|
784 | this.dataUsers.each(function (dataUser) {
|
785 | if (dataUser.hidden || (dataUser.appeared && !dataUser.visible && dataUser.stacked)) {
|
786 | dataUser.hide(0);
|
787 | }
|
788 | });
|
789 | }
|
790 | this.dataValidationProgress = 1;
|
791 | this._parseDataFrom = 0; // reset this index, it is set to dataItems.length if addData() method was used.
|
792 | this.invalidateDataItems();
|
793 | if (!this._internalDefaultsApplied) {
|
794 | this.applyInternalDefaults();
|
795 | }
|
796 | this.dispatch("datavalidated"); // can't zoom chart if dispatched immediately
|
797 | };
|
798 | /**
|
799 | * Validates (processes) data items.
|
800 | *
|
801 | * @ignore Exclude from docs
|
802 | */
|
803 | Component.prototype.validateDataItems = function () {
|
804 | $array.remove(registry.invalidDataItems, this);
|
805 | this.dataItemsInvalid = false;
|
806 | this.invalidateDataRange();
|
807 | this.invalidate();
|
808 | this.dispatch("dataitemsvalidated");
|
809 | };
|
810 | Object.defineProperty(Component.prototype, "data", {
|
811 | /**
|
812 | * Returns element's source (raw) data.
|
813 | *
|
814 | * @return Data
|
815 | */
|
816 | get: function () {
|
817 | if (!this._data) {
|
818 | this._data = [];
|
819 | }
|
820 | if (!this._adapterO) {
|
821 | return this._data;
|
822 | }
|
823 | else {
|
824 | return this._adapterO.apply("data", this._data);
|
825 | }
|
826 | },
|
827 | /**
|
828 | * Sets source (raw) data for the element. The "data" is always an `Array`
|
829 | * of objects.
|
830 | *
|
831 | * IMPORTANT: The order of data items in `data` array is important as it
|
832 | * might affect chart look and behavior. [More details](https://www.amcharts.com/docs/v4/concepts/data/#Order_of_data_items).
|
833 | *
|
834 | * @param value Data
|
835 | */
|
836 | set: function (value) {
|
837 | this.setData(value);
|
838 | },
|
839 | enumerable: true,
|
840 | configurable: true
|
841 | });
|
842 | Component.prototype.setData = function (value) {
|
843 | // array might be the same, but there might be items added
|
844 | // todo: check if array changed, toString maybe?
|
845 | if (!this.isDisposed()) {
|
846 | this._parseDataFrom = 0;
|
847 | this.disposeData();
|
848 | this._data = value;
|
849 | if (value && value.length > 0) {
|
850 | this.invalidateData();
|
851 | }
|
852 | else {
|
853 | this.dispatchImmediately("beforedatavalidated");
|
854 | this.dispatch("datavalidated");
|
855 | }
|
856 | }
|
857 | };
|
858 | /**
|
859 | * Returns (creates if necessary) a [[DataSource]] bound to any specific
|
860 | * property.
|
861 | *
|
862 | * For example if I want to bind `data` to an external JSON file, I'd create
|
863 | * a DataSource for it.
|
864 | *
|
865 | * @param property Property to bind external data to
|
866 | * @return A DataSource for property
|
867 | */
|
868 | Component.prototype.getDataSource = function (property) {
|
869 | var _this = this;
|
870 | if (!$type.hasValue(this._dataSources[property])) {
|
871 | this._dataSources[property] = new DataSource();
|
872 | this._dataSources[property].component = this;
|
873 | this.setDataSourceEvents(this._dataSources[property], property);
|
874 | this._dataSources[property].adapter.add("dateFields", function (val) {
|
875 | return _this.dataSourceDateFields(val);
|
876 | });
|
877 | this._dataSources[property].adapter.add("numberFields", function (val) {
|
878 | return _this.dataSourceNumberFields(val);
|
879 | });
|
880 | this.events.on("inited", function () {
|
881 | _this.loadData(property);
|
882 | }, this, false);
|
883 | }
|
884 | return this._dataSources[property];
|
885 | };
|
886 | Object.defineProperty(Component.prototype, "dataSource", {
|
887 | /**
|
888 | * @return Data source
|
889 | */
|
890 | get: function () {
|
891 | if (!this._dataSources["data"]) {
|
892 | this.getDataSource("data");
|
893 | }
|
894 | return this._dataSources["data"];
|
895 | },
|
896 | /**
|
897 | *A [[DataSource]] to be used for loading Component's data.
|
898 | *
|
899 | * @see {@link https://www.amcharts.com/docs/v4/concepts/loading-external-data/} for more on loading external data
|
900 | * @param value Data source
|
901 | */
|
902 | set: function (value) {
|
903 | var _this = this;
|
904 | if (this._dataSources["data"]) {
|
905 | this.removeDispose(this._dataSources["data"]);
|
906 | }
|
907 | this._dataSources["data"] = value;
|
908 | this._dataSources["data"].component = this;
|
909 | this.events.on("inited", function () {
|
910 | _this.loadData("data");
|
911 | }, this, false);
|
912 | this.setDataSourceEvents(value, "data");
|
913 | },
|
914 | enumerable: true,
|
915 | configurable: true
|
916 | });
|
917 | /**
|
918 | * Initiates loading of the external data via [[DataSource]].
|
919 | *
|
920 | * @ignore Exclude from docs
|
921 | */
|
922 | Component.prototype.loadData = function (property) {
|
923 | this._dataSources[property].load();
|
924 | };
|
925 | /**
|
926 | * This function is called by the [[DataSource]]'s `dateFields` adapater
|
927 | * so that particular chart types can popuplate this setting with their
|
928 | * own type-specific data fields so they are parsed properly.
|
929 | *
|
930 | * @ignore Exclude from docs
|
931 | * @param value Array of date fields
|
932 | * @return Array of date fields populated with chart's date fields
|
933 | */
|
934 | Component.prototype.dataSourceDateFields = function (value) {
|
935 | return value;
|
936 | };
|
937 | /**
|
938 | * This function is called by the [[DataSource]]'s `numberFields` adapater
|
939 | * so that particular chart types can popuplate this setting with their
|
940 | * own type-specific data fields so they are parsed properly.
|
941 | *
|
942 | * @ignore Exclude from docs
|
943 | * @param value Array of number fields
|
944 | * @return Array of number fields populated with chart's number fields
|
945 | */
|
946 | Component.prototype.dataSourceNumberFields = function (value) {
|
947 | return value;
|
948 | };
|
949 | /**
|
950 | *
|
951 | * @ignore Exclude from docs
|
952 | * @todo Description
|
953 | * @param list [description]
|
954 | * @param dataFields [description]
|
955 | * @param targetList [description]
|
956 | * @return [description]
|
957 | */
|
958 | Component.prototype.populateDataSourceFields = function (list, dataFields, targetList) {
|
959 | $array.each(targetList, function (value) {
|
960 | if (dataFields[value] && $array.indexOf(list, dataFields[value]) === -1) {
|
961 | list.push(dataFields[value]);
|
962 | }
|
963 | });
|
964 | return list;
|
965 | };
|
966 | /**
|
967 | * Sets events on a [[DataSource]].
|
968 | *
|
969 | * @ignore Exclude from docs
|
970 | */
|
971 | Component.prototype.setDataSourceEvents = function (ds, property) {
|
972 | var _this = this;
|
973 | ds.events.on("started", function (ev) {
|
974 | var preloader = _this.preloader;
|
975 | if (preloader) {
|
976 | preloader.progress = 0;
|
977 | //preloader.label.text = this.language.translate("Loading");
|
978 | }
|
979 | }, undefined, false);
|
980 | ds.events.on("loadstarted", function (ev) {
|
981 | var preloader = _this.preloader;
|
982 | if (preloader) {
|
983 | preloader.progress = 0.25;
|
984 | }
|
985 | }, undefined, false);
|
986 | ds.events.on("loadended", function (ev) {
|
987 | var preloader = _this.preloader;
|
988 | if (preloader) {
|
989 | preloader.progress = 0.5;
|
990 | }
|
991 | }, undefined, false);
|
992 | ds.events.on("parseended", function (ev) {
|
993 | var preloader = _this.preloader;
|
994 | if (preloader) {
|
995 | preloader.progress = 0.75;
|
996 | }
|
997 | }, undefined, false);
|
998 | ds.events.on("ended", function (ev) {
|
999 | var preloader = _this.preloader;
|
1000 | if (preloader) {
|
1001 | preloader.progress = 1;
|
1002 | }
|
1003 | }, undefined, false);
|
1004 | ds.events.on("error", function (ev) {
|
1005 | var preloader = _this.preloader;
|
1006 | if (preloader) {
|
1007 | preloader.progress = 1;
|
1008 | }
|
1009 | _this.openModal(ev.message);
|
1010 | }, undefined, false);
|
1011 | if (property) {
|
1012 | ds.events.on("done", function (ev) {
|
1013 | var preloader = _this.preloader;
|
1014 | if (preloader) {
|
1015 | preloader.progress = 1;
|
1016 | }
|
1017 | if (property == "data" && !$type.isArray(ev.data)) {
|
1018 | ev.data = [ev.data];
|
1019 | }
|
1020 | if (ds.incremental && property == "data" && _this.data.length) {
|
1021 | _this.addData(ev.data, ds.keepCount ? ev.data.length : 0);
|
1022 | }
|
1023 | else if (ds.updateCurrentData && property == "data" && _this.data.length) {
|
1024 | // cycle through existing data items
|
1025 | $array.each(_this.data, function (item, index) {
|
1026 | if ($type.hasValue(ev.data[index])) {
|
1027 | $object.each(item, function (key, val) {
|
1028 | if ($type.hasValue(ev.data[index][key])) {
|
1029 | item[key] = ev.data[index][key];
|
1030 | }
|
1031 | });
|
1032 | }
|
1033 | });
|
1034 | _this.invalidateRawData();
|
1035 | }
|
1036 | else {
|
1037 | _this[property] = ev.data;
|
1038 | }
|
1039 | });
|
1040 | }
|
1041 | };
|
1042 | Object.defineProperty(Component.prototype, "responsive", {
|
1043 | /**
|
1044 | * @return Responsive rules handler
|
1045 | */
|
1046 | get: function () {
|
1047 | if (!this._responsive) {
|
1048 | this._responsive = new Responsive();
|
1049 | this._responsive.component = this;
|
1050 | }
|
1051 | return this._responsive;
|
1052 | },
|
1053 | /**
|
1054 | * A [[Responsive]] instance to be used when applying conditional
|
1055 | * property values.
|
1056 | *
|
1057 | * NOTE: Responsive features are currently in development and may not work
|
1058 | * as expected, if at all.
|
1059 | *
|
1060 | * @param value Responsive rules handler
|
1061 | */
|
1062 | set: function (value) {
|
1063 | this._responsive = value;
|
1064 | this._responsive.component = this;
|
1065 | },
|
1066 | enumerable: true,
|
1067 | configurable: true
|
1068 | });
|
1069 | /**
|
1070 | * Sets current zoom.
|
1071 | *
|
1072 | * The range uses relative values from 0 to 1, with 0 marking beginning and 1
|
1073 | * marking end of the available data range.
|
1074 | *
|
1075 | * This method will not have any effect when called on a chart object.
|
1076 | * Since the chart can have a number of axes and series, each with its own
|
1077 | * data, the meaning of "range" is very ambiguous.
|
1078 | *
|
1079 | * To zoom the chart use `zoom*` methods on its respective axes.
|
1080 | *
|
1081 | * @param range Range
|
1082 | * @param skipRangeEvent Should rangechanged event not be triggered?
|
1083 | * @param instantly Do not animate?
|
1084 | * @return Actual modidied range (taking `maxZoomFactor` into account)
|
1085 | */
|
1086 | Component.prototype.zoom = function (range, skipRangeEvent, instantly, declination) {
|
1087 | var _this = this;
|
1088 | if (skipRangeEvent === void 0) { skipRangeEvent = false; }
|
1089 | if (instantly === void 0) { instantly = false; }
|
1090 | var start = range.start;
|
1091 | var end = range.end;
|
1092 | var priority = range.priority;
|
1093 | if (range.start == range.end) {
|
1094 | range.start = range.start - 0.5 / this.maxZoomFactor;
|
1095 | range.end = range.end + 0.5 / this.maxZoomFactor;
|
1096 | }
|
1097 | if (priority == "end" && end == 1 && start != 0) {
|
1098 | if (start < this.start) {
|
1099 | priority = "start";
|
1100 | }
|
1101 | }
|
1102 | if (priority == "start" && start == 0) {
|
1103 | if (end > this.end) {
|
1104 | priority = "end";
|
1105 | }
|
1106 | }
|
1107 | if (!$type.isNumber(declination)) {
|
1108 | declination = this.maxZoomDeclination;
|
1109 | }
|
1110 | if (!$type.isNumber(start) || !$type.isNumber(end)) {
|
1111 | return { start: this.start, end: this.end };
|
1112 | }
|
1113 | if (this._finalStart != start || this._finalEnd != end) {
|
1114 | var maxZoomFactor = this.maxZoomFactor / this.minZoomCount;
|
1115 | var minZoomFactor = this.maxZoomFactor / this.maxZoomCount;
|
1116 | // most likely we are dragging left scrollbar grip here, so we tend to modify end
|
1117 | if (priority == "start") {
|
1118 | if (this.maxZoomCount > 0) {
|
1119 | // add to the end
|
1120 | if (1 / (end - start) < minZoomFactor) {
|
1121 | end = start + 1 / minZoomFactor;
|
1122 | }
|
1123 | }
|
1124 | // add to the end
|
1125 | if (1 / (end - start) > maxZoomFactor) {
|
1126 | end = start + 1 / maxZoomFactor;
|
1127 | }
|
1128 | //unless end is > 0
|
1129 | if (end > 1 && end - start < 1 / maxZoomFactor) {
|
1130 | //end = 1;
|
1131 | start = end - 1 / maxZoomFactor;
|
1132 | }
|
1133 | }
|
1134 | // most likely we are dragging right, so we modify left
|
1135 | else {
|
1136 | if (this.maxZoomCount > 0) {
|
1137 | // add to the end
|
1138 | if (1 / (end - start) < minZoomFactor) {
|
1139 | start = end - 1 / minZoomFactor;
|
1140 | }
|
1141 | }
|
1142 | // remove from start
|
1143 | if (1 / (end - start) > maxZoomFactor) {
|
1144 | if (start <= 0) {
|
1145 | end = start + 1 / maxZoomFactor;
|
1146 | }
|
1147 | else {
|
1148 | start = end - 1 / maxZoomFactor;
|
1149 | }
|
1150 | }
|
1151 | if (start < 0 && end - start < 1 / maxZoomFactor) {
|
1152 | //start = 0;
|
1153 | end = start + 1 / maxZoomFactor;
|
1154 | }
|
1155 | }
|
1156 | if (start < -declination) {
|
1157 | start = -declination;
|
1158 | }
|
1159 | if (1 / (end - start) > maxZoomFactor) {
|
1160 | end = start + 1 / maxZoomFactor;
|
1161 | }
|
1162 | if (end > 1 + declination) {
|
1163 | end = 1 + declination;
|
1164 | }
|
1165 | if (1 / (end - start) > maxZoomFactor) {
|
1166 | start = end - 1 / maxZoomFactor;
|
1167 | }
|
1168 | this._finalEnd = end;
|
1169 | this._finalStart = start;
|
1170 | this.skipRangeEvent = skipRangeEvent;
|
1171 | this.dispatchImmediately("rangechangestarted");
|
1172 | if (this.rangeChangeDuration > 0 && !instantly) {
|
1173 | // todo: maybe move this to Animation
|
1174 | var rangeChangeAnimation = this.rangeChangeAnimation;
|
1175 | if (rangeChangeAnimation && rangeChangeAnimation.progress < 1) {
|
1176 | var options = rangeChangeAnimation.animationOptions;
|
1177 | if (options.length > 1) {
|
1178 | if (options[0].to == start && options[1].to == end) {
|
1179 | return { start: start, end: end };
|
1180 | }
|
1181 | else {
|
1182 | if (!rangeChangeAnimation.isDisposed()) {
|
1183 | rangeChangeAnimation.stop();
|
1184 | }
|
1185 | }
|
1186 | }
|
1187 | }
|
1188 | if (this.rangeChangeAnimation) {
|
1189 | this.rangeChangeAnimation.kill();
|
1190 | }
|
1191 | rangeChangeAnimation = this.animate([{ property: "start", to: start }, { property: "end", to: end }], this.rangeChangeDuration, this.rangeChangeEasing);
|
1192 | this.rangeChangeAnimation = rangeChangeAnimation;
|
1193 | if (rangeChangeAnimation && !rangeChangeAnimation.isFinished()) {
|
1194 | rangeChangeAnimation.events.on("animationended", function () {
|
1195 | _this.dispatchImmediately("rangechangeended");
|
1196 | });
|
1197 | }
|
1198 | else {
|
1199 | this.dispatchImmediately("rangechangeended");
|
1200 | }
|
1201 | }
|
1202 | else {
|
1203 | this.start = start;
|
1204 | this.end = end;
|
1205 | this.dispatch("rangechangeended");
|
1206 | }
|
1207 | }
|
1208 | return { start: start, end: end };
|
1209 | };
|
1210 | /**
|
1211 | * Zooms to specific data items using their index in data.
|
1212 | *
|
1213 | * This method will not have any effect when called on a chart object.
|
1214 | * Since the chart can have a number of axes and series, each with its own
|
1215 | * data, the meaning of "index" is very ambiguous.
|
1216 | *
|
1217 | * To zoom the chart use `zoom*` methods on its respective axes.
|
1218 | *
|
1219 | * @param startIndex Index of the starting data item
|
1220 | * @param endIndex Index of the ending data item
|
1221 | * @param skipRangeEvent Should rangechanged event not be triggered?
|
1222 | * @param instantly Do not animate?
|
1223 | */
|
1224 | Component.prototype.zoomToIndexes = function (startIndex, endIndex, skipRangeEvent, instantly) {
|
1225 | if (!$type.isNumber(startIndex) || !$type.isNumber(endIndex)) {
|
1226 | return;
|
1227 | }
|
1228 | var start = startIndex / this.dataItems.length;
|
1229 | var end = endIndex / this.dataItems.length;
|
1230 | this.zoom({ start: start, end: end }, skipRangeEvent, instantly);
|
1231 | };
|
1232 | Object.defineProperty(Component.prototype, "zoomFactor", {
|
1233 | /**
|
1234 | * A current zoom factor (0-1). 1 meaning fully zoomed out. (showing all of
|
1235 | * the available data)
|
1236 | *
|
1237 | * @return Zoom factor
|
1238 | */
|
1239 | get: function () {
|
1240 | return $math.fitToRange(1 / (this.end - this.start), 1, this.maxZoomFactor);
|
1241 | },
|
1242 | enumerable: true,
|
1243 | configurable: true
|
1244 | });
|
1245 | Object.defineProperty(Component.prototype, "maxZoomFactor", {
|
1246 | /**
|
1247 | * @return Maximum zoomFactor
|
1248 | */
|
1249 | get: function () {
|
1250 | return this.getPropertyValue("maxZoomFactor");
|
1251 | },
|
1252 | /**
|
1253 | * Max available `zoomFactor`.
|
1254 | *
|
1255 | * The element will not allow zoom to occur beyond this factor.
|
1256 | *
|
1257 | * [[DateAxis]] and [[CategoryAxis]] calculate this atutomatically so that
|
1258 | * category axis could be zoomed to one category and date axis allows to be
|
1259 | * zoomed up to one base interval.
|
1260 | *
|
1261 | * In case you want to restrict category or date axis to be zoomed to more
|
1262 | * than one category or more than one base interval, use `minZoomCount`
|
1263 | * property (set it to `> 1`).
|
1264 | *
|
1265 | * Default value of [[ValueAxis]]'s `maxZoomFactor` is `1000`.
|
1266 | *
|
1267 | * Feel free to modify it to allow bigger zoom or to restrict zooming.
|
1268 | *
|
1269 | * @param value Maximum zoomFactor
|
1270 | */
|
1271 | set: function (value) {
|
1272 | if (this.setPropertyValue("maxZoomFactor", value)) {
|
1273 | if (value == 1) {
|
1274 | this.maxZoomDeclination = 0;
|
1275 | }
|
1276 | this.invalidateDataRange();
|
1277 | }
|
1278 | },
|
1279 | enumerable: true,
|
1280 | configurable: true
|
1281 | });
|
1282 | Object.defineProperty(Component.prototype, "maxZoomDeclination", {
|
1283 | /**
|
1284 | * @ignore
|
1285 | * @return Maximum zoom declination
|
1286 | */
|
1287 | get: function () {
|
1288 | return this.getPropertyValue("maxZoomDeclination");
|
1289 | },
|
1290 | /**
|
1291 | * Max zoom declination.
|
1292 | *
|
1293 | * @ignore
|
1294 | * @default 1
|
1295 | * @param value Maximum zoom declination
|
1296 | */
|
1297 | set: function (value) {
|
1298 | if (this.setPropertyValue("maxZoomDeclination", value)) {
|
1299 | this.invalidateDataRange();
|
1300 | }
|
1301 | },
|
1302 | enumerable: true,
|
1303 | configurable: true
|
1304 | });
|
1305 | Object.defineProperty(Component.prototype, "startIndex", {
|
1306 | /**
|
1307 | * Current starting index.
|
1308 | *
|
1309 | * @return Start index
|
1310 | */
|
1311 | get: function () {
|
1312 | if (!$type.isNumber(this._startIndex)) {
|
1313 | this._startIndex = 0;
|
1314 | }
|
1315 | return this._startIndex;
|
1316 | },
|
1317 | /**
|
1318 | * Sets current starting index.
|
1319 | *
|
1320 | * @ignore Exclude from docs
|
1321 | * @param value Start index
|
1322 | */
|
1323 | set: function (value) {
|
1324 | this._startIndex = $math.fitToRange(Math.round(value), 0, this.dataItems.length);
|
1325 | //this._workingStartIndex = this._startIndex; // not good, breaks adjusted working start index of line series
|
1326 | this.start = this.indexToPosition(this._startIndex);
|
1327 | },
|
1328 | enumerable: true,
|
1329 | configurable: true
|
1330 | });
|
1331 | /**
|
1332 | * @ignore
|
1333 | * @todo:review description
|
1334 | * returns item's relative position by the index of the item
|
1335 | * @param index
|
1336 | */
|
1337 | Component.prototype.indexToPosition = function (index) {
|
1338 | return index / this.dataItems.length;
|
1339 | };
|
1340 | Object.defineProperty(Component.prototype, "endIndex", {
|
1341 | /**
|
1342 | * Current ending index.
|
1343 | *
|
1344 | * @return End index
|
1345 | */
|
1346 | get: function () {
|
1347 | var count = this.dataItems.length;
|
1348 | if (!$type.isNumber(this._endIndex) || this._endIndex > count) {
|
1349 | this._endIndex = count;
|
1350 | }
|
1351 | return this._endIndex;
|
1352 | },
|
1353 | /**
|
1354 | * Sets current ending index.
|
1355 | *
|
1356 | * @ignore Exclude from docs
|
1357 | * @param value End index
|
1358 | */
|
1359 | set: function (value) {
|
1360 | this._endIndex = $math.fitToRange(Math.round(value), 0, this.dataItems.length);
|
1361 | //this._workingEndIndex = this._endIndex; // not good, breaks adjusted workingend index of line series
|
1362 | this.end = this.indexToPosition(this._endIndex);
|
1363 | },
|
1364 | enumerable: true,
|
1365 | configurable: true
|
1366 | });
|
1367 | Object.defineProperty(Component.prototype, "start", {
|
1368 | /**
|
1369 | * @return Start (0-1)
|
1370 | */
|
1371 | get: function () {
|
1372 | if (!this._adapterO) {
|
1373 | return this._start;
|
1374 | }
|
1375 | else {
|
1376 | return this._adapterO.apply("start", this._start);
|
1377 | }
|
1378 | },
|
1379 | /**
|
1380 | * Start of the current data range (zoom).
|
1381 | *
|
1382 | * These are relative values from 0 (beginning) to 1 (end).
|
1383 | *
|
1384 | * @param value Start (0-1)
|
1385 | */
|
1386 | set: function (value) {
|
1387 | // value = $math.round(value, 10); not good
|
1388 | //if (1 / (this.end - value) > this.maxZoomFactor) {
|
1389 | // value = this.end - 1 / this.maxZoomFactor;
|
1390 | //}
|
1391 | if (this._start != value) {
|
1392 | this._start = value;
|
1393 | var startIndex = Math.max(0, Math.floor(this.dataItems.length * value) || 0);
|
1394 | this._startIndex = Math.min(startIndex, this.dataItems.length);
|
1395 | this.invalidateDataRange();
|
1396 | this.invalidate();
|
1397 | this.dispatchImmediately("startchanged");
|
1398 | this.dispatch("startendchanged");
|
1399 | }
|
1400 | },
|
1401 | enumerable: true,
|
1402 | configurable: true
|
1403 | });
|
1404 | Object.defineProperty(Component.prototype, "end", {
|
1405 | /**
|
1406 | * @return End (0-1)
|
1407 | */
|
1408 | get: function () {
|
1409 | if (!this._adapterO) {
|
1410 | return this._end;
|
1411 | }
|
1412 | else {
|
1413 | return this._adapterO.apply("end", this._end);
|
1414 | }
|
1415 | },
|
1416 | /**
|
1417 | * End of the current data range (zoom).
|
1418 | *
|
1419 | * These are relative values from 0 (beginning) to 1 (end).
|
1420 | *
|
1421 | * @param value End (0-1)
|
1422 | */
|
1423 | set: function (value) {
|
1424 | // value = $math.round(value, 10); // not good
|
1425 | //if (1 / (value - this.start) > this.maxZoomFactor) {
|
1426 | // value = 1 / this.maxZoomFactor + this.start;
|
1427 | //}
|
1428 | if (this._end != value) {
|
1429 | this._end = value;
|
1430 | this._endIndex = Math.min(this.dataItems.length, Math.ceil(this.dataItems.length * value) || 0);
|
1431 | this.invalidateDataRange();
|
1432 | this.invalidate();
|
1433 | this.dispatchImmediately("endchanged");
|
1434 | this.dispatch("startendchanged");
|
1435 | }
|
1436 | },
|
1437 | enumerable: true,
|
1438 | configurable: true
|
1439 | });
|
1440 | /**
|
1441 | * [removeFromInvalids description]
|
1442 | *
|
1443 | * @ignore Exclude from docs
|
1444 | * @todo Description
|
1445 | */
|
1446 | Component.prototype.removeFromInvalids = function () {
|
1447 | _super.prototype.removeFromInvalids.call(this);
|
1448 | registry.removeFromInvalidComponents(this);
|
1449 | $array.remove(registry.invalidDataItems, this);
|
1450 | $array.remove(registry.invalidDataRange, this);
|
1451 | $array.remove(registry.invalidRawDatas, this);
|
1452 | };
|
1453 | Object.defineProperty(Component.prototype, "dataItems", {
|
1454 | /**
|
1455 | * Returns a list of source [[DataItem]] objects currently used in the chart.
|
1456 | *
|
1457 | * @return List of data items
|
1458 | */
|
1459 | get: function () {
|
1460 | if (this._currentDataSetId != "") {
|
1461 | var dataItems = this.dataSets.getKey(this._currentDataSetId);
|
1462 | if (dataItems) {
|
1463 | return dataItems;
|
1464 | }
|
1465 | }
|
1466 | return this._dataItems;
|
1467 | },
|
1468 | enumerable: true,
|
1469 | configurable: true
|
1470 | });
|
1471 | Object.defineProperty(Component.prototype, "dataSets", {
|
1472 | /**
|
1473 | * Holds data items for data sets (usually aggregated data).
|
1474 | *
|
1475 | * @ignore
|
1476 | * @since 4.7.0
|
1477 | * @return Data sets
|
1478 | */
|
1479 | get: function () {
|
1480 | if (!this._dataSets) {
|
1481 | this._dataSets = new Dictionary();
|
1482 | }
|
1483 | return this._dataSets;
|
1484 | },
|
1485 | enumerable: true,
|
1486 | configurable: true
|
1487 | });
|
1488 | /**
|
1489 | * Makes the chart use particular data set.
|
1490 | *
|
1491 | * If `id` is not provided or there is no such data set, main data will be
|
1492 | * used.
|
1493 | *
|
1494 | * @ignore
|
1495 | * @since 4.7.0
|
1496 | * @param id Data set id
|
1497 | */
|
1498 | Component.prototype.setDataSet = function (id) {
|
1499 | if (this._currentDataSetId != id) {
|
1500 | var dataSet = this.dataSets.getKey(id);
|
1501 | if (!dataSet) {
|
1502 | if (this._currentDataSetId != "") {
|
1503 | this.dataItems.each(function (dataItem) {
|
1504 | dataItem.__disabled = true;
|
1505 | });
|
1506 | this._currentDataSetId = "";
|
1507 | this.invalidateDataRange();
|
1508 | this._prevStartIndex = undefined;
|
1509 | this.dataItems.each(function (dataItem) {
|
1510 | dataItem.__disabled = false;
|
1511 | });
|
1512 | return true;
|
1513 | }
|
1514 | }
|
1515 | else {
|
1516 | this.dataItems.each(function (dataItem) {
|
1517 | dataItem.__disabled = true;
|
1518 | });
|
1519 | this._currentDataSetId = id;
|
1520 | this.invalidateDataRange();
|
1521 | this._prevStartIndex = undefined;
|
1522 | this.dataItems.each(function (dataItem) {
|
1523 | dataItem.__disabled = false;
|
1524 | });
|
1525 | return true;
|
1526 | }
|
1527 | }
|
1528 | return false;
|
1529 | };
|
1530 | Object.defineProperty(Component.prototype, "currentDataSetId", {
|
1531 | /**
|
1532 | * Returns id of the currently used data set, or `undefined` if main data set
|
1533 | * is in use.
|
1534 | *
|
1535 | * @since 4.7.0
|
1536 | * @return Current data set id
|
1537 | */
|
1538 | get: function () {
|
1539 | return this._currentDataSetId;
|
1540 | },
|
1541 | enumerable: true,
|
1542 | configurable: true
|
1543 | });
|
1544 | Object.defineProperty(Component.prototype, "mainDataSet", {
|
1545 | /**
|
1546 | * Returns reference to "main" data set (unaggregated data as it was supplied
|
1547 | * in `data`).
|
1548 | *
|
1549 | * @since 4.7.0
|
1550 | * @return Main data set
|
1551 | */
|
1552 | get: function () {
|
1553 | return this._dataItems;
|
1554 | },
|
1555 | enumerable: true,
|
1556 | configurable: true
|
1557 | });
|
1558 | /**
|
1559 | * Updates the indexes for the dataItems
|
1560 | *
|
1561 | * @ignore Exclude from docs
|
1562 | */
|
1563 | Component.prototype._updateDataItemIndexes = function (startIndex) {
|
1564 | var dataItems = this.mainDataSet.values;
|
1565 | var length = dataItems.length;
|
1566 | for (var i = startIndex; i < length; ++i) {
|
1567 | dataItems[i]._index = i;
|
1568 | }
|
1569 | };
|
1570 | /**
|
1571 | * Processes newly added [[DataItem]] as well as triggers data re-validation.
|
1572 | *
|
1573 | * @ignore Exclude from docs
|
1574 | * @param event [description]
|
1575 | */
|
1576 | Component.prototype.handleDataItemAdded = function (event) {
|
1577 | event.newValue.component = this;
|
1578 | this._updateDataItemIndexes(event.index);
|
1579 | if (!this.dataItemsInvalid) {
|
1580 | this.invalidateDataItems();
|
1581 | }
|
1582 | };
|
1583 | /**
|
1584 | * removes [[DataItem]] as well as triggers data re-validation.
|
1585 | *
|
1586 | * @ignore Exclude from docs
|
1587 | * @param event [description]
|
1588 | */
|
1589 | Component.prototype.handleDataItemRemoved = function (event) {
|
1590 | // event.oldValue.component = undefined; // not good, as some items might be not removed from component lists
|
1591 | this._updateDataItemIndexes(event.index);
|
1592 | if (!this.dataItemsInvalid) {
|
1593 | this.invalidateDataItems();
|
1594 | }
|
1595 | };
|
1596 | /**
|
1597 | * Binds a data element's field to a specific field in raw data.
|
1598 | * For example, for the very basic column chart you'd want to bind a `value`
|
1599 | * field to a field in data, such as `price`.
|
1600 | *
|
1601 | * Some more advanced Components, like [[CandlestickSeries]] need several
|
1602 | * data fields bound to data, such as ones for open, high, low and close
|
1603 | * values.
|
1604 | *
|
1605 | * @todo Example
|
1606 | * @param field Field name
|
1607 | * @param value Field name in data
|
1608 | */
|
1609 | Component.prototype.bindDataField = function (field, value) {
|
1610 | this.dataFields[field] = value;
|
1611 | this.invalidateDataRange();
|
1612 | };
|
1613 | /**
|
1614 | * Invalidates processed data.
|
1615 | *
|
1616 | * @ignore Exclude from docs
|
1617 | */
|
1618 | Component.prototype.invalidateProcessedData = function () {
|
1619 | this.resetProcessedRange();
|
1620 | this.invalidateDataRange();
|
1621 | };
|
1622 | /**
|
1623 | * [resetProcessedRange description]
|
1624 | *
|
1625 | * @ignore Exclude from docs
|
1626 | * @todo Description
|
1627 | */
|
1628 | Component.prototype.resetProcessedRange = function () {
|
1629 | this._prevEndIndex = null;
|
1630 | this._prevStartIndex = null;
|
1631 | };
|
1632 | Object.defineProperty(Component.prototype, "dataUsers", {
|
1633 | /**
|
1634 | * Returns all other [[Component]] objects that are using this element's
|
1635 | * data.
|
1636 | *
|
1637 | * @ignore Exclude from docs
|
1638 | * @todo Description (review)
|
1639 | * @return [description]
|
1640 | */
|
1641 | get: function () {
|
1642 | var _this = this;
|
1643 | if (!this._dataUsers) {
|
1644 | this._dataUsers = new List();
|
1645 | //this._disposers.push(new ListDisposer(this._dataUsers));
|
1646 | // TODO better way of handling this? e.g. move into another module ?
|
1647 | this._disposers.push(new Disposer(function () {
|
1648 | // TODO clear the list ?
|
1649 | $iter.each(_this._dataUsers.iterator(), function (x) {
|
1650 | x.dispose();
|
1651 | });
|
1652 | }));
|
1653 | }
|
1654 | return this._dataUsers;
|
1655 | },
|
1656 | enumerable: true,
|
1657 | configurable: true
|
1658 | });
|
1659 | /**
|
1660 | * Returns a clone of this element.
|
1661 | *
|
1662 | * @return Clone
|
1663 | */
|
1664 | Component.prototype.clone = function () {
|
1665 | var component = _super.prototype.clone.call(this);
|
1666 | component.dataFields = $utils.copyProperties(this.dataFields, {});
|
1667 | return component;
|
1668 | };
|
1669 | /**
|
1670 | * Copies all parameters from another [[Component]].
|
1671 | *
|
1672 | * @param source Source Component
|
1673 | */
|
1674 | Component.prototype.copyFrom = function (source) {
|
1675 | _super.prototype.copyFrom.call(this, source);
|
1676 | this.data = source.data;
|
1677 | this.sequencedInterpolation = source.sequencedInterpolation;
|
1678 | this.sequencedInterpolationDelay = source.sequencedInterpolationDelay;
|
1679 | this.interpolationDuration = source.interpolationDuration;
|
1680 | this.interpolationEasing = source.interpolationEasing;
|
1681 | };
|
1682 | /**
|
1683 | * Invalidates the whole element, including all its children, causing
|
1684 | * complete re-parsing of data and redraw.
|
1685 | *
|
1686 | * Use sparingly!
|
1687 | */
|
1688 | Component.prototype.reinit = function () {
|
1689 | this._inited = false;
|
1690 | this.deepInvalidate();
|
1691 | };
|
1692 | /**
|
1693 | * Add an adapter for data.
|
1694 | *
|
1695 | * @return Exporting
|
1696 | */
|
1697 | Component.prototype.getExporting = function () {
|
1698 | var _export = _super.prototype.getExporting.call(this);
|
1699 | if (!_export.adapter.has("data", this._exportData, -1, this)) {
|
1700 | _export.adapter.add("data", this._exportData, -1, this);
|
1701 | this.events.on("datavalidated", function (ev) {
|
1702 | _export.handleDataUpdated();
|
1703 | });
|
1704 | }
|
1705 | return _export;
|
1706 | };
|
1707 | Component.prototype._exportData = function (arg) {
|
1708 | arg.data = this.data;
|
1709 | return arg;
|
1710 | };
|
1711 | Component.prototype.setDisabled = function (value) {
|
1712 | var changed = _super.prototype.setDisabled.call(this, value);
|
1713 | if (changed) {
|
1714 | this.invalidateData();
|
1715 | }
|
1716 | return changed;
|
1717 | };
|
1718 | /**
|
1719 | * @ignore
|
1720 | */
|
1721 | Component.prototype.setShowOnInit = function (value) {
|
1722 | if (value != this.getPropertyValue("showOnInit")) {
|
1723 | if (value && !this.inited && !this.hidden) {
|
1724 | this._showOnInitDisposer2 = this.events.once("dataitemsvalidated", this.hideInitially, this, false);
|
1725 | this._disposers.push(this._showOnInitDisposer2);
|
1726 | }
|
1727 | else {
|
1728 | if (this._showOnInitDisposer2) {
|
1729 | this.removeDispose(this._showOnInitDisposer2);
|
1730 | }
|
1731 | }
|
1732 | }
|
1733 | // important order here
|
1734 | _super.prototype.setShowOnInit.call(this, value);
|
1735 | };
|
1736 | Component.prototype.setBaseId = function (value) {
|
1737 | if (value != this._baseId) {
|
1738 | if (this.dataInvalid) {
|
1739 | this.dataInvalid = false;
|
1740 | registry.removeFromInvalidComponents(this);
|
1741 | this._baseId = value;
|
1742 | this.invalidateData();
|
1743 | }
|
1744 | }
|
1745 | _super.prototype.setBaseId.call(this, value);
|
1746 | };
|
1747 | Object.defineProperty(Component.prototype, "minZoomCount", {
|
1748 | /**
|
1749 | * @return Min zoom count
|
1750 | */
|
1751 | get: function () {
|
1752 | return this.getPropertyValue("minZoomCount");
|
1753 | },
|
1754 | /**
|
1755 | * Use this for [[CategoryAxis]] or [[DateAxis]].
|
1756 | *
|
1757 | * Allows restricting zoom in beyond certain number of categories or base
|
1758 | * intervals.
|
1759 | *
|
1760 | * @default 1
|
1761 | * @param value Min zoom count
|
1762 | */
|
1763 | set: function (value) {
|
1764 | this.setPropertyValue("minZoomCount", value);
|
1765 | },
|
1766 | enumerable: true,
|
1767 | configurable: true
|
1768 | });
|
1769 | Object.defineProperty(Component.prototype, "maxZoomCount", {
|
1770 | /**
|
1771 | * @return Max zoom count
|
1772 | */
|
1773 | get: function () {
|
1774 | return this.getPropertyValue("maxZoomCount");
|
1775 | },
|
1776 | /**
|
1777 | * Use this for [[CategoryAxis]] or [[DateAxis]].
|
1778 | *
|
1779 | * Limits how many categories or base intervals can be shown at the same
|
1780 | * time.
|
1781 | *
|
1782 | * If there are more items in the chart, the chart will auto-zoom.
|
1783 | *
|
1784 | * @default 0 (no limit)
|
1785 | * @since 4.6.2
|
1786 | * @param value Max zoom count
|
1787 | */
|
1788 | set: function (value) {
|
1789 | this.setPropertyValue("maxZoomCount", value);
|
1790 | },
|
1791 | enumerable: true,
|
1792 | configurable: true
|
1793 | });
|
1794 | /**
|
1795 | * Called during the System.update method
|
1796 | *
|
1797 | * @ignore Exclude from docs
|
1798 | */
|
1799 | Component.prototype._systemCheckIfValidate = function () {
|
1800 | if (this.dataInvalid || (this.dataProvider && this.dataProvider.dataInvalid)) {
|
1801 | return false;
|
1802 | }
|
1803 | else {
|
1804 | return true;
|
1805 | }
|
1806 | };
|
1807 | /**
|
1808 | * Adds easing functions to "function" fields.
|
1809 | *
|
1810 | * @param field Field name
|
1811 | * @return Assign as function?
|
1812 | */
|
1813 | Component.prototype.asFunction = function (field) {
|
1814 | return field == "interpolationEasing" || field == "rangeChangeEasing" || _super.prototype.asIs.call(this, field);
|
1815 | };
|
1816 | return Component;
|
1817 | }(Container));
|
1818 | export { Component };
|
1819 | /**
|
1820 | * Register class in system, so that it can be instantiated using its name from
|
1821 | * anywhere.
|
1822 | *
|
1823 | * @ignore
|
1824 | */
|
1825 | registry.registeredClasses["Component"] = Component;
|
1826 | //# sourceMappingURL=Component.js.map |
\ | No newline at end of file |