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.

453 lines (452 loc) 14.7 kB
import { RectF } from '@nativescript-community/ui-canvas'; import { PieHighlighter } from '../highlight/PieHighlighter'; import { PieChartRenderer } from '../renderer/PieChartRenderer'; import { Utils } from '../utils/Utils'; import { PieRadarChartBase } from './PieRadarChartBase'; const LOG_TAG = 'PieChart'; /** * View that represents a pie chart. Draws cake like slices. * */ export class PieChart extends PieRadarChartBase { constructor() { super(...arguments); /** * rect object that represents the bounds of the piechart, needed for * drawing the circle */ this.circleBox = new RectF(0, 0, 0, 0); /** * flag indicating if entry labels should be drawn or not */ this.drawEntryLabels = true; /** * array that holds the width of each pie-slice in degrees */ this.mDrawAngles = []; /** * array that holds the absolute angle in degrees of each slice */ this.mAbsoluteAngles = []; /** * if true, the white hole inside the chart will be drawn */ this.drawHoleEnabled = true; /** * variable for the text that is drawn in the center of the pie-chart */ this.centerText = ''; this.mCenterTextOffset = { x: 0, y: 0 }; /** * indicates the size of the hole in the center of the piechart, default: * radius / 2 */ this.mHoleRadiusPercent = 50; /** * the radius of the transparent circle that is drawn next to the hole * in the piechart in percent of the maximum radius (max = the radius of the * whole chart), default 55% -> means 5% larger than the center-hole by */ this.transparentCircleRadiusPercent = 55; /** * if enabled, centertext is drawn */ this.drawCenterText = true; /** * the rectangular radius of the bounding box for the center text, as a percentage of the pie * hole * default 1.f (100%) */ this.centerTextRadiusPercent = 100; this.mMaxAngle = 360; /** * Minimum angle to draw slices, this only works if there is enough room for all slices to have * the minimum angle, default 0. */ this.mMinAngleForSlices = 0; } init() { super.init(); this.renderer = new PieChartRenderer(this, this.animator, this.viewPortHandler); this.xAxis = null; this.highlighter = new PieHighlighter(this); } draw(canvas) { super.draw(canvas); if (!this.mData) return; this.renderer.drawData(canvas); if (this.hasValuesToHighlight && this.drawHighlight) { this.renderer.drawHighlighted(canvas, this.indicesToHighlight); } this.renderer.drawExtras(canvas); this.renderer.drawValues(canvas); if (this.legendRenderer) { this.legendRenderer.renderLegend(canvas); } this.drawDescription(canvas); this.drawMarkers(canvas); } calculateOffsets() { super.calculateOffsets(); // prevent nullpointer when no data set if (!this.mData) { return; } const diameter = this.diameter; const radius = diameter / 2; const c = this.centerOffsets; const shift = this.mData.getDataSet().selectionShift; // create the circle box that will contain the pie-chart (the bounds of the pie-chart) this.circleBox.set(c.x - radius + shift, c.y - radius + shift, c.x + radius - shift, c.y + radius - shift); } calcMinMax() { this.calcAngles(); } getMarkerPosition(highlight) { const center = this.centerCircleBox; let r = this.radius; let off = (r / 10) * 3.6; if (this.drawHoleEnabled) { off = (r - (r / 100) * this.holeRadius) / 2; } r -= off; // offset to keep things inside the chart const rotationAngle = this.rotationAngle; const entryIndex = highlight.x; // offset needed to center the drawn text in the slice const offset = this.mDrawAngles[entryIndex] / 2; // calculate the text position const x = r * Math.cos((rotationAngle + this.mAbsoluteAngles[entryIndex] - offset) * this.animator.phaseY * Utils.DEG2RAD) + center.x; const y = r * Math.sin((rotationAngle + this.mAbsoluteAngles[entryIndex] - offset) * this.animator.phaseY * Utils.DEG2RAD) + center.y; return [x, y]; } /** * calculates the needed angles for the chart slices */ calcAngles() { const entryCount = this.mData.entryCount; if (this.mDrawAngles.length !== entryCount) { this.mDrawAngles = []; } else { for (let i = 0; i < entryCount; i++) { this.mDrawAngles[i] = 0; } } if (this.mAbsoluteAngles.length !== entryCount) { this.mAbsoluteAngles = []; } else { for (let i = 0; i < entryCount; i++) { this.mAbsoluteAngles[i] = 0; } } const yValueSum = this.mData.getYValueSum(); const dataSets = this.mData.dataSets; const hasMinAngle = this.mMinAngleForSlices !== 0 && entryCount * this.mMinAngleForSlices <= this.mMaxAngle; const minAngles = []; let cnt = 0; let offset = 0; let diff = 0; for (let i = 0; i < this.mData.dataSetCount; i++) { const set = dataSets[i]; const yKey = set.yProperty; for (let j = 0; j < set.entryCount; j++) { const drawAngle = this.calcAngle(Math.abs(set.getEntryForIndex(j)[yKey]), yValueSum); if (hasMinAngle) { const temp = drawAngle - this.mMinAngleForSlices; if (temp <= 0) { minAngles[cnt] = this.mMinAngleForSlices; offset += -temp; } else { minAngles[cnt] = drawAngle; diff += temp; } } this.mDrawAngles[cnt] = drawAngle; if (cnt === 0) { this.mAbsoluteAngles[cnt] = this.mDrawAngles[cnt]; } else { this.mAbsoluteAngles[cnt] = this.mAbsoluteAngles[cnt - 1] + this.mDrawAngles[cnt]; } cnt++; } } if (hasMinAngle) { // Correct bigger slices by relatively reducing their angles based on the total angle needed to subtract // This requires that `entryCount * this.mMinAngleForSlices <= this.mMaxAngle` be true to properly work! for (let i = 0; i < entryCount; i++) { minAngles[i] -= ((minAngles[i] - this.mMinAngleForSlices) / diff) * offset; if (i === 0) { this.mAbsoluteAngles[0] = minAngles[0]; } else { this.mAbsoluteAngles[i] = this.mAbsoluteAngles[i - 1] + minAngles[i]; } } this.mDrawAngles = minAngles; } } /** * Checks if the given index is set to be highlighted. * * @param index * @return */ needsHighlight(index) { // no highlight if (!this.hasValuesToHighlight) { return false; } for (let i = 0; i < this.indicesToHighlight.length; i++) { // check if the xvalue for the given dataset needs highlight if (this.indicesToHighlight[i].x === index) { return true; } } return false; } /** * Calculates the needed angle for a given value * * @param value * @param yValueSum * @return */ calcAngle(value, yValueSum) { return (value / yValueSum) * this.mMaxAngle; } getIndexForAngle(angle) { // take the current angle of the chart into consideration const a = Utils.getNormalizedAngle(angle - this.rotationAngle); for (let i = 0; i < this.mAbsoluteAngles.length; i++) { if (this.mAbsoluteAngles[i] > a) { return i; } } return -1; // return -1 if no index found } /** * Returns the index of the DataSet this x-index belongs to. * * @param xIndex * @return */ getDataSetIndexForIndex(xIndex) { const dataSets = this.mData.dataSets; for (let i = 0; i < dataSets.length; i++) { if (dataSets[i].getEntryForXValue(xIndex, NaN)) { return i; } } return -1; } /** * returns an integer array of all the different angles the chart slices * have the angles in the returned array determine how much space (of 360°) * each slice takes */ get drawAngles() { return this.mDrawAngles; } /** * returns the absolute angles of the different chart slices (where the * slices end) */ get absoluteAngles() { return this.mAbsoluteAngles; } /** * Sets the color for the hole that is drawn in the center of the PieChart * (if enabled). * * @param color */ set holeColor(color) { this.renderer.holePaint.setColor(color); } get holeColor() { return this.renderer.holePaint.color; } get requiredLegendOffset() { return this.legendRenderer.labelPaint.getTextSize() * 2; } get requiredBaseOffset() { return 0; } get radius() { if (!this.circleBox) { return 0; } else { return Math.min(this.circleBox.width() / 2, this.circleBox.height() / 2); } } /** * Returns the center of the circlebox */ get centerCircleBox() { return { x: this.circleBox.centerX(), y: this.circleBox.centerY() }; } /** * Sets the typeface for the center-text paint * * @param t */ set centerTextTypeface(t) { this.renderer.centerTextPaint.setTypeface(t); } get centerTextTypeface() { return this.renderer.centerTextPaint.font; } /** * Sets the size of the center text of the PieChart in dp. * * @param sizeDp */ set centerTextSize(sizeDp) { this.renderer.centerTextPaint.setTextSize(sizeDp); } get centerTextSize() { return this.renderer.centerTextPaint.textSize; } /** * Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0 * * @param x * @param y */ set centerTextOffset({ x, y }) { this.mCenterTextOffset.x = x; this.mCenterTextOffset.y = y; } /** * Returns the offset on the x- and y-axis the center text has in dp. */ get centerTextOffset() { return { x: this.mCenterTextOffset.x, y: this.mCenterTextOffset.y }; } /** * Sets the color of the center text of the PieChart. * * @param color */ set centerTextColor(color) { this.renderer.centerTextPaint.setColor(color); } get centerTextColor() { return this.renderer.centerTextPaint.color; } /** * sets the radius of the hole in the center of the piechart in percent of * the maximum radius (max = the radius of the whole chart), default 50% * * @param percent */ set holeRadius(percent) { this.mHoleRadiusPercent = percent; } /** * Returns the size of the hole radius in percent of the total radius. */ get holeRadius() { return this.mHoleRadiusPercent; } /** * Sets the color the transparent-circle should have. * * @param color */ set transparentCircleColor(color) { const p = this.renderer.transparentCirclePaint; const alpha = p.getAlpha(); p.setColor(color); p.setAlpha(alpha); } /** * Sets the amount of transparency the transparent circle should have 0 = fully transparent, * 255 = fully opaque. * Default value is 100. * * @param alpha 0-255 */ set transparentCircleAlpha(alpha) { this.renderer.transparentCirclePaint.setAlpha(alpha); } /** * Sets the color the entry labels are drawn with. * * @param color */ set entryLabelColor(color) { this.renderer.entryLabelsPaint.setColor(color); } /** * Sets a custom font for the drawing of the entry labels. * * @param tf */ set entryLabelTypeface(tf) { this.renderer.entryLabelsPaint.setTypeface(tf); } /** * Sets the size of the entry labels in dp. Default: 13dp * * @param size */ set entryLabelTextSize(size) { this.renderer.entryLabelsPaint.setTextSize(size); } get maxAngle() { return this.mMaxAngle; } /** * Sets the max angle that is used for calculating the pie-circle. 360 means * it's a full PieChart, 180 results in a half-pie-chart. Default: 360 * * @param maxangle min 90, max 360 */ set maxAngle(maxangle) { if (maxangle > 360) { maxangle = 360; } if (maxangle < 90) { maxangle = 90; } this.mMaxAngle = maxangle; } /** * The minimum angle slices on the chart are rendered with, default is 0. * * @return minimum angle for slices */ get minAngleForSlices() { return this.mMinAngleForSlices; } /** * Set the angle to set minimum size for slices, you must call {@link #notifyDataSetChanged()} * and {@link #invalidate()} when changing this, only works if there is enough room for all * slices to have the minimum angle. * * @param minAngle minimum 0, maximum is half of {@link #setMaxAngle} */ set minAngleForSlices(minAngle) { if (minAngle > this.mMaxAngle / 2) { minAngle = this.mMaxAngle / 2; } else if (minAngle < 0) { minAngle = 0; } this.mMinAngleForSlices = minAngle; } _onDetachedFromWindow() { // releases the bitmap in the renderer to avoid oom error if (this.renderer instanceof PieChartRenderer) { this.renderer.releaseBitmap(); } //super.onDetachedFromWindow(); } } //# sourceMappingURL=PieChart.js.map