UNPKG

ag-charts-community

Version:

Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue

431 lines 20.3 kB
"use strict"; 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 __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var selection_1 = require("../../scene/selection"); var line_1 = require("../../scene/shape/line"); var angle_1 = require("../../util/angle"); var text_1 = require("../../scene/shape/text"); var bbox_1 = require("../../scene/bbox"); var matrix_1 = require("../../scene/matrix"); // import { Rect } from "../../scene/shape/rect"; debug (bbox) var bandScale_1 = require("../../scale/bandScale"); var tree_1 = require("../../layout/tree"); var axis_1 = require("../../axis"); var chartAxis_1 = require("../chartAxis"); var GroupedCategoryAxisLabel = /** @class */ (function (_super) { __extends(GroupedCategoryAxisLabel, _super); function GroupedCategoryAxisLabel() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.grid = false; return _this; } return GroupedCategoryAxisLabel; }(axis_1.AxisLabel)); var GroupedCategoryAxis = /** @class */ (function (_super) { __extends(GroupedCategoryAxis, _super); function GroupedCategoryAxis() { var _this = _super.call(this, new bandScale_1.BandScale()) || this; // Label scale (labels are positionsed between ticks, tick count = label count + 1). // We don't call is `labelScale` for consistency with other axes. _this.tickScale = new bandScale_1.BandScale(); _this.longestSeparatorLength = 0; _this.translation = { x: 0, y: 0 }; /** * Axis rotation angle in degrees. */ _this.rotation = 0; _this.line = { width: 1, color: 'rgba(195, 195, 195, 1)' }; // readonly tick = new AxisTick(); _this.label = new GroupedCategoryAxisLabel(); /** * The color of the labels. * Use `undefined` rather than `rgba(0, 0, 0, 0)` to make labels invisible. */ _this.labelColor = 'rgba(87, 87, 87, 1)'; var _a = _this, group = _a.group, tickScale = _a.tickScale, scale = _a.scale; scale.paddingOuter = 0.1; scale.paddingInner = scale.paddingOuter * 2; _this.requestedRange = scale.range.slice(); _this.scale = scale; tickScale.paddingInner = 1; tickScale.paddingOuter = 0; _this.gridLineSelection = selection_1.Selection.select(group).selectAll(); _this.axisLineSelection = selection_1.Selection.select(group).selectAll(); _this.separatorSelection = selection_1.Selection.select(group).selectAll(); _this.labelSelection = selection_1.Selection.select(group).selectAll(); return _this; // this.group.append(this.bboxRect); // debug (bbox) } Object.defineProperty(GroupedCategoryAxis.prototype, "domain", { get: function () { return this.scale.domain; }, set: function (domainValues) { // Prevent duplicate categories. var values = domainValues.filter(function (s, i, arr) { return arr.indexOf(s) === i; }); this.scale.domain = values; var tickTree = tree_1.ticksToTree(values); this.tickTreeLayout = tree_1.treeLayout(tickTree); var domain = values.slice(); domain.push(''); this.tickScale.domain = domain; this.resizeTickTree(); }, enumerable: true, configurable: true }); Object.defineProperty(GroupedCategoryAxis.prototype, "range", { get: function () { return this.requestedRange.slice(); }, set: function (value) { this.requestedRange = value.slice(); this.updateRange(); }, enumerable: true, configurable: true }); GroupedCategoryAxis.prototype.updateRange = function () { var _a = this, rr = _a.requestedRange, vr = _a.visibleRange, scale = _a.scale; var span = (rr[1] - rr[0]) / (vr[1] - vr[0]); var shift = span * vr[0]; var start = rr[0] - shift; this.tickScale.range = scale.range = [start, start + span]; this.resizeTickTree(); }; GroupedCategoryAxis.prototype.resizeTickTree = function () { var s = this.scale; var range = s.domain.length ? [s.convert(s.domain[0]), s.convert(s.domain[s.domain.length - 1])] : s.range; var layout = this.tickTreeLayout; var lineHeight = this.lineHeight; if (layout) { layout.resize(Math.abs(range[1] - range[0]), layout.depth * lineHeight, (Math.min(range[0], range[1]) || 0) + (s.bandwidth || 0) / 2, -layout.depth * lineHeight, (range[1] - range[0]) < 0); } }; Object.defineProperty(GroupedCategoryAxis.prototype, "lineHeight", { get: function () { return this.label.fontSize * 1.5; }, enumerable: true, configurable: true }); Object.defineProperty(GroupedCategoryAxis.prototype, "gridLength", { get: function () { return this._gridLength; }, /** * The length of the grid. The grid is only visible in case of a non-zero value. */ set: function (value) { // Was visible and now invisible, or was invisible and now visible. if (this._gridLength && !value || !this._gridLength && value) { this.gridLineSelection = this.gridLineSelection.remove().setData([]); this.labelSelection = this.labelSelection.remove().setData([]); } this._gridLength = value; }, enumerable: true, configurable: true }); /** * Creates/removes/updates the scene graph nodes that constitute the axis. * Supposed to be called _manually_ after changing _any_ of the axis properties. * This allows to bulk set axis properties before updating the nodes. * The node changes made by this method are rendered on the next animation frame. * We could schedule this method call automatically on the next animation frame * when any of the axis properties change (the way we do when properties of scene graph's * nodes change), but this will mean that we first wait for the next animation * frame to make changes to the nodes of the axis, then wait for another animation * frame to render those changes. It's nice to have everything update automatically, * but this extra level of async indirection will not just introduce an unwanted delay, * it will also make it harder to reason about the program. */ GroupedCategoryAxis.prototype.update = function () { var _this = this; var _a = this, group = _a.group, scale = _a.scale, label = _a.label, tickScale = _a.tickScale, requestedRange = _a.requestedRange; var rangeStart = scale.range[0]; var rangeEnd = scale.range[1]; var rangeLength = Math.abs(rangeEnd - rangeStart); var bandwidth = (rangeLength / scale.domain.length) || 0; var parallelLabels = label.parallel; var rotation = angle_1.toRadians(this.rotation); var isHorizontal = Math.abs(Math.cos(rotation)) < 1e-8; var labelRotation = angle_1.normalizeAngle360(angle_1.toRadians(this.label.rotation)); group.translationX = this.translation.x; group.translationY = this.translation.y; group.rotation = rotation; var title = this.title; // The Text `node` of the Caption is not used to render the title of the grouped category axis. // The phantom root of the tree layout is used instead. if (title) { title.node.visible = false; } var lineHeight = this.lineHeight; // Render ticks and labels. var tickTreeLayout = this.tickTreeLayout; var labels = scale.ticks(); var treeLabels = tickTreeLayout ? tickTreeLayout.nodes : []; var isLabelTree = tickTreeLayout ? tickTreeLayout.depth > 1 : false; var ticks = tickScale.ticks(); // The side of the axis line to position the labels on. // -1 = left (default) // 1 = right var sideFlag = label.mirrored ? 1 : -1; // When labels are parallel to the axis line, the `parallelFlipFlag` is used to // flip the labels to avoid upside-down text, when the axis is rotated // such that it is in the right hemisphere, i.e. the angle of rotation // is in the [0, π] interval. // The rotation angle is normalized, so that we have an easier time checking // if it's in the said interval. Since the axis is always rendered vertically // and then rotated, zero rotation means 12 (not 3) o-clock. // -1 = flip // 1 = don't flip (default) var parallelFlipRotation = angle_1.normalizeAngle360(rotation); var parallelFlipFlag = (!labelRotation && parallelFlipRotation >= 0 && parallelFlipRotation <= Math.PI) ? -1 : 1; var regularFlipRotation = angle_1.normalizeAngle360(rotation - Math.PI / 2); // Flip if the axis rotation angle is in the top hemisphere. var regularFlipFlag = (!labelRotation && regularFlipRotation >= 0 && regularFlipRotation <= Math.PI) ? -1 : 1; var updateGridLines = this.gridLineSelection.setData(this.gridLength ? ticks : []); updateGridLines.exit.remove(); var enterGridLines = updateGridLines.enter.append(line_1.Line); var gridLineSelection = updateGridLines.merge(enterGridLines); var updateLabels = this.labelSelection.setData(treeLabels); updateLabels.exit.remove(); var enterLabels = updateLabels.enter.append(text_1.Text); var labelSelection = updateLabels.merge(enterLabels); var labelFormatter = label.formatter; var maxLeafLabelWidth = 0; labelSelection .each(function (node, datum, index) { node.fontStyle = label.fontStyle; node.fontWeight = label.fontWeight; node.fontSize = label.fontSize; node.fontFamily = label.fontFamily; node.fill = label.color; node.textBaseline = parallelFlipFlag === -1 ? 'bottom' : 'hanging'; // label.textBaseline = parallelLabels && !labelRotation // ? (sideFlag * parallelFlipFlag === -1 ? 'hanging' : 'bottom') // : 'middle'; node.textAlign = 'center'; node.translationX = datum.screenY - label.fontSize * 0.25; node.translationY = datum.screenX; if (index === 0) { // use the phantom root as the axis title if (title && title.enabled && labels.length > 0) { node.visible = true; node.text = title.text; node.fontSize = title.fontSize; node.fontStyle = title.fontStyle; node.fontWeight = title.fontWeight; node.fontFamily = title.fontFamily; node.textBaseline = 'hanging'; } else { node.visible = false; } } else { node.text = labelFormatter ? labelFormatter({ value: String(datum.label), index: index }) : String(datum.label); node.visible = datum.screenX >= requestedRange[0] && datum.screenX <= requestedRange[1]; } var bbox = node.computeBBox(); if (bbox && bbox.width > maxLeafLabelWidth) { maxLeafLabelWidth = bbox.width; } }); var labelX = sideFlag * label.padding; var autoRotation = parallelLabels ? parallelFlipFlag * Math.PI / 2 : (regularFlipFlag === -1 ? Math.PI : 0); var labelGrid = this.label.grid; var separatorData = []; labelSelection.each(function (label, datum, index) { label.x = labelX; label.rotationCenterX = labelX; if (!datum.children.length) { label.rotation = labelRotation; label.textAlign = 'end'; label.textBaseline = 'middle'; } else { label.translationX -= maxLeafLabelWidth - lineHeight + _this.label.padding; if (isHorizontal) { label.rotation = autoRotation; } else { label.rotation = -Math.PI / 2; } } // Calculate positions of label separators for all nodes except the root. // Each separator is placed to the top of the current label. if (datum.parent && isLabelTree) { var y = !datum.children.length ? datum.screenX - bandwidth / 2 : datum.screenX - datum.leafCount * bandwidth / 2; if (!datum.children.length) { if ((datum.number !== datum.children.length - 1) || labelGrid) { separatorData.push({ y: y, x1: 0, x2: -maxLeafLabelWidth - _this.label.padding * 2, toString: function () { return String(index); } }); } } else { var x = -maxLeafLabelWidth - _this.label.padding * 2 + datum.screenY; separatorData.push({ y: y, x1: x + lineHeight, x2: x, toString: function () { return String(index); } }); } } }); // Calculate the position of the long separator on the far bottom of the axis. var minX = 0; separatorData.forEach(function (d) { return minX = Math.min(minX, d.x2); }); this.longestSeparatorLength = Math.abs(minX); separatorData.push({ y: Math.max(rangeStart, rangeEnd), x1: 0, x2: minX, toString: function () { return String(separatorData.length); } }); var updateSeparators = this.separatorSelection.setData(separatorData); updateSeparators.exit.remove(); var enterSeparators = updateSeparators.enter.append(line_1.Line); var separatorSelection = updateSeparators.merge(enterSeparators); this.separatorSelection = separatorSelection; var epsilon = 0.0000001; separatorSelection.each(function (line, datum, i) { line.x1 = datum.x1; line.x2 = datum.x2; line.y1 = datum.y; line.y2 = datum.y; line.visible = datum.y >= requestedRange[0] - epsilon && datum.y <= requestedRange[1] + epsilon; line.stroke = _this.tick.color; line.fill = undefined; line.strokeWidth = 1; }); this.gridLineSelection = gridLineSelection; this.labelSelection = labelSelection; // Render axis lines. var lineCount = tickTreeLayout ? tickTreeLayout.depth + 1 : 1; var lines = []; for (var i = 0; i < lineCount; i++) { lines.push(i); } var updateAxisLines = this.axisLineSelection.setData(lines); updateAxisLines.exit.remove(); var enterAxisLines = updateAxisLines.enter.append(line_1.Line); var axisLineSelection = updateAxisLines.merge(enterAxisLines); this.axisLineSelection = axisLineSelection; axisLineSelection.each(function (line, _, index) { var x = index > 0 ? -maxLeafLabelWidth - _this.label.padding * 2 - (index - 1) * lineHeight : 0; line.x1 = x; line.x2 = x; line.y1 = requestedRange[0]; line.y2 = requestedRange[1]; line.strokeWidth = _this.line.width; line.stroke = _this.line.color; line.visible = labels.length > 0 && (index === 0 || (labelGrid && isLabelTree)); }); if (this.gridLength) { var styles_1 = this.gridStyle; var styleCount_1 = styles_1.length; gridLineSelection .each(function (line, datum, index) { var y = Math.round(tickScale.convert(datum)); line.x1 = 0; line.x2 = -sideFlag * _this.gridLength; line.y1 = y; line.y2 = y; line.visible = y >= requestedRange[0] && y <= requestedRange[1] && Math.abs(line.parent.translationY - rangeStart) > 1; var style = styles_1[index % styleCount_1]; line.stroke = style.stroke; line.strokeWidth = _this.tick.width; line.lineDash = style.lineDash; line.fill = undefined; }); } // debug (bbox) // const bbox = this.computeBBox(); // const bboxRect = this.bboxRect; // bboxRect.x = bbox.x; // bboxRect.y = bbox.y; // bboxRect.width = bbox.width; // bboxRect.height = bbox.height; }; GroupedCategoryAxis.prototype.computeBBox = function (options) { var includeTitle = !options || !options.excludeTitle; var left = Infinity; var right = -Infinity; var top = Infinity; var bottom = -Infinity; this.labelSelection.each(function (label, _, index) { // The label itself is rotated, but not translated, the group that // contains it is. So to capture the group transform in the label bbox // calculation we combine the transform matrices of the label and the group. // Depending on the timing of the `axis.computeBBox()` method call, we may // not have the group's and the label's transform matrices updated yet (because // the transform matrix is not recalculated whenever a node's transform attributes // change, instead it's marked for recalculation on the next frame by setting // the node's `dirtyTransform` flag to `true`), so we force them to update // right here by calling `computeTransformMatrix`. if (index > 0 || includeTitle) { // first node is the root (title) label.computeTransformMatrix(); var matrix = matrix_1.Matrix.flyweight(label.matrix); var labelBBox = label.computeBBox(); if (labelBBox) { var bbox = matrix.transformBBox(labelBBox); left = Math.min(left, bbox.x); right = Math.max(right, bbox.x + bbox.width); top = Math.min(top, bbox.y); bottom = Math.max(bottom, bbox.y + bbox.height); } } }); return new bbox_1.BBox(left, top, Math.max(right - left, this.longestSeparatorLength), bottom - top); }; // debug (bbox) // private bboxRect = (() => { // const rect = new Rect(); // rect.fill = undefined; // rect.stroke = 'red'; // rect.strokeWidth = 1; // rect.strokeOpacity = 0.2; // return rect; // })(); GroupedCategoryAxis.className = 'GroupedCategoryAxis'; GroupedCategoryAxis.type = 'groupedCategory'; return GroupedCategoryAxis; }(chartAxis_1.ChartAxis)); exports.GroupedCategoryAxis = GroupedCategoryAxis; //# sourceMappingURL=groupedCategoryAxis.js.map