@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
JavaScript
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