ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
389 lines • 17 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var group_1 = require("../scene/group");
var selection_1 = require("../scene/selection");
var markerLabel_1 = require("./markerLabel");
var observable_1 = require("../util/observable");
var util_1 = require("./marker/util");
var id_1 = require("../util/id");
var LegendOrientation;
(function (LegendOrientation) {
LegendOrientation[LegendOrientation["Vertical"] = 0] = "Vertical";
LegendOrientation[LegendOrientation["Horizontal"] = 1] = "Horizontal";
})(LegendOrientation = exports.LegendOrientation || (exports.LegendOrientation = {}));
var LegendPosition;
(function (LegendPosition) {
LegendPosition["Top"] = "top";
LegendPosition["Right"] = "right";
LegendPosition["Bottom"] = "bottom";
LegendPosition["Left"] = "left";
})(LegendPosition = exports.LegendPosition || (exports.LegendPosition = {}));
var LegendLabel = /** @class */ (function (_super) {
__extends(LegendLabel, _super);
function LegendLabel() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.color = 'black';
_this.fontSize = 12;
_this.fontFamily = 'Verdana, sans-serif';
return _this;
}
__decorate([
observable_1.reactive('change')
], LegendLabel.prototype, "color", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendLabel.prototype, "fontStyle", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendLabel.prototype, "fontWeight", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendLabel.prototype, "fontSize", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendLabel.prototype, "fontFamily", void 0);
__decorate([
observable_1.reactive()
], LegendLabel.prototype, "formatter", void 0);
return LegendLabel;
}(observable_1.Observable));
exports.LegendLabel = LegendLabel;
var LegendMarker = /** @class */ (function (_super) {
__extends(LegendMarker, _super);
function LegendMarker() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.size = 15;
/**
* Padding between the marker and the label within each legend item.
*/
_this.padding = 8;
_this.strokeWidth = 1;
return _this;
}
__decorate([
observable_1.reactive('layoutChange')
], LegendMarker.prototype, "size", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendMarker.prototype, "shape", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendMarker.prototype, "padding", void 0);
__decorate([
observable_1.reactive('change')
], LegendMarker.prototype, "strokeWidth", void 0);
return LegendMarker;
}(observable_1.Observable));
exports.LegendMarker = LegendMarker;
var LegendItem = /** @class */ (function (_super) {
__extends(LegendItem, _super);
function LegendItem() {
var _this = _super.call(this) || this;
_this.marker = new LegendMarker();
_this.label = new LegendLabel();
/**
* The legend uses grid layout for its items, occupying as few columns as possible when positioned to left or right,
* and as few rows as possible when positioned to top or bottom. This config specifies the amount of horizontal
* padding between legend items.
*/
_this.paddingX = 16;
/**
* The legend uses grid layout for its items, occupying as few columns as possible when positioned to left or right,
* and as few rows as possible when positioned to top or bottom. This config specifies the amount of vertical
* padding between legend items.
*/
_this.paddingY = 8;
var changeListener = function () { return _this.fireEvent({ type: 'change' }); };
_this.marker.addEventListener('change', changeListener);
_this.label.addEventListener('change', changeListener);
var layoutChangeListener = function () { return _this.fireEvent({ type: 'layoutChange' }); };
_this.marker.addEventListener('layoutChange', layoutChangeListener);
_this.label.addEventListener('layoutChange', layoutChangeListener);
return _this;
}
__decorate([
observable_1.reactive('layoutChange')
], LegendItem.prototype, "paddingX", void 0);
__decorate([
observable_1.reactive('layoutChange')
], LegendItem.prototype, "paddingY", void 0);
return LegendItem;
}(observable_1.Observable));
exports.LegendItem = LegendItem;
var Legend = /** @class */ (function (_super) {
__extends(Legend, _super);
function Legend() {
var _this = _super.call(this) || this;
_this.id = id_1.createId(_this);
_this.group = new group_1.Group();
_this.itemSelection = selection_1.Selection.select(_this.group).selectAll();
_this.oldSize = [0, 0];
_this.item = new LegendItem();
_this.data = [];
_this.enabled = true;
_this.orientation = LegendOrientation.Vertical;
_this.position = LegendPosition.Right;
/**
* Spacing between the legend and the edge of the chart's element.
*/
_this.spacing = 20;
_this._size = [0, 0];
_this.addPropertyListener('data', _this.onDataChange);
_this.addPropertyListener('enabled', _this.onEnabledChange);
_this.addPropertyListener('position', _this.onPositionChange);
_this.item.marker.addPropertyListener('shape', _this.onMarkerShapeChange, _this);
_this.addEventListener('change', _this.update);
_this.item.addEventListener('change', function () { return _this.fireEvent({ type: 'change' }); });
_this.item.addEventListener('layoutChange', function () { return _this.fireEvent({ type: 'layoutChange' }); });
return _this;
}
Object.defineProperty(Legend.prototype, "size", {
get: function () {
return this._size;
},
enumerable: true,
configurable: true
});
Legend.prototype.onDataChange = function (event) {
this.group.visible = event.value.length > 0 && this.enabled;
};
Legend.prototype.onEnabledChange = function (event) {
this.group.visible = event.value && this.data.length > 0;
};
Legend.prototype.onPositionChange = function (event) {
switch (event.value) {
case 'right':
case 'left':
this.orientation = LegendOrientation.Vertical;
break;
case 'bottom':
case 'top':
this.orientation = LegendOrientation.Horizontal;
break;
}
};
Legend.prototype.onMarkerShapeChange = function () {
this.itemSelection = this.itemSelection.setData([]);
this.itemSelection.exit.remove();
if (this.group.scene) {
this.group.scene.dirty = false;
}
};
/**
* The method is given the desired size of the legend, which only serves as a hint.
* The vertically oriented legend will take as much horizontal space as needed, but will
* respect the height constraints, and the horizontal legend will take as much vertical
* space as needed in an attempt not to exceed the given width.
* After the layout is done, the {@link size} will contain the actual size of the legend.
* If the actual size is not the same as the previous actual size, the legend will fire
* the 'layoutChange' event to communicate that another layout is needed, and the above
* process should be repeated.
* @param width
* @param height
*/
Legend.prototype.performLayout = function (width, height) {
var item = this.item;
var marker = item.marker, paddingX = item.paddingX, paddingY = item.paddingY;
var updateSelection = this.itemSelection.setData(this.data, function (_, datum) {
var MarkerShape = util_1.getMarker(marker.shape || datum.marker.shape);
return datum.id + '-' + datum.itemId + '-' + MarkerShape.name;
});
updateSelection.exit.remove();
var enterSelection = updateSelection.enter.append(markerLabel_1.MarkerLabel).each(function (node, datum) {
var MarkerShape = util_1.getMarker(marker.shape || datum.marker.shape);
node.marker = new MarkerShape();
});
var itemSelection = this.itemSelection = updateSelection.merge(enterSelection);
var itemCount = itemSelection.size;
// Update properties that affect the size of the legend items and measure them.
var bboxes = [];
var itemMarker = this.item.marker;
var itemLabel = this.item.label;
var maxCharCount = 25;
var ellipsis = "...";
itemSelection.each(function (markerLabel, datum) {
// TODO: measure only when one of these properties or data change (in a separate routine)
markerLabel.markerSize = itemMarker.size;
markerLabel.spacing = itemMarker.padding;
markerLabel.fontStyle = itemLabel.fontStyle;
markerLabel.fontWeight = itemLabel.fontWeight;
markerLabel.fontSize = itemLabel.fontSize;
markerLabel.fontFamily = itemLabel.fontFamily;
var text = datum.label.text;
if (text.length > maxCharCount) {
text = "" + text.substring(0, maxCharCount - ellipsis.length) + ellipsis;
}
markerLabel.text = text;
bboxes.push(markerLabel.computeBBox());
});
var itemHeight = bboxes.length && bboxes[0].height;
var rowCount = 0;
var columnWidth = 0;
var paddedItemsWidth = 0;
var paddedItemsHeight = 0;
width = Math.max(1, width);
height = Math.max(1, height);
switch (this.orientation) {
case LegendOrientation.Horizontal:
if (!(isFinite(width) && width > 0)) {
return false;
}
rowCount = 0;
var columnCount = 0;
// Split legend items into columns until the width is suitable.
do {
var itemsWidth = 0;
columnCount = 0;
columnWidth = 0;
rowCount++;
var i = 0;
while (i < itemCount) {
var bbox = bboxes[i];
if (bbox.width > columnWidth) {
columnWidth = bbox.width;
}
i++;
if (i % rowCount === 0) {
itemsWidth += columnWidth;
columnWidth = 0;
columnCount++;
}
}
if (i % rowCount !== 0) {
itemsWidth += columnWidth;
columnCount++;
}
paddedItemsWidth = itemsWidth + (columnCount - 1) * paddingX;
} while (paddedItemsWidth > width && columnCount > 1);
paddedItemsHeight = itemHeight * rowCount + (rowCount - 1) * paddingY;
break;
case LegendOrientation.Vertical:
if (!(isFinite(height) && height > 0)) {
return false;
}
rowCount = itemCount * 2;
// Split legend items into columns until the height is suitable.
do {
rowCount = (rowCount >> 1) + (rowCount % 2);
columnWidth = 0;
var itemsWidth = 0;
var itemsHeight = 0;
var columnCount_1 = 0;
var i = 0;
while (i < itemCount) {
var bbox = bboxes[i];
if (!columnCount_1) {
itemsHeight += bbox.height;
}
if (bbox.width > columnWidth) {
columnWidth = bbox.width;
}
i++;
if (i % rowCount === 0) {
itemsWidth += columnWidth;
columnWidth = 0;
columnCount_1++;
}
}
if (i % rowCount !== 0) {
itemsWidth += columnWidth;
columnCount_1++;
}
paddedItemsWidth = itemsWidth + (columnCount_1 - 1) * paddingX;
paddedItemsHeight = itemsHeight + (rowCount - 1) * paddingY;
} while (paddedItemsHeight > height && rowCount > 1);
break;
}
// Top-left corner of the first legend item.
var startX = (width - paddedItemsWidth) / 2;
var startY = (height - paddedItemsHeight) / 2;
var x = 0;
var y = 0;
columnWidth = 0;
// Position legend items using the layout computed above.
itemSelection.each(function (markerLabel, datum, i) {
// Round off for pixel grid alignment to work properly.
markerLabel.translationX = Math.floor(startX + x);
markerLabel.translationY = Math.floor(startY + y);
var bbox = bboxes[i];
if (bbox.width > columnWidth) {
columnWidth = bbox.width;
}
if ((i + 1) % rowCount === 0) {
x += columnWidth + paddingX;
y = 0;
columnWidth = 0;
}
else {
y += bbox.height + paddingY;
}
});
// Update legend item properties that don't affect the layout.
this.update();
var size = this._size;
var oldSize = this.oldSize;
size[0] = paddedItemsWidth;
size[1] = paddedItemsHeight;
if (size[0] !== oldSize[0] || size[1] !== oldSize[1]) {
oldSize[0] = size[0];
oldSize[1] = size[1];
}
};
Legend.prototype.update = function () {
var _this = this;
this.itemSelection.each(function (markerLabel, datum) {
var marker = datum.marker;
markerLabel.markerFill = marker.fill;
markerLabel.markerStroke = marker.stroke;
markerLabel.markerStrokeWidth = _this.item.marker.strokeWidth;
markerLabel.markerFillOpacity = marker.fillOpacity;
markerLabel.markerStrokeOpacity = marker.strokeOpacity;
markerLabel.opacity = datum.enabled ? 1 : 0.5;
markerLabel.color = _this.item.label.color;
});
};
Legend.prototype.getDatumForPoint = function (x, y) {
var node = this.group.pickNode(x, y);
if (node && node.parent) {
return node.parent.datum;
}
};
Legend.className = 'Legend';
__decorate([
observable_1.reactive('layoutChange')
], Legend.prototype, "data", void 0);
__decorate([
observable_1.reactive('layoutChange')
], Legend.prototype, "enabled", void 0);
__decorate([
observable_1.reactive('layoutChange')
], Legend.prototype, "orientation", void 0);
__decorate([
observable_1.reactive('layoutChange')
], Legend.prototype, "position", void 0);
__decorate([
observable_1.reactive('layoutChange')
], Legend.prototype, "spacing", void 0);
return Legend;
}(observable_1.Observable));
exports.Legend = Legend;
//# sourceMappingURL=legend.js.map