@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.
585 lines (584 loc) • 18 kB
JavaScript
import { Trace } from '@nativescript/core';
import { Matrix, RectF } from '@nativescript-community/ui-canvas';
import { CLog, CLogTypes, Utils } from './Utils';
const EPSILON = 0.0001;
const LOG_TAG = 'ViewPortHandler';
/**
* Class that contains information about the charts current viewport settings, including offsets, scale & translation
* levels, ...
*
*/
export class ViewPortHandler {
constructor() {
/**
* matrix used for touch events
*/
this.mMatrixTouch = new Matrix();
/**
* this rectangle defines the area in which graph values can be drawn
*/
this.contentRect = new RectF(0, 0, 0, 0);
this.chartRect = new RectF(0, 0, 0, 0);
this.chartWidth = 0;
this.chartHeight = 0;
/**
* minimum scale value on the y-axis
*/
this.mMinScaleY = 1;
/**
* maximum scale value on the y-axis
*/
this.mMaxScaleY = Infinity;
/**
* minimum scale value on the x-axis
*/
this.mMinScaleX = 1;
/**
* maximum scale value on the x-axis
*/
this.mMaxScaleX = Infinity;
/**
* contains the current scale factor of the x-axis
*/
this.mScaleX = 1;
/**
* contains the current scale factor of the y-axis
*/
this.mScaleY = 1;
/**
* current translation (drag distance) on the x-axis
*/
this.mTransX = 0;
/**
* current translation (drag distance) on the y-axis
*/
this.mTransY = 0;
/**
* offset that allows the chart to be dragged over its bounds on the x-axis
*/
this.mTransOffsetX = 0;
/**
* offset that allows the chart to be dragged over its bounds on the x-axis
*/
this.mTransOffsetY = 0;
}
/**
* Sets the width and height of the chart.
*
* @param width
* @param height
*/
setChartDimens(width, height) {
const offsetLeft = this.offsetLeft;
const offsetTop = this.offsetTop;
const offsetRight = this.offsetRight;
const offsetBottom = this.offsetBottom;
this.chartHeight = Math.round(height);
this.chartWidth = Math.round(width);
this.chartRect.set(0, 0, this.chartWidth, this.chartHeight);
this.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom);
}
get hasChartDimens() {
if (this.chartHeight > 0 && this.chartWidth > 0)
return true;
else
return false;
}
restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom) {
this.contentRect.set(offsetLeft, offsetTop, this.chartWidth - offsetRight, this.chartHeight - offsetBottom);
}
get offsetLeft() {
return this.contentRect.left;
}
get offsetRight() {
return this.chartWidth - this.contentRect.right;
}
get offsetTop() {
return this.contentRect.top;
}
get offsetBottom() {
return this.chartHeight - this.contentRect.bottom;
}
get contentTop() {
return this.contentRect.top;
}
get contentLeft() {
return this.contentRect.left;
}
get contentRight() {
return this.contentRect.right;
}
get contentBottom() {
return this.contentRect.bottom;
}
get contentCenter() {
return { x: this.contentRect.centerX(), y: this.contentRect.centerY() };
}
/**
* Returns the smallest extension of the content rect (width or height).
*/
get smallestContentExtension() {
return Math.min(this.contentRect.width(), this.contentRect.height());
}
/**
* ################ ################ ################ ################
*/
/** CODE BELOW THIS RELATED TO SCALING AND GESTURES */
/**
* Zooms in by 1.4f, x and y are the coordinates (in pixels) of the zoom
* center.
*
* @param x
* @param y
*/
zoomIn(x, y, outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.postScale(1.4, 1.4, x, y);
return outputMatrix;
}
zoomOut(x, y, outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.postScale(0.7, 0.7, x, y);
return outputMatrix;
}
/**
* Zooms out to original size.
* @param outputMatrix
*/
resetZoom(outputMatrix) {
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.postScale(1, 1, 0, 0);
}
/**
* Post-scales by the specified scale factors.
*
* @param scaleX
* @param scaleY
* @return
*/
zoom(scaleX, scaleY, outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.postScale(scaleX, scaleY);
return outputMatrix;
}
/**
* Post-scales by the specified scale factors. x and y is pivot.
*
* @param scaleX
* @param scaleY
* @param x
* @param y
* @return
*/
zoomAtPosition(scaleX, scaleY, x, y, outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.postScale(scaleX, scaleY, x, y);
return outputMatrix;
}
/**
* Sets the scale factor to the specified values.
*
* @param scaleX
* @param scaleY
* @return
*/
setZoom(scaleX, scaleY, outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
outputMatrix.setScale(scaleX, scaleY);
return outputMatrix;
}
/**
* Sets the scale factor to the specified values. x and y is pivot.
*
* @param scaleX
* @param scaleY
* @param x
* @param y
* @return
*/
setZoomAtPosition(scaleX, scaleY, x, y) {
const save = new Matrix();
save.set(this.mMatrixTouch);
save.setScale(scaleX, scaleY, x, y);
return save;
}
/**
* Resets all zooming and dragging and makes the chart fit exactly it's
* bounds. Output Matrix is available for those who wish to cache the object.
*/
fitScreen(outputMatrix) {
if (!outputMatrix) {
outputMatrix = new Matrix();
}
this.mMinScaleX = 1;
this.mMinScaleY = 1;
outputMatrix.set(this.mMatrixTouch);
const vals = Utils.getTempArray(9);
for (let i = 0; i < 9; i++) {
vals[i] = 0;
}
outputMatrix.getValues(vals);
// reset all translations and scaling
vals[Matrix.MTRANS_X] = 0;
vals[Matrix.MTRANS_Y] = 0;
vals[Matrix.MSCALE_X] = 1;
vals[Matrix.MSCALE_Y] = 1;
outputMatrix.setValues(vals);
return outputMatrix;
}
/**
* Post-translates to the specified points. Output matrix allows for caching objects.
*
* @param transformedPts
* @return
*/
translate(transformedPts, outputMatrix) {
if (!outputMatrix) {
outputMatrix = Utils.getTempMatrix();
}
outputMatrix.reset();
outputMatrix.set(this.mMatrixTouch);
const x = transformedPts[0] - this.offsetLeft;
const y = transformedPts[1] - this.offsetTop;
outputMatrix.postTranslate(-x, -y);
return outputMatrix;
}
/**
* Centers the viewport around the specified position (x-index and y-value)
* in the chart. Centering the viewport outside the bounds of the chart is
* not possible. Makes most sense in combination with the
* setScaleMinima(...) method.
*
* @param transformedPts the position to center view viewport to
* @param view
* @return save
*/
centerViewPort(transformedPts, view) {
const save = Utils.getTempMatrix();
save.reset();
save.set(this.mMatrixTouch);
const x = transformedPts[0] - this.offsetLeft;
const y = transformedPts[1] - this.offsetTop;
save.postTranslate(-x, -y);
this.refresh(save, view, true);
}
/**
* call this method to refresh the graph with a given matrix
*
* @param newMatrix
* @return
*/
refresh(newMatrix, chart, invalidate) {
if (Trace.isEnabled()) {
CLog(CLogTypes.info, LOG_TAG, 'refresh:', newMatrix);
}
this.mMatrixTouch.set(newMatrix);
// make sure scale and translation are within their bounds
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
if (invalidate)
chart.invalidate();
newMatrix.set(this.mMatrixTouch);
return newMatrix;
}
setScale(scaleX, scaleY) {
if (!this.mMatrixBuffer) {
this.mMatrixBuffer = Utils.createArrayBuffer(9);
}
const matrixBuffer = this.mMatrixBuffer;
this.mMatrixTouch.getValues(matrixBuffer);
const curTransX = matrixBuffer[Matrix.MTRANS_X];
const curTransY = matrixBuffer[Matrix.MTRANS_Y];
// min scale-x is 1
this.mScaleX = Math.min(Math.max(this.mMinScaleX, scaleX), this.mMaxScaleX);
// min scale-y is 1
this.mScaleY = Math.min(Math.max(this.mMinScaleY, scaleY), this.mMaxScaleY);
let width = 0;
let height = 0;
if (this.contentRect) {
width = this.contentRect.width();
height = this.contentRect.height();
}
const maxTransX = -width * (this.mScaleX - 1);
this.mTransX = Math.min(Math.max(curTransX, maxTransX - this.mTransOffsetX), this.mTransOffsetX);
const maxTransY = height * (this.mScaleY - 1);
this.mTransY = Math.max(Math.min(curTransY, maxTransY + this.mTransOffsetY), -this.mTransOffsetY);
matrixBuffer[Matrix.MTRANS_X] = this.mTransX;
matrixBuffer[Matrix.MSCALE_X] = this.mScaleX;
matrixBuffer[Matrix.MTRANS_Y] = this.mTransY;
matrixBuffer[Matrix.MSCALE_Y] = this.mScaleY;
this.mMatrixTouch.setValues(matrixBuffer);
}
get scaleX() {
const matrixBuffer = this.mMatrixBuffer;
this.mMatrixTouch.getValues(matrixBuffer);
return matrixBuffer[Matrix.MSCALE_X];
}
get scaleY() {
const matrixBuffer = this.mMatrixBuffer;
this.mMatrixTouch.getValues(matrixBuffer);
return matrixBuffer[Matrix.MSCALE_Y];
}
/**
* limits the maximum scale and X translation of the given matrix
*
* @param matrix
*/
limitTransAndScale(matrix, content) {
if (!this.mMatrixBuffer) {
this.mMatrixBuffer = Utils.createArrayBuffer(9);
}
const matrixBuffer = this.mMatrixBuffer;
matrix.getValues(matrixBuffer);
const curTransX = matrixBuffer[Matrix.MTRANS_X];
const curScaleX = matrixBuffer[Matrix.MSCALE_X];
const curTransY = matrixBuffer[Matrix.MTRANS_Y];
const curScaleY = matrixBuffer[Matrix.MSCALE_Y];
// min scale-x is 1
this.mScaleX = Math.min(Math.max(this.mMinScaleX, curScaleX), this.mMaxScaleX);
// min scale-y is 1
this.mScaleY = Math.min(Math.max(this.mMinScaleY, curScaleY), this.mMaxScaleY);
let width = 0;
let height = 0;
if (content) {
width = content.width();
height = content.height();
}
const maxTransX = -width * (this.mScaleX - 1);
this.mTransX = Math.min(Math.max(curTransX, maxTransX - this.mTransOffsetX), this.mTransOffsetX);
const maxTransY = height * (this.mScaleY - 1);
this.mTransY = Math.max(Math.min(curTransY, maxTransY + this.mTransOffsetY), -this.mTransOffsetY);
matrixBuffer[Matrix.MTRANS_X] = this.mTransX;
matrixBuffer[Matrix.MSCALE_X] = this.mScaleX;
matrixBuffer[Matrix.MTRANS_Y] = this.mTransY;
matrixBuffer[Matrix.MSCALE_Y] = this.mScaleY;
matrix.setValues(matrixBuffer);
}
/**
* Sets the minimum scale factor for the x-axis
*
* @param xScale
*/
setMinimumScaleX(xScale) {
if (xScale < 1)
xScale = 1;
this.mMinScaleX = xScale;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
/**
* Sets the maximum scale factor for the x-axis
*
* @param xScale
*/
setMaximumScaleX(xScale) {
if (xScale === 0)
xScale = Infinity;
this.mMaxScaleX = xScale;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
/**
* Sets the minimum and maximum scale factors for the x-axis
*
* @param minScaleX
* @param maxScaleX
*/
setMinMaxScaleX(minScaleX, maxScaleX) {
if (minScaleX < 1)
minScaleX = 1;
if (maxScaleX === 0)
maxScaleX = Infinity;
this.mMinScaleX = minScaleX;
this.mMaxScaleX = maxScaleX;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
/**
* Sets the minimum scale factor for the y-axis
*
* @param yScale
*/
setMinimumScaleY(yScale) {
if (yScale < 1)
yScale = 1;
this.mMinScaleY = yScale;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
/**
* Sets the maximum scale factor for the y-axis
*
* @param yScale
*/
setMaximumScaleY(yScale) {
if (yScale === 0)
yScale = Infinity;
this.mMaxScaleY = yScale;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
setMinMaxScaleY(minScaleY, maxScaleY) {
if (minScaleY < 1)
minScaleY = 1;
if (maxScaleY === 0)
maxScaleY = Infinity;
this.mMinScaleY = minScaleY;
this.mMaxScaleY = maxScaleY;
this.limitTransAndScale(this.mMatrixTouch, this.contentRect);
}
/**
* Returns the charts-touch matrix used for translation and scale on touch.
*/
getMatrixTouch() {
return this.mMatrixTouch;
}
/**
* ################ ################ ################ ################
*/
/**
* BELOW METHODS FOR BOUNDS CHECK
*/
isInBoundsX(x) {
return this.isInBoundsLeft(x) && this.isInBoundsRight(x);
}
isInBoundsY(y) {
return this.isInBoundsTop(y) && this.isInBoundsBottom(y);
}
isInBounds(x, y) {
return this.isInBoundsX(x) && this.isInBoundsY(y);
}
isInBoundsLeft(x) {
return this.contentRect.left - (x + 1) <= EPSILON;
}
isInBoundsRight(x) {
x = (x * 100) / 100;
return this.contentRect.right - (x - 1) >= -EPSILON;
}
isInBoundsTop(y) {
return this.contentRect.top - y <= EPSILON;
}
isInBoundsBottom(y) {
y = (y * 100) / 100;
return this.contentRect.bottom - y >= -EPSILON;
}
/**
* returns the current x-scale factor
*/
getScaleX() {
return this.mScaleX;
}
/**
* returns the current y-scale factor
*/
getScaleY() {
return this.mScaleY;
}
getMinScaleX() {
return this.mMinScaleX;
}
getMaxScaleX() {
return this.mMaxScaleX;
}
getMinScaleY() {
return this.mMinScaleY;
}
getMaxScaleY() {
return this.mMaxScaleY;
}
/**
* Returns the translation (drag / pan) distance on the x-axis
*/
getTransX() {
return this.mTransX;
}
/**
* Returns the translation (drag / pan) distance on the y-axis
*/
getTransY() {
return this.mTransY;
}
/**
* if the chart is fully zoomed out, return true
*/
isFullyZoomedOut() {
return this.isFullyZoomedOutX() && this.isFullyZoomedOutY();
}
/**
* Returns true if the chart is fully zoomed out on it's y-axis (vertical).
*/
isFullyZoomedOutY() {
return !(this.mScaleY > this.mMinScaleY || this.mMinScaleY > 1);
}
/**
* Returns true if the chart is fully zoomed out on it's x-axis
* (horizontal).
*/
isFullyZoomedOutX() {
return !(this.mScaleX > this.mMinScaleX || this.mMinScaleX > 1);
}
/**
* 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.mTransOffsetX = 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.mTransOffsetY = offset;
}
/**
* Returns true if both drag offsets (x and y) are zero or smaller.
*/
hasNoDragOffset() {
return this.mTransOffsetX === 0 && this.mTransOffsetY === 0;
}
/**
* Returns true if the chart is not yet fully zoomed out on the x-axis
*/
canZoomOutMoreX() {
return this.mScaleX > this.mMinScaleX;
}
/**
* Returns true if the chart is not yet fully zoomed in on the x-axis
*/
canZoomInMoreX() {
return this.mScaleX < this.mMaxScaleX;
}
/**
* Returns true if the chart is not yet fully zoomed out on the y-axis
*/
canZoomOutMoreY() {
return this.mScaleY > this.mMinScaleY;
}
/**
* Returns true if the chart is not yet fully zoomed in on the y-axis
*/
canZoomInMoreY() {
return this.mScaleY < this.mMaxScaleY;
}
}
//# sourceMappingURL=ViewPortHandler.js.map