@amcharts/amcharts4
Version:
amCharts 4
997 lines (996 loc) • 36.4 kB
JavaScript
/**
* Defines functionality for "Data Item"
*
* A Data Item can be any object that can hold data. For example [[LineSeries]]
* holds a number of values, that comprise a line graph. Each of those values
* (data points) is a {DataItem}.
*
* Furthermore the [[LineSeries]] itself can be represented as a entry in the
* legend. Since legend needs access to Line Series' value, a DataItem is
* created for the series.
*/
import { __extends } from "tslib";
/**
* ============================================================================
* IMPORTS
* ============================================================================
* @hidden
*/
import { BaseObjectEvents } from "./Base";
import { Adapter } from "./utils/Adapter";
import { Animation, AnimationDisposer } from "./utils/Animation";
import * as $utils from "./utils/Utils";
import * as $array from "./utils/Array";
//import * as $object from "./utils/Object";
import * as $type from "./utils/Type";
/**
* ============================================================================
* DATA ITEM
* ============================================================================
* @hidden
*/
/**
* DataItem represents single element in data, for example a data point in a
* Serial Chart Series, e.g. a column.
*
* DataItem defines relationship between structured data, required for specific
* chart type or task, and raw source data.
*
* It also implements required calculations, updates related visual elements,
* etc.
*
* @todo Description
* @important
*/
var DataItem = /** @class */ (function (_super) {
__extends(DataItem, _super);
/**
* Constructor
* @todo Adding events to disposers produces errors in some cases, which means that chart is using disposed Data Items which is not right
*/
function DataItem() {
var _this = _super.call(this) || this;
/**
* This Data Item is currently disabled.
*
* @ignore Exclude from docs
*/
_this._disabled = false;
/**
* Indicates whether Data Item has any properties set.
*
* If it does not have any, the code can use this property to check whether
* they need to apply costly operation of re-applying properties, whenever
* Data Item-related element is redrawn, e.g. series.
*/
_this.hasProperties = false;
/**
* An object containing calculated values.
*/
_this.values = {};
/**
* An object container current working values.
*/
//public readonly workingValues: { [index: string]: { [index: string]: number } } = {};
/**
* An object containing categories.
*/
_this.categories = {};
/**
* An object containing dates.
*/
_this.dates = {};
/**
* An object containing locations for the Data Item.
*
* A location is a position within date or category, or, in some other cases,
* where there is no single point but rather some period.
*
* @see {@link https://www.amcharts.com/docs/v4/concepts/series/#Data_item_locations} for info how data item locations work
*/
_this.locations = {};
/**
* Current working locations.
*/
_this.workingLocations = {};
/**
* An object containing Data Item specific appearance properties in key-value
* pairs.
*
* Sometimes a single Data Item needs to apply different properties than the
* rest of the data [[Series]] it is part of. E.g. a single column,
* represented by a Data Item needs to be filled with a different color than
* the reset of the [[ColumnSeries]] it belongs to.
*
* That's where Data Item's `properties` come into play.
*
* Please note that you should set Data Item-specific properties using
* `setProperty()` method, rather than access `properties` object directly.
*/
_this.properties = {};
/**
* A list of [[Sprite]] elements that are associated with this Data Item.
*
* E.g. an [[Axis]] Data Item has several separate elements associated with
* it, like [[AxisTick]], [[AxisLabel]], and [[Grid]].
*
* Data Item keeps track of all of them, so it can toggle all related visual
* elements when it itself is toggled.
*/
_this.sprites = [];
/**
* Identifies if this object is a "template" and should not be treated as
* real object that is drawn or actually used in the chart.
*/
_this.isTemplate = false;
/**
* The current index within the dataItems
*
* @ignore Exclude from docs
*/
_this._index = null;
/**
* Is Data Item currently visible?
*
* @ignore Exclude from docs
*/
_this._visible = true;
/**
* Is Data Item currently hidden?
*
* @ignore Exclude from docs
*/
_this._hidden = false;
/**
* Should this Data Item be used when calculating data ranges and scales?
*
* @ignore Exclude from docs
*/
_this._ignoreMinMax = false;
/**
* Some of the Data Item's data fields may contain an array of children. This
* property contains an object indicating which fields hold an array, so that
* they can be processed properly.
*
* @ignore Exclude from docs
*/
_this.hasChildren = {};
/**
* Indicates whether Data Item is currently animiting from visible to hidden
* state.
*/
_this.isHiding = false;
/**
*
* @ignore Exclude from docs
*/
_this._valueAnimations = {};
/**
*
* @ignore Exclude from docs
*/
_this._locationAnimations = {};
_this.className = "DataItem";
_this.applyTheme();
return _this;
}
Object.defineProperty(DataItem.prototype, "adapter", {
/**
* Holds Adapter.
*/
get: function () {
if (!this._adapterO) {
this._adapterO = new Adapter(this);
}
return this._adapterO;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "index", {
/**
* Data Item's position index in Component's data.
*
* @return Index
*/
get: function () {
if (this.component) {
if (this._index != null) {
return this._index;
}
else {
return -1;
}
}
else {
return -1;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "animations", {
/**
* A list of [[Animations]] objects currently mutating Data Item's values.
*
* @return [description]
*/
get: function () {
if (!this._animations) {
this._animations = [];
this._disposers.push(new AnimationDisposer(this._animations));
}
return this._animations;
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "visible", {
/**
* Returns `true` if this Data Item is currently visible.
*
* @return Visible?
*/
get: function () {
if (this._hidden) {
return false;
}
return this._visible;
},
/**
* Sets visibility of the Data Item.
*
* @param value Visible?
*/
set: function (value) {
if (value) {
this.hidden = false;
}
if (this._visible != value) {
this.setVisibility(value);
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "hidden", {
/**
* Returns `true` if this Data Item is currently hidden.
*
* @return Hidden?
*/
get: function () {
return this._hidden;
},
/**
* Sets hidden flag for data item. Mostly used to initially hide data item.
*
* @param value Hidden?
*/
set: function (value) {
if (this._hidden != value) {
this._hidden = value;
if (value) {
this.setVisibility(false);
}
else {
this.setVisibility(true, true);
}
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "__disabled", {
/**
* Is this Data Item currently disabled?
*
* @ignore Exclude from docs
* @param {boolean}
*/
get: function () {
return this._disabled;
},
/**
* Disables all Sprites associated with this Data Item.
*
* @ignore Exclude from docs
* @param {boolean}
*/
set: function (value) {
// if (this._disabled != value) { // not good
this._disabled = value;
$array.each(this.sprites, function (sprite) {
sprite.__disabled = value;
});
// }
},
enumerable: true,
configurable: true
});
/**
* Sets visibility of the Data Item.
*
* @param value Data Item
*/
DataItem.prototype.setVisibility = function (value, noChangeValues) {
$array.each(this.sprites, function (sprite) {
if (value) {
sprite.visible = sprite.defaultState.properties.visible;
}
else {
if (sprite.hiddenState) {
sprite.visible = sprite.hiddenState.properties.visible;
}
else {
sprite.visible = false;
}
}
});
this._visible = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("visibilitychanged")) {
var event_1 = {
type: "visibilitychanged",
target: this,
visible: value
};
this.events.dispatchImmediately("visibilitychanged", event_1);
}
}
};
/**
* Shows the Data Item and related visual elements.
*
* @param duration Animation duration (ms)
* @param delay Delay animation (ms)
* @param fields A list of fields to set values of
*/
DataItem.prototype.show = function (duration, delay, fields) {
var _this = this;
if (!this.hidden) {
this.setVisibility(true, true);
this.isHiding = false;
if (this._hideDisposer) {
this.removeDispose(this._hideDisposer);
}
var animation_1;
if (fields) {
$array.each(fields, function (field) {
animation_1 = _this.setWorkingValue(field, _this.values[field].value, duration, delay);
});
}
$array.each(this.sprites, function (sprite) {
var animation = sprite.show(duration);
if (animation != null && !animation.isFinished()) {
_this._disposers.push(animation);
if (delay != null && delay > 0) {
animation.delay(delay);
}
}
});
return animation_1;
}
};
/**
* Destroys this object and all related data.
*/
DataItem.prototype.dispose = function () {
_super.prototype.dispose.call(this);
$array.each(this.sprites, function (sprite) {
sprite.dispose();
});
this.sprites = [];
};
/**
* Hides the Data Item and related visual elements.
*
* @param duration Animation duration (ms)
* @param delay Delay animation (ms)
* @param toValue A value to set to `fields` when hiding
* @param fields A list of data fields to set value to `toValue`
*/
DataItem.prototype.hide = function (duration, delay, toValue, fields) {
var _this = this;
this.isHiding = true;
$array.each(this.sprites, function (sprite) {
var animation = sprite.hide(duration);
if (animation != null && !animation.isFinished()) {
_this._disposers.push(animation);
if (delay != null && delay > 0) {
animation.delay(delay);
}
}
});
if ($type.isNumber(toValue) && fields) {
var animation_2;
$array.each(fields, function (field) {
var anim = _this.setWorkingValue(field, toValue, duration, delay);
if (anim) {
animation_2 = anim;
}
});
if (animation_2 && !animation_2.isFinished()) {
this._hideDisposer = animation_2.events.on("animationended", function () {
_this.setVisibility(false, true);
_this.isHiding = false;
});
this._disposers.push(this._hideDisposer);
return animation_2;
}
else {
this.isHiding = false;
this.setVisibility(false, true);
}
}
else {
this.isHiding = false;
this.setVisibility(false);
}
};
/**
* Returns a duration (ms) the Data Item should take to animate from one
* value to another.
*
* If the duration is not specified via parameter, this method will try to
* request a default duration from the related `Component`.
*
* @param duration Default duration (ms)
* @return Duration (ms)
*/
DataItem.prototype.getDuration = function (duration) {
if (!$type.isNumber(duration)) {
var component = this.component;
if (component) {
duration = component.interpolationDuration;
}
}
if (duration != null) {
if (!this._adapterO) {
return duration;
}
else {
return this._adapterO.apply("duration", duration);
}
}
};
/**
* Returns a numeric value for specific data field.
*
* If `calculated` is not set, it will return a raw value, as it is in
* source data.
*
* If `calculated` is set, it will return a pre-calculated specific value.
*
* @param name Data field name
* @param calculated A calculated value name
* @return Value
*/
DataItem.prototype.getValue = function (name, calculated) {
if (name && this.component) {
if (!calculated) {
calculated = this.component.dataFields[name + "Show"];
if (!calculated) {
calculated = "value";
}
}
var value = this.values[name][calculated];
if (this._adapterO && this._adapterO.isEnabled("value")) {
return this._adapterO.apply("value", {
value: value,
field: name
}).value;
}
else {
return value;
}
}
};
/**
* Returns a current working value for a specific data field.
*
* The actual value may differ from the one returned by `getValue()`. The
* latter returns static values from the data source.
*
* `getWorkingValue()` returns current value, which is usually different if
* Data Item is animating from one state to another.
*
* @param name Data field name
* @return Value
*/
DataItem.prototype.getWorkingValue = function (name) {
if (name && this.component) {
var realName = this.component.dataFields[name + "Show"];
if (!realName) {
realName = "workingValue";
}
if (this._adapterO) {
return this._adapterO.apply("workingValue", {
workingValue: this.values[name][realName],
field: name
}).workingValue;
}
else {
return this.values[name][realName];
}
}
};
/**
* @ignore
* @return Value
*/
DataItem.prototype.getActualWorkingValue = function (name) {
return this.values[name].workingValue;
};
/**
* Sets a numeric value for specific data field.
*
* @param name Data field name
* @param value Value
* @param calculated Calculated data field name
* @param duration Duration (ms) to animate to new value to
* @param delay Delay animation (ms)
*/
DataItem.prototype.setValue = function (name, value, duration, delay) {
var currentValue = this.values[name].value;
var newDuration = this.getDuration(duration);
value = $type.toNumber(value);
if (currentValue !== value) {
this.values[name].value = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("valuechanged")) {
var event_2 = {
type: "valuechanged",
target: this,
property: name
};
this.events.dispatchImmediately("valuechanged", event_2);
}
}
if (this.component) {
this.component.handleDataItemValueChange(this, name);
}
}
this.setWorkingValue(name, value, newDuration, delay);
};
DataItem.prototype.setCalculatedValue = function (name, value, calculated) {
var currentValue = this.values[name][calculated];
if (currentValue !== value && $type.isNumber(value)) {
this.values[name][calculated] = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("calculatedvaluechanged")) {
var event_3 = {
type: "calculatedvaluechanged",
target: this,
property: name
};
this.events.dispatchImmediately("calculatedvaluechanged", event_3);
}
}
if (this.component) {
this.component.handleDataItemCalculatedValueChange(this, name);
}
}
};
/**
* Set current working numeric value for a specific data field.
*
* @param name Data field name
* @param value Value
* @param calculated Calculated data field name
* @param duration Duration (ms) to animate to new value to
* @param delay Delay animation (ms)
* @return An [[Animation]] object used for transition to new values
*/
DataItem.prototype.setWorkingValue = function (name, value, duration, delay) {
if ($type.isNumber(this.values[name].value)) {
var newDuration = this.getDuration(duration);
var workingValue = this.values[name].workingValue;
if (newDuration != null && newDuration > 0 && $type.isNumber(workingValue) && this.component) { // sometimes NaN is passed, so only change this to != null if all cases of NaN are handled, otherwise animation won't stop
if (workingValue != value) {
var animation = this.animate({ childObject: this.values[name], property: "workingValue", from: workingValue, to: value, dummyData: name }, newDuration, this.component.interpolationEasing);
if (delay != null) {
animation.delay(delay);
}
animation.events.on("animationstarted", this.handleInterpolationProgress, this);
animation.events.on("animationprogress", this.handleInterpolationProgress, this);
animation.events.on("animationended", this.handleInterpolationProgress, this);
this._valueAnimations[name] = animation;
return animation;
}
else {
var valueAnimation = this._valueAnimations[name];
if (valueAnimation) {
valueAnimation.stop();
}
this.values[name].workingValue = value;
}
}
else {
var valueAnimation = this._valueAnimations[name];
if (valueAnimation) {
valueAnimation.stop();
}
this.values[name].workingValue = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("workingvaluechanged")) {
var event_4 = {
type: "workingvaluechanged",
target: this,
property: name
};
this.events.dispatchImmediately("workingvaluechanged", event_4);
}
}
if (this.component) {
this.component.handleDataItemWorkingValueChange(this, name);
}
}
}
};
/**
* Sets a relative location for a data field.
*
* A location is always relative on a 0 to 1 scale, with 0 being beginning,
* 0.5 middle and 1 end.
*
* @todo Rewiew description
* @param name Data field name
* @param value Location (0-1)
* @param duration Duration (ms) to animate to new value to
* @param delay Delay animation (ms)
*/
DataItem.prototype.setLocation = function (name, value, duration, delay) {
var currentLocation = this.locations[name];
if (currentLocation !== value) {
this.locations[name] = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("locationchanged")) {
var event_5 = {
type: "locationchanged",
target: this,
property: name
};
this.events.dispatchImmediately("locationchanged", event_5);
}
}
if (this.component) {
this.component.handleDataItemValueChange(this, name); // correct
}
this.setWorkingLocation(name, value, duration, delay);
}
};
/**
* Sets a current working location for a data field.
*
* @todo Rewiew description
* @param name Data field name
* @param value Location (0-1)
* @param duration Duration (ms) to animate to new value to
* @param delay Delay animation (ms)
*/
DataItem.prototype.setWorkingLocation = function (name, value, duration, delay) {
var newDuration = this.getDuration(duration);
var workingLocation = this.workingLocations[name];
if (newDuration != null && newDuration > 0 && $type.isNumber(workingLocation) && this.component) { // sometimes NaN is passed, so only change this to != null if all cases of NaN are handled, otherwise animation won't stop
if (workingLocation != value) {
var animation = this.animate({ childObject: this.workingLocations, property: name, from: workingLocation, to: value, dummyData: name }, newDuration, this.component.interpolationEasing);
if (delay != null) {
animation.delay(delay);
}
animation.events.on("animationstarted", this.handleInterpolationProgress, this);
animation.events.on("animationprogress", this.handleInterpolationProgress, this);
animation.events.on("animationended", this.handleInterpolationProgress, this);
this._locationAnimations[name] = animation;
return animation;
}
else {
var locationAnimation = this._locationAnimations[name];
if (locationAnimation) {
locationAnimation.stop();
}
this.workingLocations[name] = value;
}
}
else {
var locationAnimation = this._locationAnimations[name];
if (locationAnimation) {
locationAnimation.stop();
}
this.workingLocations[name] = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("workinglocationchanged")) {
var event_6 = {
type: "workinglocationchanged",
target: this,
property: name
};
this.events.dispatchImmediately("workinglocationchanged", event_6);
}
}
if (this.component) {
this.component.handleDataItemWorkingLocationChange(this, name);
}
}
};
/**
* Sets Date value to a data field.
*
* @param name Data field name
* @param date Date object
* @param duration Duration (ms) to animate to new value to
*/
DataItem.prototype.setDate = function (name, date, duration) {
if (!$type.isDate(date) && this.component) {
date = this.component.dateFormatter.parse(date);
}
var currentDate = this.dates[name];
if (currentDate !== date) {
this.dates[name] = date;
this.setValue(name, date.getTime(), duration);
}
};
/**
* Returns a Date value of the data field.
*
* @param name Data field name
* @return Date object
*/
DataItem.prototype.getDate = function (name) {
if (this._adapterO) {
return this._adapterO.apply("date", {
date: this.dates[name],
field: name
}).date;
}
else {
return this.dates[name];
}
};
/**
* Sets a Data Item-specific visual properties to apply to related elements.
*
* @param name Property name
* @param value Property value
*/
DataItem.prototype.setProperty = function (name, value) {
if (this.properties[name] !== value) {
this.hasProperties = true;
this.properties[name] = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("propertychanged")) {
var event_7 = {
type: "propertychanged",
target: this,
property: name,
value: value
};
this.events.dispatchImmediately("propertychanged", event_7);
}
}
if (this.component) {
this.component.handleDataItemPropertyChange(this, name);
}
}
};
/**
* Sets a related category for this Data Item.
*
* @todo Review description
* @param name Data field name
* @param value Category
*/
DataItem.prototype.setCategory = function (name, value) {
if (!$type.isString(value)) {
value = $type.castString(value);
}
if (this.categories[name] !== value) {
this.categories[name] = value;
}
};
/**
* Clones the Data Item, including all related data.
*
* @return New Data Item clone
*/
//public clone(cloneId?: string): this {
// let dataItem: this = super.clone(cloneId);
// dataItem.copyFrom(this);
// return dataItem;
//}
/**
* Copies all properties and related data from different data item.
*
* @param object Source data item
*/
DataItem.prototype.copyFrom = function (source) {
_super.prototype.copyFrom.call(this, source);
if (source.dataContext) {
this.dataContext = $utils.copy(source.dataContext, {});
}
$utils.copyProperties(source.locations, this.locations);
/*
$utils.copyProperties(source.properties, this.properties);
$utils.copyProperties(source.categories, this.categories);
$utils.copyProperties(source.values, this.values);
$utils.copyProperties(source.dates, this.dates);
$object.each(source.values, (name, value) => {
this.values[name] = $object.copy(value);
});*/
if (source._adapterO) {
this.adapter.copyFrom(source._adapterO);
}
//this.events.copyFrom(source.events); // because copied in Base
this.component = source.component;
};
Object.defineProperty(DataItem.prototype, "opacity", {
/**
* Sets opacity for all Data Item's related elements (Sprites).
*
* @param value Opacity (0-1)
*/
set: function (value) {
$array.each(this.sprites, function (sprite) {
sprite.opacity = value;
});
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "ignoreMinMax", {
/**
* Exclude from min/max calculations?
* @return Exclude from min/max calculations?
*/
get: function () {
return this._ignoreMinMax;
},
/**
* Sets whether this data point should not be included in the scale and
* minimum/maximum calculations.
*
* E.g. some we may want to exclude a particular data point from influencing
* [[ValueAxis]] scale.
*
* @param value Exclude from min/max calculations?
*/
set: function (value) {
this._ignoreMinMax = value;
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("propertychanged")) {
var event_8 = {
type: "propertychanged",
target: this,
property: "ignoreMinMax",
value: value
};
this.events.dispatchImmediately("propertychanged", event_8);
}
}
if (this.component) {
this.component.handleDataItemPropertyChange(this, "ignoreMinMax");
}
},
enumerable: true,
configurable: true
});
/**
* Creates and starts an [[Animation]] to interpolate (morph) Data Item's
* properties and/or values.
*
* @see {@link Animation}
* @param animationOptions Animation options
* @param duration Animation duration (ms)
* @param easing Easing function
* @return Animation
*/
DataItem.prototype.animate = function (animationOptions, duration, easing) {
return new Animation(this, animationOptions, duration, easing).start();
};
/**
* Handles intermediate steps when Data Item is interpolating (morphing) from
* one value to another.
*
* @ignore Exclude from docs
* @param event Event object
*/
DataItem.prototype.handleInterpolationProgress = function (event) {
var animation = event.target;
// it's always only one options, no need cycle
var animationOptions = animation.animationOptions[0];
if (animationOptions) {
if (this._eventDispatcher && !this.__disabled) {
if (this.events.isEnabled("workingvaluechanged")) {
var event_9 = {
type: "workingvaluechanged",
target: this,
property: animationOptions.dummyData
};
this.events.dispatchImmediately("workingvaluechanged", event_9);
}
}
if (this.component) {
this.component.handleDataItemWorkingValueChange(this, animationOptions.dummyData);
}
}
};
/**
* Checks whether Data Item has values set for all of the data fields,
* supplied via argument.
*
* @ignore Exclude from docs
* @param fields Field list to check
* @return Has values for all fields?
*/
DataItem.prototype.hasValue = function (fields) {
// todo: what about categories?
for (var i = 0, len = fields.length; i < len; i++) {
var values = this.values[fields[i]];
if (!values || !$type.hasValue(values.value)) {
return false;
}
}
return true;
};
Object.defineProperty(DataItem.prototype, "depth", {
/**
* Depth of the Data Item.
*
* In nested data structures, like TreeMap, this indicates the level this
* data point is at, in relation to the parent Data Item.
*
* @return Depth
*/
get: function () {
if (!this.parent) {
return 0;
}
else {
return this.parent.depth + 1;
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(DataItem.prototype, "dataContext", {
/**
* Sets to a reference to an original object from Component's data.
*
* @return [description]
*/
get: function () {
return this._dataContext;
},
/**
* A reference to an original object in Component's data, that this Data Item
* is derived from.
*
* @param value Original data object
*/
set: function (value) {
this._dataContext = value;
},
enumerable: true,
configurable: true
});
/**
* adds a sprite to dataItem.sprites array
* @ignore
*/
DataItem.prototype.addSprite = function (sprite) {
if (sprite.dataItem && sprite.dataItem != this) {
$array.remove(sprite.dataItem.sprites, sprite);
}
if (!this.visible) {
sprite.hide(0);
}
if (this.isHiding) {
sprite.hide();
}
this.sprites.push(sprite);
sprite.dataItem = this;
};
return DataItem;
}(BaseObjectEvents));
export { DataItem };
//# sourceMappingURL=DataItem.js.map