UNPKG

@nativescript-community/ui-chart

Version:

A powerful chart / graph plugin, supporting line, bar, pie, radar, bubble, and candlestick charts as well as scaling, panning and animations.

1,232 lines 48.6 kB
import { Matrix } from '@nativescript-community/ui-canvas'; import { Observable, Trace } from '@nativescript/core'; import { getEventOrGestureName } from '@nativescript/core/ui/core/bindable'; import { GestureTypes } from '@nativescript/core/ui/gestures'; import { LegendHorizontalAlignment, LegendOrientation, LegendVerticalAlignment } from '../components/Legend'; import { XAxisPosition } from '../components/XAxis'; import { AxisDependency, YAxis } from '../components/YAxis'; import { ChartHighlighter } from '../highlight/ChartHighlighter'; import { AnimatedMoveViewJob } from '../jobs/AnimatedMoveViewJob'; import { AnimatedZoomJob } from '../jobs/AnimatedZoomJob'; import { MoveViewJob } from '../jobs/MoveViewJob'; import { ZoomJob } from '../jobs/ZoomJob'; import { BarLineChartTouchListener } from '../listener/BarLineChartTouchListener'; import { XAxisRenderer } from '../renderer/XAxisRenderer'; import { YAxisRenderer } from '../renderer/YAxisRenderer'; import { Transformer } from '../utils/Transformer'; import { CLog, CLogTypes, Utils } from '../utils/Utils'; import { Chart } from './Chart'; const LOG_TAG = 'BarLineChartBase'; export class BarLineChartBase extends Chart { constructor() { super(...arguments); /** * the maximum number of entries to which values will be drawn * (entry numbers greater than this value will cause value-labels to disappear) */ this.maxVisibleValueCount = 100; /** * flag that indicates if auto scaling on the y axis is enabled */ this.autoScaleMinMaxEnabled = false; /** * flag that indicates zoomed pan gesture should only work with 2 pointers */ this.zoomedPanWith2Pointers = false; this.clipValuesToContent = true; this.clipDataToContent = true; this.drawHighlight = true; /** * Sets the minimum offset (padding) around the chart, defaults to 15 */ this.minOffset = 15; // public onTouchEvent( event) { // super.onTouchEvent(event); // if (mChartTouchListener == null || this.mData == null) // return false; // // check if touch gestures are enabled // if (!mTouchEnabled) // return false; // else // return this.chartTouchListener.onTouch(this, event); // } // public computeScroll() { // if (this.chartTouchListener instanceof BarLineChartTouchListener) // (this.chartTouchListener).computeScroll(); // } /** * ################ ################ ################ ################ */ /** * CODE BELOW THIS RELATED TO SCALING AND GESTURES AND MODIFICATION OF THE * VIEWPORT */ this.mZoomMatrixBuffer = new Matrix(); this.mFitScreenMatrixBuffer = new Matrix(); /** * wheter to highlight drawing will be clipped to contentRect, * otherwise they can bleed outside the content rect. */ this.clipHighlightToContent = true; /** * buffer for storing lowest visible x point */ this.posForGetLowestVisibleX = { x: 0, y: 0 }; /** * buffer for storing highest visible x point */ this.posForGetHighestVisibleX = { x: 0, y: 0 }; this.mOnSizeChangedBuffer = Utils.createArrayBuffer(2); } // /** the approximator object used for data filtering */ // private Approximator this.mApproximator; init() { super.init(); this.mAxisLeft = new YAxis(AxisDependency.LEFT, new WeakRef(this)); this.leftAxisTransformer = new Transformer(this.viewPortHandler); this.leftAxisRenderer = new YAxisRenderer(this.viewPortHandler, this.mAxisLeft, this.leftAxisTransformer); this.xAxisRenderer = new XAxisRenderer(this.viewPortHandler, this.xAxis, this.leftAxisTransformer); this.highlighter = new ChartHighlighter(this); } get gridBackgroundPaint() { if (!this.mGridBackgroundPaint) { this.mGridBackgroundPaint = Utils.getTemplatePaint('black-fill'); this.mGridBackgroundPaint.setColor('#F0F0F0'); // light } return this.mGridBackgroundPaint; } get borderPaint() { if (!this.mBorderPaint) { this.mBorderPaint = Utils.getTemplatePaint('black-stroke'); } return this.mBorderPaint; } getOrCreateBarTouchListener() { if (!this.chartTouchListener) { this.chartTouchListener = new BarLineChartTouchListener(this, this.viewPortHandler.getMatrixTouch(), 3); if (!!this.nativeViewProtected) { this.chartTouchListener.init(); } } return this.chartTouchListener; } draw(canvas) { super.draw(canvas); const noComputeAutoScaleOnNextDraw = this.noComputeAutoScaleOnNextDraw; const noComputeAxisOnNextDraw = this.noComputeAxisOnNextDraw; this.noComputeAxisOnNextDraw = false; this.noComputeAutoScaleOnNextDraw = false; if (!this.mData) return; // execute all drawing commands this.drawGridBackground(canvas); const xAxis = this.xAxis; const axisLeft = this.mAxisLeft; const axisRight = this.mAxisRight; // the order is important: // * computeAxis needs axis.axisMinimum set in autoScale // * calculateOffsets needs computeAxis because it needs axis longestLabel if (!noComputeAutoScaleOnNextDraw && this.autoScaleMinMaxEnabled) { this.autoScale(); } if (!noComputeAxisOnNextDraw) { this.leftAxisRenderer?.computeAxis(axisLeft.axisMinimum, axisLeft.axisMaximum, axisLeft.inverted); this.rightAxisRenderer?.computeAxis(axisRight.axisMinimum, axisRight.axisMaximum, axisRight.inverted); this.xAxisRenderer?.computeAxis(this.xAxis.axisMinimum, this.xAxis.axisMaximum, false); } if (!this.offsetsCalculated) { this.calculateOffsets(false); this.notify({ eventName: 'firstOffsetsCalculated' }); this.offsetsCalculated = true; } if (xAxis.drawGridLinesBehindData) this.xAxisRenderer.renderGridLines(canvas); if (axisLeft.drawGridLinesBehindData) this.leftAxisRenderer.renderGridLines(canvas); if (axisRight?.drawGridLinesBehindData) this.rightAxisRenderer.renderGridLines(canvas); if (xAxis.drawLimitLinesBehindData) this.xAxisRenderer.renderLimitLines(canvas); if (axisLeft.drawLimitLinesBehindData) this.leftAxisRenderer.renderLimitLines(canvas); if (axisRight?.drawLimitLinesBehindData) this.rightAxisRenderer.renderLimitLines(canvas); this.xAxisRenderer.renderAxisLine(canvas); this.leftAxisRenderer.renderAxisLine(canvas); this.rightAxisRenderer?.renderAxisLine(canvas); if (xAxis.drawLabelsBehindData) this.xAxisRenderer.renderAxisLabels(canvas); if (axisLeft.drawLabelsBehindData) this.leftAxisRenderer.renderAxisLabels(canvas); if (axisRight?.drawLabelsBehindData) this.rightAxisRenderer.renderAxisLabels(canvas); // make sure the data cannot be drawn outside the content-rect if (this.clipDataToContent) { canvas.save(); canvas.clipRect(this.viewPortHandler.contentRect); this.renderer.drawData(canvas); } else { this.renderer.drawData(canvas); } if (!xAxis.drawGridLinesBehindData) this.xAxisRenderer.renderGridLines(canvas); if (!axisLeft.drawGridLinesBehindData) this.leftAxisRenderer.renderGridLines(canvas); if (axisRight?.drawGridLinesBehindData === false) this.rightAxisRenderer.renderGridLines(canvas); if (!this.clipHighlightToContent && this.clipDataToContent) { // restore before drawing highlight canvas.restore(); } if (this.hasValuesToHighlight && this.drawHighlight) { this.renderer.drawHighlighted(canvas, this.indicesToHighlight, this.drawHighlight); } // Removes clipping rectangle if (this.clipHighlightToContent && this.clipDataToContent) { canvas.restore(); } this.renderer.drawExtras(canvas); if (!xAxis.drawLimitLinesBehindData) this.xAxisRenderer.renderLimitLines(canvas); if (!axisLeft.drawLimitLinesBehindData) this.leftAxisRenderer.renderLimitLines(canvas); if (axisRight?.drawLimitLinesBehindData === false) this.rightAxisRenderer.renderLimitLines(canvas); if (!xAxis.drawLabelsBehindData) this.xAxisRenderer.renderAxisLabels(canvas); if (!axisLeft.drawLabelsBehindData) this.leftAxisRenderer.renderAxisLabels(canvas); if (axisRight?.drawLabelsBehindData === false) this.rightAxisRenderer.renderAxisLabels(canvas); if (this.clipValuesToContent) { canvas.save(); canvas.clipRect(this.viewPortHandler.contentRect); this.renderer.drawValues(canvas); canvas.restore(); } else { this.renderer.drawValues(canvas); } this.legendRenderer?.renderLegend(canvas); this.drawDescription(canvas); this.drawMarkers(canvas); } prepareValuePxMatrix() { if (Trace.isEnabled()) { CLog(CLogTypes.info, LOG_TAG, 'Preparing Value-Px Matrix, xmin: ' + this.xAxis.axisMinimum + ', xmax: ' + this.xAxis.axisMaximum + ', xdelta: ' + this.xAxis.axisRange); } if (this.mAxisRight?.enabled) { this.rightAxisTransformer.prepareMatrixValuePx(this.xAxis.axisMinimum, this.xAxis.axisRange, this.mAxisRight.axisRange, this.mAxisRight.axisMinimum); } if (this.mAxisLeft?.enabled || this.xAxis.enabled) { this.leftAxisTransformer.prepareMatrixValuePx(this.xAxis.axisMinimum, this.xAxis.axisRange, this.mAxisLeft.axisRange, this.mAxisLeft.axisMinimum); } } prepareOffsetMatrix() { if (this.mAxisRight?.enabled) { this.rightAxisTransformer.prepareMatrixOffset(this.mAxisRight.inverted); } if (this.mAxisLeft?.enabled || this.xAxis.enabled) { this.leftAxisTransformer.prepareMatrixOffset(this.mAxisLeft.inverted); } } notifyDataSetChanged() { if (!this.mData || this.mData.dataSetCount === 0) { if (Trace.isEnabled()) { CLog(CLogTypes.info, LOG_TAG, 'Preparing... DATA NOT SET.'); } return; } else if (!this.viewPortHandler.hasChartDimens) { if (Trace.isEnabled()) { CLog(CLogTypes.info, LOG_TAG, 'Preparing... NOT SIZED YET.'); } return; } else { if (Trace.isEnabled()) { CLog(CLogTypes.info, LOG_TAG, 'Preparing...'); } } this.renderer?.initBuffers(); this.calcMinMax(); if (this.mAxisLeft?.enabled) { this.leftAxisRenderer.computeAxis(this.mAxisLeft.axisMinimum, this.mAxisLeft.axisMaximum, this.mAxisLeft.inverted); } if (this.mAxisRight?.enabled) { this.rightAxisRenderer.computeAxis(this.mAxisRight.axisMinimum, this.mAxisRight.axisMaximum, this.mAxisRight.inverted); } if (this.xAxis.enabled) { this.xAxisRenderer.computeAxis(this.xAxis.axisMinimum, this.xAxis.axisMaximum, false); } if (this.mLegend?.enabled) { this.legendRenderer.computeLegend(this.mData); } // offsets will be calculated on draw this.offsetsCalculated = false; this.invalidate(); } /** * Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. */ autoScale() { const fromX = this.lowestVisibleX; const toX = this.highestVisibleX; this.mData.calcMinMaxYRange(fromX, toX); this.xAxis.calculate(this.mData.xMin, this.mData.xMax); // calculate axis range (min / max) according to provided data if (this.mAxisLeft?.enabled) { this.mAxisLeft.calculate(this.mData.getYMin(AxisDependency.LEFT), this.mData.getYMax(AxisDependency.LEFT)); } if (this.mAxisRight?.enabled) { this.mAxisRight.calculate(this.mData.getYMin(AxisDependency.RIGHT), this.mData.getYMax(AxisDependency.RIGHT)); } } calcMinMax() { this.xAxis.calculate(this.mData.xMin, this.mData.xMax); // calculate axis range (min / max) according to provided data if (this.mAxisLeft?.enabled) { this.mAxisLeft.calculate(this.mData.getYMin(AxisDependency.LEFT), this.mData.getYMax(AxisDependency.LEFT)); } if (this.mAxisRight?.enabled) { this.mAxisRight.calculate(this.mData.getYMin(AxisDependency.RIGHT), this.mData.getYMax(AxisDependency.RIGHT)); } } calculateLegendOffsets(offsets) { offsets.left = 0; offsets.right = 0; offsets.top = 0; offsets.bottom = 0; // setup offsets for legend if (this.mLegend?.enabled && !this.mLegend.drawInside) { switch (this.mLegend.orientation) { case LegendOrientation.VERTICAL: switch (this.mLegend.horizontalAlignment) { case LegendHorizontalAlignment.LEFT: offsets.left += Math.min(this.mLegend.mNeededWidth, this.viewPortHandler.chartWidth * this.mLegend.maxSizePercent) + this.mLegend.xOffset; break; case LegendHorizontalAlignment.RIGHT: offsets.right += Math.min(this.mLegend.mNeededWidth, this.viewPortHandler.chartWidth * this.mLegend.maxSizePercent) + this.mLegend.xOffset; break; case LegendHorizontalAlignment.CENTER: switch (this.mLegend.verticalAlignment) { case LegendVerticalAlignment.TOP: offsets.top += Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent) + this.mLegend.yOffset; break; case LegendVerticalAlignment.BOTTOM: offsets.bottom += Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent) + this.mLegend.yOffset; break; default: break; } } break; case LegendOrientation.HORIZONTAL: switch (this.mLegend.verticalAlignment) { case LegendVerticalAlignment.TOP: offsets.top += Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent) + this.mLegend.yOffset; break; case LegendVerticalAlignment.BOTTOM: offsets.bottom += Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent) + this.mLegend.yOffset; break; default: break; } break; } } } calculateOffsets(force = true) { if (!this.viewPortHandler.hasChartDimens || (this.offsetsCalculated && !force)) { return; } this.offsetsCalculated = true; if (!this.mCustomViewPortEnabled) { const offsetBuffer = Utils.getTempRectF(); this.calculateLegendOffsets(offsetBuffer); let offsetLeft = offsetBuffer.left; let offsetTop = offsetBuffer.top; let offsetRight = offsetBuffer.right; let offsetBottom = offsetBuffer.bottom; // offsets for y-labels if (this.mAxisLeft?.needsOffset) { offsetLeft += this.mAxisLeft.getRequiredWidthSpace(this.leftAxisRenderer.axisLabelsPaint); } if (this.mAxisRight?.needsOffset) { offsetRight += this.mAxisRight.getRequiredWidthSpace(this.rightAxisRenderer.axisLabelsPaint); } if (this.xAxis.enabled && this.xAxis.drawLabels) { const xLabelHeight = this.xAxis.mLabelRotatedHeight + this.xAxis.yOffset; // offsets for x-labels if (this.xAxis.position === XAxisPosition.BOTTOM) { offsetBottom += xLabelHeight; } else if (this.xAxis.position === XAxisPosition.TOP) { offsetTop += xLabelHeight; } else if (this.xAxis.position === XAxisPosition.BOTH_SIDED) { offsetBottom += xLabelHeight; offsetTop += xLabelHeight; } } offsetTop += this.extraTopOffset; offsetRight += this.extraRightOffset; offsetBottom += this.extraBottomOffset; offsetLeft += this.extraLeftOffset; const minOffset = this.minOffset; this.viewPortHandler.restrainViewPort(Math.max(minOffset, offsetLeft), Math.max(minOffset, offsetTop), Math.max(minOffset, offsetRight), Math.max(minOffset, offsetBottom)); if (Trace.isEnabled()) { CLog(CLogTypes.info, LOG_TAG, 'offsetLeft: ' + offsetLeft + ', offsetTop: ' + offsetTop + ', offsetRight: ' + offsetRight + ', offsetBottom: ' + offsetBottom); CLog(CLogTypes.info, LOG_TAG, 'Content: ' + this.viewPortHandler.contentRect.toString()); } } this.prepareOffsetMatrix(); this.prepareValuePxMatrix(); } /** * draws the grid background */ drawGridBackground(c) { if (this.drawGridBackgroundEnabled) { // draw the grid background c.drawRect(this.viewPortHandler.contentRect, this.gridBackgroundPaint); } if (this.drawBorders) { c.drawRect(this.viewPortHandler.contentRect, this.borderPaint); } } /** * Returns the Transformer class that contains all matrices and is * responsible for transforming values into pixels on the screen and * backwards. */ getTransformer(which) { if (which === undefined) { return this.transformer; } if (which === AxisDependency.LEFT) return this.leftAxisTransformer; else return this.rightAxisTransformer; } get transformer() { if (this.mAxisLeft.enabled) { return this.leftAxisTransformer; } return this.rightAxisTransformer; } /** * Zooms in by 1.4f, into the charts center. */ zoomIn() { const center = this.viewPortHandler.contentCenter; this.viewPortHandler.zoomIn(center.x, -center.y, this.mZoomMatrixBuffer); this.viewPortHandler.refresh(this.mZoomMatrixBuffer, this, false); // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. this.calculateOffsets(); this.invalidate(); } /** * Zooms out by 0.7f, from the charts center. */ zoomOut() { const center = this.viewPortHandler.contentCenter; this.viewPortHandler.zoomOut(center.x, -center.y, this.mZoomMatrixBuffer); this.viewPortHandler.refresh(this.mZoomMatrixBuffer, this, false); // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. this.calculateOffsets(); this.invalidate(); } /** * Zooms out to original size. */ resetZoom() { this.viewPortHandler.mMatrixTouch.reset(); this.viewPortHandler.resetZoom(this.mZoomMatrixBuffer); this.viewPortHandler.refresh(this.mZoomMatrixBuffer, this, false); // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. this.calculateOffsets(); this.invalidate(); } /** * Zooms in or out by the given scale factor. x and y are the coordinates * (in pixels) of the zoom center. * * @param scaleX if < 1 --> zoom out, if > 1 --> zoom in * @param scaleY if < 1 --> zoom out, if > 1 --> zoom in * @param x * @param y */ zoom(scaleX, scaleY, x, y) { this.viewPortHandler.zoomAtPosition(scaleX, scaleY, x, y, this.mZoomMatrixBuffer); this.viewPortHandler.refresh(this.mZoomMatrixBuffer, this, false); // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. this.calculateOffsets(); this.invalidate(); if (this.hasListeners('zoom')) { this.notify({ eventName: 'zoom', object: this, scaleX, scaleY, x, y }); } } /** * Zooms in or out by the given scale factor. * x and y are the values (NOT PIXELS) of the zoom center.. * * @param scaleX * @param scaleY * @param xValue * @param yValue * @param axis the axis relative to which the zoom should take place */ zoomAtValue(scaleX, scaleY, xValue, yValue, axis) { const job = ZoomJob.getInstance(this.viewPortHandler, scaleX, scaleY, xValue, yValue, this.getTransformer(axis), axis, this); this.addViewportJob(job); } /** * Zooms to the center of the chart with the given scale factor. * * @param scaleX * @param scaleY */ zoomToCenter(scaleX, scaleY) { const center = this.centerOffsets; const save = this.mZoomMatrixBuffer; this.viewPortHandler.zoomAtPosition(scaleX, scaleY, center.x, -center.y, save); this.viewPortHandler.refresh(save, this, false); } /** * Zooms by the specified scale factor to the specified values on the specified axis. * * @param scaleX * @param scaleY * @param xValue * @param yValue * @param axis * @param duration */ zoomAndCenterAnimated(scaleX, scaleY, xValue, yValue, axis, duration) { const origin = this.getValuesByTouchPoint(this.viewPortHandler.contentLeft, this.viewPortHandler.contentTop, axis); const job = AnimatedZoomJob.getInstance(this.viewPortHandler, this, this.getTransformer(axis), this.getAxis(axis), this.xAxis.axisRange, scaleX, scaleY, this.viewPortHandler.getScaleX(), this.viewPortHandler.getScaleY(), xValue, yValue, origin.x, origin.y, duration); this.addViewportJob(job); } /** * Zooms by the specified scale factor to the specified values on the specified axis. * * @param scaleX * @param scaleY * @param xValue * @param yValue * @param axis */ zoomAndCenter(scaleX, scaleY, xValue, yValue, axis) { const origin = this.getValuesByTouchPoint(this.viewPortHandler.contentLeft, this.viewPortHandler.contentTop, axis); const job = AnimatedZoomJob.getInstance(this.viewPortHandler, this, this.getTransformer(axis), this.getAxis(axis), this.xAxis.axisRange, scaleX, scaleY, this.viewPortHandler.getScaleX(), this.viewPortHandler.getScaleY(), xValue, yValue, origin.x, origin.y, 0); job.phase = 1; job.onAnimationUpdate(0); } /** * Resets all zooming and dragging and makes the chart fit exactly it's * bounds. */ fitScreen() { const save = this.mFitScreenMatrixBuffer; this.viewPortHandler.fitScreen(save); this.viewPortHandler.refresh(save, this, false); this.calculateOffsets(); this.invalidate(); } /** * Sets the minimum scale factor value to which can be zoomed out. 1 = * fitScreen * * @param scaleX * @param scaleY */ setScaleMinima(scaleX, scaleY) { this.viewPortHandler.setMinimumScaleX(scaleX); this.viewPortHandler.setMinimumScaleY(scaleY); } /** * Sets the scale factor. 1 = * fitScreen * * @param scaleX * @param scaleY */ setScale(scaleX, scaleY) { this.viewPortHandler.setScale(scaleX, scaleY); // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. this.calculateOffsets(); this.invalidate(); } /** * Sets the size of the area (range on the x-axis) that should be maximum * visible at once (no further zooming out allowed). If this is e.g. set to * 10, no more than a range of 10 on the x-axis can be viewed at once without * scrolling. * * @param maxXRange The maximum visible range of x-values. */ set visibleXRangeMaximum(maxXRange) { const xScale = this.xAxis.axisRange / maxXRange; this.viewPortHandler.setMinimumScaleX(xScale); } /** * Sets the size of the area (range on the x-axis) that should be minimum * visible at once (no further zooming in allowed). If this is e.g. set to * 10, no less than a range of 10 on the x-axis can be viewed at once without * scrolling. * * @param minXRange The minimum visible range of x-values. */ set visibleXRangeMinimum(minXRange) { const xScale = this.xAxis.axisRange / minXRange; this.viewPortHandler.setMaximumScaleX(xScale); } /** * Limits the maximum and minimum x range that can be visible by pinching and zooming. e.g. minRange=10, maxRange=100 the * smallest range to be displayed at once is 10, and no more than a range of 100 values can be viewed at once without * scrolling * * @param minXRange * @param maxXRange */ setVisibleXRange(minXRange, maxXRange) { const minScale = this.xAxis.axisRange / minXRange; const maxScale = this.xAxis.axisRange / maxXRange; this.viewPortHandler.setMinMaxScaleX(minScale, maxScale); } /** * Sets the size of the area (range on the y-axis) that should be maximum * visible at once. * * @param maxYRange the maximum visible range on the y-axis * @param axis the axis for which this limit should apply */ setVisibleYRangeMaximum(maxYRange, axis) { const yScale = this.getAxisRange(axis) / maxYRange; this.viewPortHandler.setMinimumScaleY(yScale); } /** * Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. * * @param minYRange * @param axis the axis for which this limit should apply */ setVisibleYRangeMinimum(minYRange, axis) { const yScale = this.getAxisRange(axis) / minYRange; this.viewPortHandler.setMaximumScaleY(yScale); } /** * Limits the maximum and minimum y range that can be visible by pinching and zooming. * * @param minYRange * @param maxYRange * @param axis */ setVisibleYRange(minYRange, maxYRange, axis) { const minScale = this.getAxisRange(axis) / minYRange; const maxScale = this.getAxisRange(axis) / maxYRange; this.viewPortHandler.setMinMaxScaleY(minScale, maxScale); } /** * Moves the left side of the current viewport to the specified x-position. * This also refreshes the chart by calling invalidate(). * * @param xValue */ moveViewToX(xValue) { const job = MoveViewJob.getInstance(this.viewPortHandler, xValue, 0, this.transformer, this); this.addViewportJob(job); } /** * This will move the left side of the current viewport to the specified * x-value on the x-axis, and center the viewport to the specified y value on the y-axis. * This also refreshes the chart by calling invalidate(). * * @param xValue * @param yValue * @param axis - which axis should be used as a reference for the y-axis */ moveViewTo(xValue, yValue, axis) { const yInView = this.getAxisRange(axis) / this.viewPortHandler.getScaleY(); const job = MoveViewJob.getInstance(this.viewPortHandler, xValue, yValue + yInView / 2, this.getTransformer(axis), this); this.addViewportJob(job); } /** * This will move the left side of the current viewport to the specified x-value * and center the viewport to the y value animated. * This also refreshes the chart by calling invalidate(). * * @param xValue * @param yValue * @param axis * @param duration the duration of the animation in milliseconds */ moveViewToAnimated(xValue, yValue, axis, duration) { const bounds = this.getValuesByTouchPoint(this.viewPortHandler.contentLeft, this.viewPortHandler.contentTop, axis); const yInView = this.getAxisRange(axis) / this.viewPortHandler.getScaleY(); const job = AnimatedMoveViewJob.getInstance(this.viewPortHandler, xValue, yValue + yInView / 2, this.getTransformer(axis), this, bounds.x, bounds.y, duration); this.addViewportJob(job); // MPPointD.recycleInstance(bounds); } /** * Centers the viewport to the specified y value on the y-axis. * This also refreshes the chart by calling invalidate(). * * @param yValue * @param axis - which axis should be used as a reference for the y-axis */ centerViewToY(yValue, axis) { const valsInView = this.getAxisRange(axis) / this.viewPortHandler.getScaleY(); const job = MoveViewJob.getInstance(this.viewPortHandler, 0, yValue + valsInView / 2, this.getTransformer(axis), this); this.addViewportJob(job); } /** * This will move the center of the current viewport to the specified * x and y value. * This also refreshes the chart by calling invalidate(). * * @param xValue * @param yValue * @param axis - which axis should be used as a reference for the y axis */ centerViewTo(xValue, yValue, axis) { const yInView = this.getAxisRange(axis) / this.viewPortHandler.getScaleY(); const xInView = this.xAxis.axisRange / this.viewPortHandler.getScaleX(); const job = MoveViewJob.getInstance(this.viewPortHandler, xValue - xInView / 2, yValue + yInView / 2, this.getTransformer(axis), this); this.addViewportJob(job); } /** * This will move the center of the current viewport to the specified * x and y value animated. * * @param xValue * @param yValue * @param axis * @param duration the duration of the animation in milliseconds */ centerViewToAnimated(xValue, yValue, axis, duration) { const bounds = this.getValuesByTouchPoint(this.viewPortHandler.contentLeft, this.viewPortHandler.contentTop, axis); const yInView = this.getAxisRange(axis) / this.viewPortHandler.getScaleY(); const xInView = this.xAxis.axisRange / this.viewPortHandler.getScaleX(); const job = AnimatedMoveViewJob.getInstance(this.viewPortHandler, xValue - xInView / 2, yValue + yInView / 2, this.getTransformer(axis), this, bounds.x, bounds.y, duration); this.addViewportJob(job); // MPPointD.recycleInstance(bounds); } /** * Sets custom offsets for the current ViewPort (the offsets on the sides of * the actual chart window). Setting this will prevent the chart from * automatically calculating it's offsets. Use resetViewPortOffsets() to * undo this. ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use * setExtraOffsets(...). * * @param left * @param top * @param right * @param bottom */ setViewPortOffsets(left, top, right, bottom) { this.mCustomViewPortEnabled = true; // post(new Runnable() { // public run() { this.viewPortHandler.restrainViewPort(left, top, right, bottom); this.prepareOffsetMatrix(); this.prepareValuePxMatrix(); // } // }); } /** * Resets all custom offsets set via setViewPortOffsets(...) method. Allows * the chart to again calculate all offsets automatically. */ resetViewPortOffsets() { this.mCustomViewPortEnabled = false; this.calculateOffsets(); } /** * ################ ################ ################ ################ */ /** CODE BELOW IS GETTERS AND SETTERS */ /** * Returns the range of the specified axis. * * @param axis * @return */ getAxisRange(axis) { if (axis === AxisDependency.LEFT) return this.mAxisLeft.axisRange; else return this.mAxisRight && this.mAxisRight.axisRange; } /** * Returns a recyclable MPPointF instance. * Returns the position (in pixels) the provided Entry has inside the chart * view or null, if the provided Entry is null. * * @param e * @return */ getPosition(e, axis) { if (!e) return null; const pts = Utils.getTempArray(2); pts[0] = e.getX(); pts[1] = e.getY(); this.getTransformer(axis).pointValuesToPixel(pts); return { x: pts[0], y: pts[1] }; } /** * Set this to true to allow highlighting per dragging over the chart * surface when it is fully zoomed out. Default: true * * @param enabled */ set highlightPerDragEnabled(enabled) { this.mHighlightPerDragEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setPan(true); } else if (this.chartTouchListener) { this.chartTouchListener.setPan(false); } } get highlightPerDragEnabled() { return this.mHighlightPerDragEnabled; } /** * Sets the color for the background of the chart-drawing area (everything * behind the grid lines). * * @param color */ set gridBackgroundColor(color) { this.gridBackgroundPaint.setColor(color); } /** * Set this to true to enable dragging (moving the chart with the finger) * for the chart (this does not effect scaling). * * @param enabled */ set dragEnabled(enabled) { this.dragXEnabled = enabled; this.dragYEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setPan(true); } else if (this.chartTouchListener) { this.chartTouchListener.setPan(false); } } /** * Returns true if dragging is enabled for the chart, false if not. */ get dragEnabled() { return this.dragXEnabled || this.dragYEnabled; } /** * Set this to true to enable scaling (zooming in and out by gesture) for * the chart (this does not effect dragging) on both X- and Y-Axis. * * @param enabled */ set scaleEnabled(enabled) { this.mScaleXEnabled = enabled; this.mScaleYEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setPinch(true); } else if (this.chartTouchListener) { this.chartTouchListener.setPinch(false); } } set scaleXEnabled(enabled) { this.mScaleXEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setPinch(true); } else if (!this.mScaleYEnabled && this.chartTouchListener) { this.chartTouchListener.setPinch(false); } } get scaleXEnabled() { return this.mScaleXEnabled; } set scaleYEnabled(enabled) { this.mScaleYEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setPinch(true); } else if (!this.mScaleXEnabled && this.chartTouchListener) { this.chartTouchListener.setPinch(false); } } get scaleYEnabled() { return this.mScaleYEnabled; } /** * Set this to true to enable zooming in by double-tap on the chart. * Default: enabled * * @param enabled */ set doubleTapToZoomEnabled(enabled) { this.mDoubleTapToZoomEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setDoubleTap(true); } else if (this.chartTouchListener) { this.chartTouchListener.setDoubleTap(false); } } /** * Returns true if zooming via double-tap is enabled false if not. */ get doubleTapToZoomEnabled() { return this.mDoubleTapToZoomEnabled; } set highlightPerTapEnabled(enabled) { this.mHighlightPerTapEnabled = enabled; if (enabled) { this.getOrCreateBarTouchListener().setTap(true); } else if (this.chartTouchListener) { this.chartTouchListener.setTap(false); } } get highlightPerTapEnabled() { return this.mHighlightPerTapEnabled; } /** * Sets the width of the border lines in dp. * * @param width */ setBorderWidth(width) { this.borderPaint.setStrokeWidth(width); } /** * Sets the color of the chart border lines. * * @param color */ setBorderColor(color) { this.borderPaint.setColor(color); } /** * Returns a recyclable MPPointD instance * Returns the x and y values in the chart at the given touch point * (encapsulated in a MPPointD). This method transforms pixel coordinates to * coordinates / values in the chart. This is the opposite method to * getPixelForValues(...). * * @param x * @param y * @return */ // public getValuesByTouchPoint( x ,y, axis) { // const result = {x:0, y:0}; // this.getValuesByTouchPoint(x, y, axis, result); // return result; // } getValuesByTouchPoint(x, y, axis) { return this.getTransformer(axis).getValuesByTouchPoint(x, y); } /** * Returns a recyclable MPPointD instance * Transforms the given chart values into pixels. This is the opposite * method to getValuesByTouchPoint(...). * * @param x * @param y * @return */ getPixelForValues(x, y, axis) { return this.getTransformer(axis).getPixelForValues(x, y); } /** * Returns a recyclable MPPointD instance * Transforms the given chart values into pixels. This is the opposite * method to getValuesByTouchPoint(...). * * @param set * @param entry * @param entry * @return */ getPixelForEntry(set, entry, index) { const xVal = set.getEntryXValue(entry, index); return this.getTransformer(set.axisDependency).getPixelForValues(xVal, set.getEntryYValue(entry)); // return this.getTransformer(axis).getPixelForValues(x, y); } /** * returns the Entry object displayed at the touched position of the chart * * @param x * @param y * @return */ getEntryByTouchPoint(x, y) { const h = this.getHighlightByTouchPoint(x, y); if (h) { return this.mData.getEntryForHighlight(h); } return null; } /** * returns the DataSet object displayed at the touched position of the chart * * @param x * @param y * @return */ getDataSetByTouchPoint(x, y) { const h = this.getHighlightByTouchPoint(x, y); if (h) { return this.mData.getDataSetByIndex(h.dataSetIndex); } return null; } /** * Returns the lowest x-index (value on the x-axis) that is still visible on * the chart. */ get lowestVisibleX() { this.transformer.getValuesByTouchPoint(this.viewPortHandler.contentLeft, this.viewPortHandler.contentBottom, this.posForGetLowestVisibleX); const result = Math.max(this.xAxis.axisMinimum, this.posForGetLowestVisibleX.x); return result; } /** * Returns the highest x-index (value on the x-axis) that is still visible * on the chart. */ get highestVisibleX() { this.transformer.getValuesByTouchPoint(this.viewPortHandler.contentRight, this.viewPortHandler.contentBottom, this.posForGetHighestVisibleX); const result = Math.min(this.xAxis.axisMaximum, this.posForGetHighestVisibleX.x); return result; } /** * Returns the range visible on the x-axis. */ getVisibleXRange() { return Math.abs(this.highestVisibleX - this.lowestVisibleX); } /** * returns the current x-scale factor */ getScaleX() { if (this.viewPortHandler == null) return 1; else return this.viewPortHandler.getScaleX(); } /** * returns the current y-scale factor */ getScaleY() { if (this.viewPortHandler == null) return 1; else return this.viewPortHandler.getScaleY(); } /** * if the chart is fully zoomed out, return true */ isFullyZoomedOut() { return this.viewPortHandler.isFullyZoomedOut(); } /** * Returns the left y-axis object. In the horizontal bar-chart, this is the * top axis. */ get leftAxis() { return this.mAxisLeft; } /** * @deprecated use leftAxis */ get axisLeft() { return this.mAxisLeft; } /** * Returns the right y-axis object. In the horizontal bar-chart, this is the * bottom axis. */ get rightAxis() { if (!this.mAxisRight) { this.mAxisRight = new YAxis(AxisDependency.RIGHT, new WeakRef(this)); this.rightAxisTransformer = new Transformer(this.viewPortHandler); this.rightAxisRenderer = new YAxisRenderer(this.viewPortHandler, this.mAxisRight, this.rightAxisTransformer); } return this.mAxisRight; } /** * @deprecated use rightAxis */ get axisRight() { return this.rightAxis; } /** * Returns the y-axis object to the corresponding AxisDependency. In the * horizontal bar-chart, LEFT == top, RIGHT == BOTTOM * * @param axis * @return */ getAxis(axis) { if (axis === AxisDependency.LEFT) return this.mAxisLeft; else return this.mAxisRight; } isInverted(axis) { return this.getAxis(axis).inverted; } /** * Set an offset in dp that allows the user to drag the chart over it's * bounds on the x-axis. * * @param offset */ setDragOffsetX(offset) { this.viewPortHandler.setDragOffsetX(offset); } /** * Set an offset in dp that allows the user to drag the chart over it's * bounds on the y-axis. * * @param offset */ setDragOffsetY(offset) { this.viewPortHandler.setDragOffsetY(offset); } /** * Returns true if both drag offsets (x and y) are zero or smaller. */ hasNoDragOffset() { return this.viewPortHandler.hasNoDragOffset(); } get yChartMax() { let max = -Infinity; if (this.mAxisLeft.enabled) { max = Math.max(this.mAxisLeft.axisMaximum, max); } if (this.mAxisRight?.enabled) { max = Math.max(this.mAxisRight.axisMaximum, max); } return max; } get yChartMin() { let min = Infinity; if (this.mAxisLeft.enabled) { min = Math.min(this.mAxisLeft.axisMinimum, min); } if (this.mAxisRight?.enabled) { min = Math.min(this.mAxisRight.axisMinimum, min); } return min; } /** * Returns true if either the left or the right or both axes are inverted. */ get anyAxisInverted() { if (this.mAxisLeft.enabled && this.mAxisLeft.inverted) return true; if (this.mAxisRight?.enabled && this.mAxisRight.inverted) return true; return false; } onSizeChanged(w, h, oldw, oldh) { // Saving current position of chart. this.mOnSizeChangedBuffer[0] = this.mOnSizeChangedBuffer[1] = 0; if (this.keepPositionOnRotation) { this.mOnSizeChangedBuffer[0] = this.viewPortHandler.contentLeft; this.mOnSizeChangedBuffer[1] = this.viewPortHandler.contentTop; this.transformer.pixelsToValue(this.mOnSizeChangedBuffer); } //Superclass transforms chart. super.onSizeChanged(w, h, oldw, oldh); if (this.keepPositionOnRotation) { //Restoring old position of chart. this.transformer.pointValuesToPixel(this.mOnSizeChangedBuffer); this.viewPortHandler.centerViewPort(this.mOnSizeChangedBuffer, this); } else { // a resize of the view will redraw the view anyway? this.viewPortHandler.refresh(this.viewPortHandler.getMatrixTouch(), this, false); } } addEventListener(arg, callback, thisArg) { if (typeof arg === 'number') { arg = GestureTypes[arg]; } if (typeof arg === 'string') { arg = getEventOrGestureName(arg); const events = arg.split(','); if (events.length > 0) { for (let i = 0; i < events.length; i++) { const evt = events[i].trim(); if (arg === 'tap') { this.getOrCreateBarTouchListener().setTap(true); } else if (arg === 'doubleTap') { this.getOrCreateBarTouchListener().setDoubleTap(true); } else if (arg === 'pan') { this.getOrCreateBarTouchListener().setPan(true); } else if (arg === 'pinch') { this.getOrCreateBarTouchListener().setPinch(true); } Observable.prototype.addEventListener.call(this, evt, callback, thisArg); } } else { Observable.prototype.addEventListener.call(this, arg, callback, thisArg); } } } removeEventListener(arg, callback, thisArg) { if (typeof arg === 'number') { arg = GestureTypes[arg]; } if (typeof arg === 'string') { arg = getEventOrGestureName(arg); const events = arg.split(','); if (events.length > 0) { for (let i = 0; i < events.length; i++) { const evt = events[i].trim(); if (arg === 'tap' && !this.highlightPerTapEnabled) { this.chartTouchListener && this.chartTouchListener.setTap(false); } else if (arg === 'doubleTap' && !this.doubleTapToZoomEnabled) { this.chartTouchListener && this.chartTouchListener.setDoubleTap(false); } else if (arg === 'pan' && !this.highlightPerDragEnabled && !this.dragEnabled) { this.chartTouchListener && this.chartTouchListener.setPan(false); } else if (arg === 'pinch' && !this.pinchZoomEnabled) { this.chartTouchListener && this.chartTouchListener.setPinch(false); } Observable.prototype.removeEventListener.call(this, evt, callback, thisArg); } } else { Observable.prototype.removeEventListener.call(this, arg, callback, thisArg); } } } } //# sourceMappingURL=BarLineChartBase.js.map