@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.
360 lines (359 loc) • 14.9 kB
JavaScript
import { Observable, Trace } from '@nativescript/core';
import { getEventOrGestureName } from '@nativescript/core/ui/core/bindable';
import { GestureTypes } from '@nativescript/core/ui/gestures';
import { Chart } from './Chart';
import { LegendHorizontalAlignment, LegendOrientation, LegendVerticalAlignment } from '../components/Legend';
import { PieRadarChartTouchListener } from '../listener/PieRadarChartTouchListener';
import { CLog, CLogTypes, Utils } from '../utils/Utils';
const LOG_TAG = 'PieRadarChartBase';
/**
* View that represents a pie chart. Draws cake like slices.
*
*/
export class PieRadarChartBase extends Chart {
constructor() {
super(...arguments);
/**
* holds the normalized version of the current rotation angle of the chart
*/
this.mRotationAngle = 270;
/**
* holds the raw version of the current rotation angle of the chart
*/
this.mRawRotationAngle = 270;
/**
* flag that indicates if rotation is enabled or not
*/
this.mRotateEnabled = true;
/**
* Sets the minimum offset (padding) around the chart, defaults to 0
*/
this.minOffset = 0;
this.drawHighlight = true;
}
init() {
super.init();
//this.chartTouchListener = new PieRadarChartTouchListener(this);
}
getOrCreateTouchListener() {
if (!this.chartTouchListener) {
this.chartTouchListener = new PieRadarChartTouchListener(this);
if (!!this.nativeViewProtected) {
this.chartTouchListener.init();
}
}
return this.chartTouchListener;
}
calcMinMax() {
//mXAxis.axisRange = this.mData.getXVals().length - 1;
}
get maxVisibleValueCount() {
return this.mData.entryCount;
}
notifyDataSetChanged() {
if (!this.mData) {
return;
}
this.calcMinMax();
if (this.mLegend?.enabled) {
this.legendRenderer.computeLegend(this.mData);
}
this.calculateOffsets();
}
calculateOffsets() {
let legendLeft = 0, legendRight = 0, legendBottom = 0, legendTop = 0;
if (this.mLegend?.enabled && !this.mLegend.drawInside) {
const fullLegendWidth = Math.min(this.mLegend.mNeededWidth, this.viewPortHandler.chartWidth * this.mLegend.maxSizePercent);
switch (this.mLegend.orientation) {
case LegendOrientation.VERTICAL:
{
let xLegendOffset = 0;
if (this.mLegend.horizontalAlignment === LegendHorizontalAlignment.LEFT || this.mLegend.horizontalAlignment === LegendHorizontalAlignment.RIGHT) {
if (this.mLegend.verticalAlignment === LegendVerticalAlignment.CENTER) {
// this is the space between the legend and the chart
const spacing = 13;
xLegendOffset = fullLegendWidth + spacing;
}
else {
// this is the space between the legend and the chart
const spacing = 8;
const legendWidth = fullLegendWidth + spacing;
const legendHeight = this.mLegend.mNeededHeight + this.mLegend.mTextHeightMax;
const center = this.center;
const bottomX = this.mLegend.horizontalAlignment === LegendHorizontalAlignment.RIGHT ? this.viewPortHandler.chartWidth - legendWidth + 15 : legendWidth - 15;
const bottomY = legendHeight + 15;
const distLegend = this.distanceToCenter(bottomX, bottomY);
const reference = this.getPosition(center, this.radius, this.getAngleForPoint(bottomX, bottomY));
const distReference = this.distanceToCenter(reference.x, reference.y);
const minOffset = 5;
if (bottomY >= center.y && this.viewPortHandler.chartHeight - legendWidth > this.viewPortHandler.chartWidth) {
xLegendOffset = legendWidth;
}
else if (distLegend < distReference) {
const diff = distReference - distLegend;
xLegendOffset = minOffset + diff;
}
}
}
switch (this.mLegend.horizontalAlignment) {
case LegendHorizontalAlignment.LEFT:
legendLeft = xLegendOffset;
break;
case LegendHorizontalAlignment.RIGHT:
legendRight = xLegendOffset;
break;
case LegendHorizontalAlignment.CENTER:
switch (this.mLegend.verticalAlignment) {
case LegendVerticalAlignment.TOP:
legendTop = Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent);
break;
case LegendVerticalAlignment.BOTTOM:
legendBottom = Math.min(this.mLegend.mNeededHeight, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent);
break;
}
break;
}
}
break;
case LegendOrientation.HORIZONTAL:
let yLegendOffset = 0;
if (this.mLegend.verticalAlignment === LegendVerticalAlignment.TOP || this.mLegend.verticalAlignment === LegendVerticalAlignment.BOTTOM) {
// It's possible that we do not need this offset anymore as it
// is available through the extraOffsets, but changing it can mean
// changing default visibility for existing apps.
const yOffset = this.requiredLegendOffset;
yLegendOffset = Math.min(this.mLegend.mNeededHeight + yOffset, this.viewPortHandler.chartHeight * this.mLegend.maxSizePercent);
switch (this.mLegend.verticalAlignment) {
case LegendVerticalAlignment.TOP:
legendTop = yLegendOffset;
break;
case LegendVerticalAlignment.BOTTOM:
legendBottom = yLegendOffset;
break;
}
}
break;
}
legendLeft += this.requiredBaseOffset;
legendRight += this.requiredBaseOffset;
legendTop += this.requiredBaseOffset;
legendBottom += this.requiredBaseOffset;
}
let minOffset = this.minOffset;
// detect PieChart while preventing circular dep
if (!this['circleBox']) {
const x = this.xAxis;
if (x.enabled && x.drawLabels) {
minOffset = Math.max(minOffset, x.mLabelRotatedWidth);
}
}
legendTop += this.extraTopOffset;
legendRight += this.extraRightOffset;
legendBottom += this.extraBottomOffset;
legendLeft += this.extraLeftOffset;
const offsetLeft = Math.max(minOffset, legendLeft);
const offsetTop = Math.max(minOffset, legendTop);
const offsetRight = Math.max(minOffset, legendRight);
const offsetBottom = Math.max(minOffset, Math.max(this.requiredBaseOffset, legendBottom));
this.viewPortHandler.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom);
if (Trace.isEnabled()) {
CLog(CLogTypes.info, LOG_TAG, 'offsetLeft: ' + offsetLeft + ', offsetTop: ' + offsetTop + ', offsetRight: ' + offsetRight + ', offsetBottom: ' + offsetBottom);
}
}
/**
* Returns the angle relative to the chart center for the given polet on the
* chart in degrees. The angle is always between 0 and 360°, 0° is NORTH,
* 90° is EAST, ...
*
* @param x
* @param y
* @return
*/
getAngleForPoint(x, y) {
const c = this.centerOffsets;
const tx = x - c.x, ty = y - c.y;
const length = Math.sqrt(tx * tx + ty * ty);
const radians = Math.acos(ty / length);
let angle = radians * Utils.RAD2DEG;
if (x > c.x) {
angle = 360 - angle;
}
// add 90° because chart starts EAST
angle = angle + 90;
// neutralize overflow
if (angle > 360)
angle = angle - 360;
return angle;
}
/**
* Returns a recyclable MPPointF instance.
* Calculates the position around a center point, depending on the distance
* from the center, and the angle of the position around the center.
*
* @param center
* @param dist
* @param angle in degrees, converted to radians internally
* @return
*/
getPosition(center, dist, angle, outputPoint = { x: 0, y: 0 }) {
outputPoint.x = center.x + dist * Math.cos(angle * Utils.DEG2RAD);
outputPoint.y = center.y + dist * Math.sin(angle * Utils.DEG2RAD);
return outputPoint;
}
/**
* Returns the distance of a certain polet on the chart to the center of the
* chart.
*
* @param x
* @param y
* @return
*/
distanceToCenter(x, y) {
const c = this.centerOffsets;
let dist = 0;
let xDist = 0;
let yDist = 0;
if (x > c.x) {
xDist = x - c.x;
}
else {
xDist = c.x - x;
}
if (y > c.y) {
yDist = y - c.y;
}
else {
yDist = c.y - y;
}
// pythagoras
dist = Math.sqrt(Math.pow(xDist, 2.0) + Math.pow(yDist, 2.0));
return dist;
}
set highlightPerTapEnabled(enabled) {
this.mHighlightPerTapEnabled = enabled;
if (enabled) {
this.getOrCreateTouchListener().setTap(true);
}
else if (this.chartTouchListener) {
this.chartTouchListener.setTap(false);
}
}
get highlightPerTapEnabled() {
return this.mHighlightPerTapEnabled;
}
/**
* Set an offset for the rotation of the RadarChart in degrees. Default 270
* --> top (NORTH)
*
* @param angle
*/
set rotationAngle(angle) {
this.mRawRotationAngle = angle;
this.mRotationAngle = Utils.getNormalizedAngle(this.mRawRotationAngle);
}
/**
* gets a normalized version of the current rotation angle of the pie chart,
* which will always be between 0.0 < 360.0
*/
get rotationAngle() {
return this.mRotationAngle;
}
/**
* gets the raw version of the current rotation angle of the pie chart the
* returned value could be any value, negative or positive, outside of the
* 360 degrees. this is used when working with rotation direction, mainly by
* gestures and animations.
*/
get rawRotationAngle() {
return this.mRawRotationAngle;
}
/**
* Set this to true to enable the rotation / spinning of the chart by touch.
* Set it to false to disable it. Default: true
*
* @param enabled
*/
set rotationEnabled(enabled) {
this.mRotateEnabled = enabled;
if (enabled) {
this.getOrCreateTouchListener().setRotation(true);
}
else if (this.chartTouchListener) {
this.chartTouchListener.setRotation(false);
}
}
/**
* Returns true if rotation of the chart by touch is enabled, false if not.
*/
get rotationEnabled() {
return this.mRotateEnabled;
}
/**
* returns the diameter of the pie- or radar-chart
*/
get diameter() {
const content = this.viewPortHandler.contentRect;
content.left += this.extraLeftOffset;
content.top += this.extraTopOffset;
content.right -= this.extraRightOffset;
content.bottom -= this.extraBottomOffset;
return Math.min(content.width(), content.height());
}
get yChartMax() {
// TODO Auto-generated method stub
return 0;
}
get yChartMin() {
// TODO Auto-generated method stub
return 0;
}
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.getOrCreateTouchListener().setTap(true);
}
else if (arg === 'rotate') {
this.getOrCreateTouchListener().setRotation(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.getOrCreateTouchListener().setTap(false);
}
else if (arg === 'rotate' && !this.rotationEnabled) {
this.getOrCreateTouchListener().setRotation(false);
}
Observable.prototype.removeEventListener.call(this, evt, callback, thisArg);
}
}
else {
Observable.prototype.removeEventListener.call(this, arg, callback, thisArg);
}
}
}
}
//# sourceMappingURL=PieRadarChartBase.js.map