UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

1,000 lines (830 loc) 31.4 kB
import { drawing as draw, geometry as geom } from '@progress/kendo-drawing'; import ChartElement from './chart-element'; import TextBox from './text-box'; import AxisLabel from './axis-label'; import Note from './note'; import Box from './box'; import { ChartService } from '../services'; import createAxisTick from './utils/create-axis-tick'; import createAxisGridLine from './utils/create-axis-grid-line'; import { NONE, BLACK, CENTER, TOP, BOTTOM, LEFT, RIGHT, OUTSIDE, X, Y, WIDTH, HEIGHT } from '../common/constants'; import { alignPathToPixel, deepExtend, getTemplate, grep, defined, isObject, inArray, limitValue, round, setDefaultOptions } from '../common'; var Axis = (function (ChartElement) { function Axis(options, chartService) { if ( chartService === void 0 ) chartService = new ChartService(); ChartElement.call(this, options); this.chartService = chartService; if (!this.options.visible) { this.options = deepExtend({}, this.options, { labels: { visible: false }, line: { visible: false }, margin: 0, majorTickSize: 0, minorTickSize: 0 }); } this.options.minorTicks = deepExtend({}, { color: this.options.line.color, width: this.options.line.width, visible: this.options.minorTickType !== NONE }, this.options.minorTicks, { size: this.options.minorTickSize, align: this.options.minorTickType }); this.options.majorTicks = deepExtend({}, { color: this.options.line.color, width: this.options.line.width, visible: this.options.majorTickType !== NONE }, this.options.majorTicks, { size: this.options.majorTickSize, align: this.options.majorTickType }); this.initFields(); if (!this.options._deferLabels) { this.createLabels(); } this.createTitle(); this.createNotes(); } if ( ChartElement ) Axis.__proto__ = ChartElement; Axis.prototype = Object.create( ChartElement && ChartElement.prototype ); Axis.prototype.constructor = Axis; Axis.prototype.initFields = function initFields () { }; // abstract labelsCount(): Number // abstract createAxisLabel(index, options): AxisLabel Axis.prototype.labelsRange = function labelsRange () { return { min: this.options.labels.skip, max: this.labelsCount() }; }; Axis.prototype.normalizeLabelRotation = function normalizeLabelRotation (labelOptions) { var rotation = labelOptions.rotation; if (isObject(rotation)) { labelOptions.alignRotation = rotation.align; labelOptions.rotation = rotation.angle; } }; Axis.prototype.createLabels = function createLabels () { var this$1 = this; var options = this.options; var align = options.vertical ? RIGHT : CENTER; var labelOptions = deepExtend({ }, options.labels, { align: align, zIndex: options.zIndex }); var step = Math.max(1, labelOptions.step); this.clearLabels(); if (labelOptions.visible) { this.normalizeLabelRotation(labelOptions); if (labelOptions.rotation === "auto") { labelOptions.rotation = 0; options.autoRotateLabels = true; } var range = this.labelsRange(); for (var idx = range.min; idx < range.max; idx += step) { var labelContext = { index: idx, count: range.max }; var label = this$1.createAxisLabel(idx, labelOptions, labelContext); if (label) { this$1.append(label); this$1.labels.push(label); } } } }; Axis.prototype.clearLabels = function clearLabels () { this.children = grep(this.children, function (child) { return !(child instanceof AxisLabel); }); this.labels = []; }; Axis.prototype.clearTitle = function clearTitle () { var this$1 = this; if (this.title) { this.children = grep(this.children, function (child) { return child !== this$1.title; }); this.title = undefined; } }; Axis.prototype.clear = function clear () { this.clearLabels(); this.clearTitle(); }; Axis.prototype.lineBox = function lineBox () { var ref = this; var options = ref.options; var box = ref.box; var vertical = options.vertical; var mirror = options.labels.mirror; var axisX = mirror ? box.x1 : box.x2; var axisY = mirror ? box.y2 : box.y1; var lineWidth = options.line.width || 0; return vertical ? new Box(axisX, box.y1, axisX, box.y2 - lineWidth) : new Box(box.x1, axisY, box.x2 - lineWidth, axisY); }; Axis.prototype.createTitle = function createTitle () { var options = this.options; var titleOptions = deepExtend({ rotation: options.vertical ? -90 : 0, text: "", zIndex: 1, visualSize: true }, options.title); if (titleOptions.visible && titleOptions.text) { var title = new TextBox(titleOptions.text, titleOptions); this.append(title); this.title = title; } }; Axis.prototype.createNotes = function createNotes () { var this$1 = this; var options = this.options; var notes = options.notes; var items = notes.data || []; this.notes = []; for (var i = 0; i < items.length; i++) { var item = deepExtend({}, notes, items[i]); item.value = this$1.parseNoteValue(item.value); var note = new Note({ value: item.value, text: item.label.text, dataItem: item }, item, this$1.chartService); if (note.options.visible) { if (defined(note.options.position)) { if (options.vertical && !inArray(note.options.position, [ LEFT, RIGHT ])) { note.options.position = options.reverse ? LEFT : RIGHT; } else if (!options.vertical && !inArray(note.options.position, [ TOP, BOTTOM ])) { note.options.position = options.reverse ? BOTTOM : TOP; } } else { if (options.vertical) { note.options.position = options.reverse ? LEFT : RIGHT; } else { note.options.position = options.reverse ? BOTTOM : TOP; } } this$1.append(note); this$1.notes.push(note); } } }; Axis.prototype.parseNoteValue = function parseNoteValue (value) { return value; }; Axis.prototype.renderVisual = function renderVisual () { ChartElement.prototype.renderVisual.call(this); this.createPlotBands(); }; Axis.prototype.createVisual = function createVisual () { ChartElement.prototype.createVisual.call(this); this.createBackground(); this.createLine(); }; Axis.prototype.gridLinesVisual = function gridLinesVisual () { var gridLines = this._gridLines; if (!gridLines) { gridLines = this._gridLines = new draw.Group({ zIndex: -2 }); this.appendVisual(this._gridLines); } return gridLines; }; Axis.prototype.createTicks = function createTicks (lineGroup) { var options = this.options; var lineBox = this.lineBox(); var mirror = options.labels.mirror; var majorUnit = options.majorTicks.visible ? options.majorUnit : 0; var tickLineOptions = { // TODO // _alignLines: options._alignLines, vertical: options.vertical }; function render(tickPositions, tickOptions, skipUnit) { var count = tickPositions.length; var step = Math.max(1, tickOptions.step); if (tickOptions.visible) { for (var i = tickOptions.skip; i < count; i += step) { if (defined(skipUnit) && (i % skipUnit === 0)) { continue; } tickLineOptions.tickX = mirror ? lineBox.x2 : lineBox.x2 - tickOptions.size; tickLineOptions.tickY = mirror ? lineBox.y1 - tickOptions.size : lineBox.y1; tickLineOptions.position = tickPositions[i]; lineGroup.append(createAxisTick(tickLineOptions, tickOptions)); } } } render(this.getMajorTickPositions(), options.majorTicks); render(this.getMinorTickPositions(), options.minorTicks, majorUnit / options.minorUnit); }; Axis.prototype.createLine = function createLine () { var options = this.options; var line = options.line; var lineBox = this.lineBox(); if (line.width > 0 && line.visible) { var path = new draw.Path({ stroke: { width: line.width, color: line.color, dashType: line.dashType } /* TODO zIndex: line.zIndex, */ }); path.moveTo(lineBox.x1, lineBox.y1) .lineTo(lineBox.x2, lineBox.y2); if (options._alignLines) { alignPathToPixel(path); } var group = this._lineGroup = new draw.Group(); group.append(path); this.visual.append(group); this.createTicks(group); } }; Axis.prototype.getActualTickSize = function getActualTickSize () { var options = this.options; var tickSize = 0; if (options.majorTicks.visible && options.minorTicks.visible) { tickSize = Math.max(options.majorTicks.size, options.minorTicks.size); } else if (options.majorTicks.visible) { tickSize = options.majorTicks.size; } else if (options.minorTicks.visible) { tickSize = options.minorTicks.size; } return tickSize; }; Axis.prototype.createBackground = function createBackground () { var ref = this; var options = ref.options; var box = ref.box; var background = options.background; if (background) { this._backgroundPath = draw.Path.fromRect(box.toRect(), { fill: { color: background }, stroke: null }); this.visual.append(this._backgroundPath); } }; Axis.prototype.createPlotBands = function createPlotBands () { var this$1 = this; var options = this.options; var plotBands = options.plotBands || []; var vertical = options.vertical; var plotArea = this.plotArea; if (plotBands.length === 0) { return; } var group = this._plotbandGroup = new draw.Group({ zIndex: -1 }); var altAxis = grep(this.pane.axes, function (axis) { return axis.options.vertical !== this$1.options.vertical; })[0]; for (var idx = 0; idx < plotBands.length; idx++) { var item = plotBands[idx]; var slotX = (void 0), slotY = (void 0); var labelOptions = item.label; var label = (void 0); if (vertical) { slotX = (altAxis || plotArea.axisX).lineBox(); slotY = this$1.getSlot(item.from, item.to, true); } else { slotX = this$1.getSlot(item.from, item.to, true); slotY = (altAxis || plotArea.axisY).lineBox(); } if (labelOptions) { labelOptions.vAlign = labelOptions.position || LEFT; label = this$1.createPlotBandLabel( labelOptions, item, new Box( slotX.x1, slotY.y1, slotX.x2, slotY.y2 ) ); } if (slotX.width() !== 0 && slotY.height() !== 0) { var bandRect = new geom.Rect( [ slotX.x1, slotY.y1 ], [ slotX.width(), slotY.height() ] ); var path = draw.Path.fromRect(bandRect, { fill: { color: item.color, opacity: item.opacity }, stroke: null }); group.append(path); if (label) { group.append(label); } } } this.appendVisual(group); }; Axis.prototype.createPlotBandLabel = function createPlotBandLabel (label, item, box) { if (label.visible === false) { return null; } var text = label.text; var textbox; if (defined(label) && label.visible) { var labelTemplate = getTemplate(label); if (labelTemplate) { text = labelTemplate({ text: text, item: item }); } else if (label.format) { text = this.chartService.format.auto(label.format, text); } if (!label.color) { label.color = this.options.labels.color; } } textbox = new TextBox(text, label); textbox.reflow(box); textbox.renderVisual(); return textbox.visual; }; Axis.prototype.createGridLines = function createGridLines (altAxis) { var options = this.options; var minorGridLines = options.minorGridLines; var majorGridLines = options.majorGridLines; var minorUnit = options.minorUnit; var vertical = options.vertical; var axisLineVisible = altAxis.options.line.visible; var majorUnit = majorGridLines.visible ? options.majorUnit : 0; var lineBox = altAxis.lineBox(); var linePos = lineBox[vertical ? "y1" : "x1"]; var lineOptions = { lineStart: lineBox[vertical ? "x1" : "y1"], lineEnd: lineBox[vertical ? "x2" : "y2"], vertical: vertical }; var majorTicks = []; var container = this.gridLinesVisual(); function render(tickPositions, gridLine, skipUnit) { var count = tickPositions.length; var step = Math.max(1, gridLine.step); if (gridLine.visible) { for (var i = gridLine.skip; i < count; i += step) { var pos = round(tickPositions[i]); if (!inArray(pos, majorTicks)) { if (i % skipUnit !== 0 && (!axisLineVisible || linePos !== pos)) { lineOptions.position = pos; container.append(createAxisGridLine(lineOptions, gridLine)); majorTicks.push(pos); } } } } } render(this.getMajorTickPositions(), majorGridLines); render(this.getMinorTickPositions(), minorGridLines, majorUnit / minorUnit); return container.children; }; Axis.prototype.reflow = function reflow (box) { var ref = this; var options = ref.options; var labels = ref.labels; var title = ref.title; var vertical = options.vertical; var count = labels.length; var sizeFn = vertical ? WIDTH : HEIGHT; var titleSize = title ? title.box[sizeFn]() : 0; var space = this.getActualTickSize() + options.margin + titleSize; var rootBox = (this.getRoot() || {}).box || box; var boxSize = rootBox[sizeFn](); var maxLabelSize = 0; for (var i = 0; i < count; i++) { var labelSize = labels[i].box[sizeFn](); if (labelSize + space <= boxSize) { maxLabelSize = Math.max(maxLabelSize, labelSize); } } if (vertical) { this.box = new Box( box.x1, box.y1, box.x1 + maxLabelSize + space, box.y2 ); } else { this.box = new Box( box.x1, box.y1, box.x2, box.y1 + maxLabelSize + space ); } this.arrangeTitle(); this.arrangeLabels(); this.arrangeNotes(); }; Axis.prototype.getLabelsTickPositions = function getLabelsTickPositions () { return this.getMajorTickPositions(); }; Axis.prototype.labelTickIndex = function labelTickIndex (label) { return label.index; }; Axis.prototype.arrangeLabels = function arrangeLabels () { var this$1 = this; var ref = this; var options = ref.options; var labels = ref.labels; var labelsBetweenTicks = this.labelsBetweenTicks(); var vertical = options.vertical; var mirror = options.labels.mirror; var tickPositions = this.getLabelsTickPositions(); for (var idx = 0; idx < labels.length; idx++) { var label = labels[idx]; var tickIx = this$1.labelTickIndex(label); var labelSize = vertical ? label.box.height() : label.box.width(); var firstTickPosition = tickPositions[tickIx]; var nextTickPosition = tickPositions[tickIx + 1]; var positionStart = (void 0), positionEnd = (void 0); if (vertical) { if (labelsBetweenTicks) { var middle = firstTickPosition + (nextTickPosition - firstTickPosition) / 2; positionStart = middle - (labelSize / 2); } else { positionStart = firstTickPosition - (labelSize / 2); } positionEnd = positionStart; } else { if (labelsBetweenTicks) { positionStart = firstTickPosition; positionEnd = nextTickPosition; } else { positionStart = firstTickPosition - (labelSize / 2); positionEnd = positionStart + labelSize; } } this$1.positionLabel(label, mirror, positionStart, positionEnd); } }; Axis.prototype.positionLabel = function positionLabel (label, mirror, positionStart, positionEnd) { if ( positionEnd === void 0 ) positionEnd = positionStart; var options = this.options; var vertical = options.vertical; var lineBox = this.lineBox(); var labelOffset = this.getActualTickSize() + options.margin; var labelBox; if (vertical) { var labelX = lineBox.x2; if (mirror) { labelX += labelOffset; label.options.rotationOrigin = LEFT; } else { labelX -= labelOffset + label.box.width(); label.options.rotationOrigin = RIGHT; } labelBox = label.box.move(labelX, positionStart); } else { var labelY = lineBox.y1; if (mirror) { labelY -= labelOffset + label.box.height(); label.options.rotationOrigin = BOTTOM; } else { labelY += labelOffset; label.options.rotationOrigin = TOP; } labelBox = new Box( positionStart, labelY, positionEnd, labelY + label.box.height() ); } label.reflow(labelBox); }; Axis.prototype.autoRotateLabelAngle = function autoRotateLabelAngle (labelBox, slotWidth) { if (labelBox.width() < slotWidth) { return 0; } if (labelBox.height() > slotWidth) { return -90; } return -45; }; Axis.prototype.autoRotateLabels = function autoRotateLabels () { var this$1 = this; if (!this.options.autoRotateLabels || this.options.vertical) { return false; } var tickPositions = this.getMajorTickPositions(); var labels = this.labels; var limit = Math.min(labels.length, tickPositions.length - 1); var angle = 0; for (var idx = 0; idx < limit; idx++) { var width = Math.abs(tickPositions[idx + 1] - tickPositions[idx]); var labelBox = labels[idx].box; var labelAngle = this$1.autoRotateLabelAngle(labelBox, width); if (labelAngle !== 0) { angle = labelAngle; } if (angle === -90) { break; } } if (angle !== 0) { for (var idx$1 = 0; idx$1 < labels.length; idx$1++) { labels[idx$1].options.rotation = angle; labels[idx$1].reflow(new Box()); } return true; } }; Axis.prototype.arrangeTitle = function arrangeTitle () { var ref = this; var options = ref.options; var title = ref.title; var mirror = options.labels.mirror; var vertical = options.vertical; if (title) { if (vertical) { title.options.align = mirror ? RIGHT : LEFT; title.options.vAlign = title.options.position; } else { title.options.align = title.options.position; title.options.vAlign = mirror ? TOP : BOTTOM; } title.reflow(this.box); } }; Axis.prototype.arrangeNotes = function arrangeNotes () { var this$1 = this; for (var idx = 0; idx < this.notes.length; idx++) { var item = this$1.notes[idx]; var value = item.options.value; var slot = (void 0); if (defined(value)) { if (this$1.shouldRenderNote(value)) { item.show(); } else { item.hide(); } slot = this$1.noteSlot(value); } else { item.hide(); } item.reflow(slot || this$1.lineBox()); } }; Axis.prototype.noteSlot = function noteSlot (value) { return this.getSlot(value); }; Axis.prototype.alignTo = function alignTo (secondAxis) { var lineBox = secondAxis.lineBox(); var vertical = this.options.vertical; var pos = vertical ? Y : X; this.box.snapTo(lineBox, pos); if (vertical) { this.box.shrink(0, this.lineBox().height() - lineBox.height()); } else { this.box.shrink(this.lineBox().width() - lineBox.width(), 0); } this.box[pos + 1] -= this.lineBox()[pos + 1] - lineBox[pos + 1]; this.box[pos + 2] -= this.lineBox()[pos + 2] - lineBox[pos + 2]; }; Axis.prototype.axisLabelText = function axisLabelText (value, options, context) { var this$1 = this; var text; var tmpl = getTemplate(options); var defaultText = function () { if (!options.format) { return value; } return this$1.chartService.format.localeAuto( options.format, [ value ], options.culture ); }; if (tmpl) { var templateContext = Object.assign({}, context, { get text() { return defaultText(); }, value: value, format: options.format, culture: options.culture }); text = tmpl(templateContext); } else { text = defaultText(); } return text; }; Axis.prototype.slot = function slot (from , to, limit) { var slot = this.getSlot(from, to, limit); if (slot) { return slot.toRect(); } }; Axis.prototype.contentBox = function contentBox () { var box = this.box.clone(); var labels = this.labels; if (labels.length) { var axis = this.options.vertical ? Y : X; if (this.chartService.isPannable(axis)) { var offset = this.maxLabelOffset(); box[axis + 1] -= offset.start; box[axis + 2] += offset.end; } else { if (labels[0].options.visible) { box.wrap(labels[0].box); } var lastLabel = labels[labels.length - 1]; if (lastLabel.options.visible) { box.wrap(lastLabel.box); } } } return box; }; Axis.prototype.maxLabelOffset = function maxLabelOffset () { var this$1 = this; var ref = this.options; var vertical = ref.vertical; var reverse = ref.reverse; var labelsBetweenTicks = this.labelsBetweenTicks(); var tickPositions = this.getLabelsTickPositions(); var offsetField = vertical ? Y : X; var labels = this.labels; var startPosition = reverse ? 1 : 0; var endPosition = reverse ? 0 : 1; var maxStartOffset = 0; var maxEndOffset = 0; for (var idx = 0; idx < labels.length; idx++) { var label = labels[idx]; var tickIx = this$1.labelTickIndex(label); var startTick = (void 0), endTick = (void 0); if (labelsBetweenTicks) { startTick = tickPositions[tickIx + startPosition]; endTick = tickPositions[tickIx + endPosition]; } else { startTick = endTick = tickPositions[tickIx]; } maxStartOffset = Math.max(maxStartOffset, startTick - label.box[offsetField + 1]); maxEndOffset = Math.max(maxEndOffset, label.box[offsetField + 2] - endTick); } return { start: maxStartOffset, end: maxEndOffset }; }; Axis.prototype.limitRange = function limitRange (from, to, min, max, offset) { var options = this.options; if ((from < min && offset < 0 && (!defined(options.min) || options.min <= min)) || (max < to && offset > 0 && (!defined(options.max) || max <= options.max))) { return null; } if ((to < min && offset > 0) || (max < from && offset < 0)) { return { min: from, max: to }; } var rangeSize = to - from; var minValue = from; var maxValue = to; if (from < min && offset < 0) { minValue = limitValue(from, min, max); maxValue = limitValue(from + rangeSize, min + rangeSize, max); } else if (to > max && offset > 0) { maxValue = limitValue(to, min, max); minValue = limitValue(to - rangeSize, min, max - rangeSize); } return { min: minValue, max: maxValue }; }; Axis.prototype.valueRange = function valueRange () { return { min: this.seriesMin, max: this.seriesMax }; }; Axis.prototype.lineDir = function lineDir () { /* * Axis line direction: * * Vertical: up. * * Horizontal: right. */ var ref = this.options; var vertical = ref.vertical; var reverse = ref.reverse; return (vertical ? -1 : 1) * (reverse ? -1 : 1); }; Axis.prototype.lineInfo = function lineInfo () { var ref = this.options; var vertical = ref.vertical; var lineBox = this.lineBox(); var lineSize = vertical ? lineBox.height() : lineBox.width(); var axis = vertical ? Y : X; var axisDir = this.lineDir(); var startEdge = axisDir === 1 ? 1 : 2; var axisOrigin = axis + startEdge.toString(); var lineStart = lineBox[axisOrigin]; return { axis: axis, axisOrigin: axisOrigin, axisDir: axisDir, lineBox: lineBox, lineSize: lineSize, lineStart: lineStart }; }; Axis.prototype.pointOffset = function pointOffset (point) { var ref = this.lineInfo(); var axis = ref.axis; var axisDir = ref.axisDir; var axisOrigin = ref.axisOrigin; var lineBox = ref.lineBox; var lineSize = ref.lineSize; var relative = axisDir > 0 ? point[axis] - lineBox[axisOrigin] : lineBox[axisOrigin] - point[axis]; var offset = relative / lineSize; return offset; }; // Computes the axis range change (delta) for a given scale factor. // The delta is subtracted from the axis range: // * delta > 0 reduces the axis range (zoom-in) // * delta < 0 expands the axis range (zoom-out) Axis.prototype.scaleToDelta = function scaleToDelta (rawScale, range) { // Scale >= 1 would result in axis range of 0. // Scale <= -1 would reverse the scale direction. var MAX_SCALE = 0.999; var scale = limitValue(rawScale, -MAX_SCALE, MAX_SCALE); var delta; if (scale > 0) { delta = range * Math.min(1, scale); } else { delta = range - (range / (1 + scale)); } return delta; }; Axis.prototype.labelsBetweenTicks = function labelsBetweenTicks () { return !this.options.justified; }; //add legacy fields to the options that are no longer generated by default Axis.prototype.prepareUserOptions = function prepareUserOptions () { }; return Axis; }(ChartElement)); setDefaultOptions(Axis, { labels: { visible: true, rotation: 0, mirror: false, step: 1, skip: 0 }, line: { width: 1, color: BLACK, visible: true }, title: { visible: true, position: CENTER }, majorTicks: { align: OUTSIDE, size: 4, skip: 0, step: 1 }, minorTicks: { align: OUTSIDE, size: 3, skip: 0, step: 1 }, axisCrossingValue: 0, majorTickType: OUTSIDE, minorTickType: NONE, majorGridLines: { skip: 0, step: 1 }, minorGridLines: { visible: false, width: 1, color: BLACK, skip: 0, step: 1 }, // TODO: Move to line or labels options margin: 5, visible: true, reverse: false, justified: true, notes: { label: { text: "" } }, _alignLines: true, _deferLabels: false }); export default Axis;