@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.
745 lines • 32 kB
JavaScript
import { Canvas, Direction, FillType, LinearGradient, Path, Style, TileMode, releaseImage } from '@nativescript-community/ui-canvas';
import { Screen } from '@nativescript/core';
import { Rounding } from '../data/DataSet';
import { Mode } from '../data/LineDataSet';
import { ColorTemplate } from '../utils/ColorTemplate';
import { Utils } from '../utils/Utils';
import { LineRadarRenderer } from './LineRadarRenderer';
function distanceBetweenPoints(pt1, pt2) {
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
}
function almostEquals(x, y, epsilon) {
return Math.abs(x - y) < epsilon;
}
function _isPointInArea(point, area, margin = 0.5) {
// margin - default is to match rounded decimals
return point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
}
export function splineCurve(firstPoint, middlePoint, afterPoint, tension) {
// Props to Rob Spencer at scaled innovation for his post on splining between points
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
// This function must also respect "skipped" points
const previous = firstPoint;
const current = middlePoint;
const next = afterPoint;
const d01 = distanceBetweenPoints(current, previous);
const d12 = distanceBetweenPoints(next, current);
let s01 = d01 / (d01 + d12);
let s12 = d12 / (d01 + d12);
// If all points are the same, s01 & s02 will be inf
s01 = isNaN(s01) ? 0 : s01;
s12 = isNaN(s12) ? 0 : s12;
const fa = tension * s01; // scaling factor for triangle Ta
const fb = tension * s12;
return {
previous: {
x: current.x - fa * (next.x - previous.x),
y: current.y - fa * (next.y - previous.y)
},
next: {
x: current.x + fb * (next.x - previous.x),
y: current.y + fb * (next.y - previous.y)
}
};
}
function getXYValue(dataSet, index) {
const yKey = dataSet.yProperty;
const entry = dataSet.getEntryForIndex(index);
const yVal = entry[yKey];
if (yVal === undefined || yVal === null) {
return null;
}
return { x: dataSet.getEntryXValue(entry, index), y: entry[yKey] };
}
// fix drawing "too" thin paths on iOS
export class DataSetImageCache {
/**
* Sets up the cache, returns true if a change of cache was required.
*
* @param set
* @return
*/
init(set) {
const size = set.circleColors.length || 1;
let changeRequired = false;
if (!this.circleBitmaps) {
this.circleBitmaps = [];
changeRequired = true;
}
else if (this.circleBitmaps.length !== size) {
this.circleBitmaps = [];
changeRequired = true;
}
return changeRequired;
}
/**
* Fills the cache with bitmaps for the given dataset.
*
* @param set
* @param drawCircleHole
* @param drawTransparentCircleHole
*/
fill(set, renderPaint, circlePaintInner, drawCircleHole, drawTransparentCircleHole) {
const colorCount = set.circleColors.length || 1;
const circleRadius = set.circleRadius;
const circleHoleRadius = set.circleHoleRadius;
const scale = set.circleHighRes ? Screen.mainScreen.scale : 1;
for (let i = 0; i < colorCount; i++) {
const canvas = new Canvas(Math.round(circleRadius * 2 * scale), Math.round(circleRadius * 2 * scale));
canvas.scale(scale, scale);
renderPaint.setColor(set.circleColors[i] || set.color);
if (drawTransparentCircleHole) {
const circlePathBuffer = Utils.getTempPath();
// Begin path for circle with hole
circlePathBuffer.reset();
// const oldType = circlePathBuffer.getFillType();
circlePathBuffer.setFillType(FillType.EVEN_ODD);
circlePathBuffer.addCircle(circleRadius, circleRadius, circleRadius, Direction.CW);
// Cut hole in path
circlePathBuffer.addCircle(circleRadius, circleRadius, circleHoleRadius, Direction.CCW);
// Fill in-between
canvas.drawPath(circlePathBuffer, renderPaint);
// circlePathBuffer.setFillType(oldType);
}
else {
canvas.drawCircle(circleRadius, circleRadius, circleRadius, renderPaint);
if (drawCircleHole) {
canvas.drawCircle(circleRadius, circleRadius, circleHoleRadius, circlePaintInner);
}
}
this.circleBitmaps[i] = canvas.getImage();
}
}
/**
* Returns the cached Bitmap at the given index.
*
* @param index
* @return
*/
getBitmap(index) {
return this.circleBitmaps[index % this.circleBitmaps.length];
}
}
export class LineChartRenderer extends LineRadarRenderer {
get fillPath() {
if (!LineChartRenderer.mFillPath) {
LineChartRenderer.mFillPath = new Path();
}
return LineChartRenderer.mFillPath;
}
constructor(chart, animator, viewPortHandler) {
super(animator, viewPortHandler);
/**
* cache for the circle bitmaps of all datasets
*/
this.mImageCaches = new Map();
this.mChart = chart;
// if (__ANDROID__) {
// this.mBitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
// }
}
get circlePaintInner() {
if (!this.mCirclePaintInner) {
this.mCirclePaintInner = Utils.getTemplatePaint('white-fill');
}
return this.mCirclePaintInner;
}
drawData(c) {
const width = this.mViewPortHandler.chartWidth;
const height = this.mViewPortHandler.chartHeight;
let drawBitmap = this.mDrawBitmap?.get();
if (!drawBitmap || drawBitmap.width !== width || drawBitmap.height !== height) {
if (width > 0 && height > 0) {
this.mBitmapCanvas = new Canvas(width, height);
drawBitmap = this.mBitmapCanvas.getImage();
this.mDrawBitmap = new WeakRef(drawBitmap);
}
else
return;
}
else {
this.mBitmapCanvas.clear();
}
const lineData = this.mChart.lineData;
let needsBitmapDrawing = false;
for (const set of lineData.visibleDataSets) {
needsBitmapDrawing = this.drawDataSet(c, set) || needsBitmapDrawing;
}
if (needsBitmapDrawing) {
const renderPaint = this.renderPaint;
c.drawBitmap(drawBitmap, 0, 0, renderPaint);
}
}
drawDataSet(c, dataSet) {
if (dataSet.entryCount < 1)
return false;
const renderPaint = this.renderPaint;
renderPaint.setStrokeWidth(dataSet.lineWidth);
if (dataSet.dashPathEffect) {
renderPaint.setPathEffect(dataSet.dashPathEffect);
}
if (dataSet.shader) {
renderPaint.setShader(dataSet.shader);
}
renderPaint.setColor(dataSet.color);
renderPaint.setStyle(Style.STROKE);
const scaleX = this.mViewPortHandler.getScaleX();
dataSet.applyFiltering(scaleX);
const result = this.draw(c, dataSet);
if (dataSet.dashPathEffect) {
renderPaint.setPathEffect(null);
}
if (dataSet.shader) {
renderPaint.setShader(null);
}
return result;
}
generateHorizontalBezierPath(dataSet, outputPath) {
if (this.mXBounds.range >= 1) {
const pointsPerEntryPair = 6;
const entryCount = dataSet.entryCount;
if (!this.mLineBuffer || this.mLineBuffer.length < Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2) {
this.mLineBuffer = Utils.createArrayBuffer(Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2);
}
const phaseY = this.animator.phaseY;
const yKey = dataSet.yProperty;
let firstIndex = Math.max(0, this.mXBounds.min);
// let firstIndex = this.mXBounds.min + 1;
const lastIndex = this.mXBounds.min + this.mXBounds.range;
let prev = dataSet.getEntryForIndex(firstIndex);
let yVal = prev[yKey];
while (firstIndex < lastIndex && (yVal === undefined || yVal === null)) {
firstIndex++;
prev = dataSet.getEntryForIndex(firstIndex);
yVal = prev[yKey];
}
let prevXVal = dataSet.getEntryXValue(prev, firstIndex);
let cur = prev;
let curXVal = prevXVal;
const float32arr = this.mLineBuffer;
let index = 0;
float32arr[index++] = prevXVal;
float32arr[index++] = cur[yKey] * phaseY;
// let the spline start
for (let j = firstIndex + 1; j <= lastIndex; j++) {
const newEntry = dataSet.getEntryForIndex(j);
const yVal = newEntry[yKey];
if (yVal === undefined || yVal === null) {
continue;
}
prev = cur;
prevXVal = curXVal;
cur = newEntry;
curXVal = dataSet.getEntryXValue(cur, j);
const cpx = prevXVal + (curXVal - prevXVal) / 2.0;
float32arr[index++] = cpx;
float32arr[index++] = prev[yKey] * phaseY;
float32arr[index++] = cpx;
float32arr[index++] = cur[yKey] * phaseY;
float32arr[index++] = curXVal;
float32arr[index++] = cur[yKey] * phaseY;
}
const points = Utils.pointsFromBuffer(float32arr);
if (__ANDROID__ && Utils.supportsDirectArrayBuffers()) {
outputPath['setCubicLinesBuffer'](points, 0, index);
}
else {
outputPath.setCubicLines(points, 0, index);
}
return [points, index];
}
else {
outputPath.reset();
return [];
}
}
generateCubicPath(dataSet, outputPath) {
if (this.mXBounds.range >= 1) {
const pointsPerEntryPair = 6;
const entryCount = dataSet.entryCount;
if (!this.mLineBuffer || this.mLineBuffer.length < Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2) {
this.mLineBuffer = Utils.createArrayBuffer(Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2);
}
const phaseY = this.animator.phaseY;
const xKey = dataSet.xProperty;
const yKey = dataSet.yProperty;
const intensity = dataSet.cubicIntensity;
// Take an extra polet from the left, and an extra from the right.
// That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart.
// So in the starting `prev` and `cur`, go -2, -1
// And in the `lastIndex`, add +1
const firstIndex = Math.max(0, this.mXBounds.min);
// let firstIndex = this.mXBounds.min + 1;
const lastIndex = this.mXBounds.min + this.mXBounds.range;
const float32arr = this.mLineBuffer;
let index = 0;
let nextIndex = -1;
let next;
let controlPoints;
let point;
let prev;
let prevControlPoints;
for (let j = firstIndex; j <= lastIndex; j++) {
point = getXYValue(dataSet, j);
if (!point) {
// if (j === 0) {
// return [];
// }
continue;
}
if (!prev) {
prev = point;
}
nextIndex = j + 1 < dataSet.entryCount ? j + 1 : j;
next = getXYValue(dataSet, nextIndex);
if (!next) {
continue;
}
controlPoints = splineCurve(prev, point, next, intensity);
if (j === firstIndex) {
float32arr[index++] = point.x;
float32arr[index++] = point.y * phaseY;
}
else {
float32arr[index++] = prevControlPoints.next.x;
float32arr[index++] = prevControlPoints.next.y * phaseY;
float32arr[index++] = controlPoints.previous.x;
float32arr[index++] = controlPoints.previous.y * phaseY;
float32arr[index++] = point.x;
float32arr[index++] = point.y * phaseY;
}
prevControlPoints = controlPoints;
prev = point;
}
const points = Utils.pointsFromBuffer(float32arr);
if (__ANDROID__ && Utils.supportsDirectArrayBuffers()) {
outputPath['setCubicLinesBuffer'](points, 0, index);
}
else {
outputPath.setCubicLines(points, 0, index);
}
return [points, index];
}
else {
outputPath.reset();
return [];
}
}
generateLinearPath(dataSet, outputPath) {
if (this.mXBounds.range >= 1) {
const isDrawSteppedEnabled = dataSet.mode === Mode.STEPPED;
const entryCount = dataSet.entryCount;
const pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2;
if (!this.mLineBuffer || this.mLineBuffer.length < Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2) {
this.mLineBuffer = Utils.createArrayBuffer(Math.max(entryCount * pointsPerEntryPair, pointsPerEntryPair) * 2);
}
const phaseX = this.animator.phaseX;
const phaseY = this.animator.phaseY;
const yKey = dataSet.yProperty;
// const filled = outputPath;
// outputPath.reset();
let firstIndex = Math.max(0, this.mXBounds.min);
const lastIndex = this.mXBounds.min + this.mXBounds.range;
let firstEntry = dataSet.getEntryForIndex(firstIndex);
let entryXVal = dataSet.getEntryXValue(firstEntry, firstIndex);
let yVal = firstEntry[yKey];
while (firstIndex < lastIndex && (yVal === undefined || yVal === null)) {
firstIndex++;
firstEntry = dataSet.getEntryForIndex(firstIndex);
entryXVal = dataSet.getEntryXValue(firstEntry, firstIndex);
yVal = firstEntry[yKey];
}
const float32arr = this.mLineBuffer;
float32arr[0] = entryXVal;
float32arr[1] = firstEntry[yKey] * phaseY;
let index = 2;
// create a new path
let currentEntry = null;
let currentEntryXVal;
let currentEntryYVal;
let previousEntryYVal;
// doing the if test outside is much much faster on big data
if (isDrawSteppedEnabled) {
for (let x = firstIndex + 1; x <= lastIndex; x++) {
currentEntry = dataSet.getEntryForIndex(x);
currentEntryXVal = dataSet.getEntryXValue(currentEntry, x);
currentEntryYVal = currentEntry[yKey];
if (currentEntryYVal === undefined || currentEntryYVal === null) {
continue;
}
float32arr[index++] = currentEntryXVal;
float32arr[index++] = previousEntryYVal * phaseY;
float32arr[index++] = currentEntryXVal;
float32arr[index++] = currentEntryYVal * phaseY;
previousEntryYVal = currentEntryYVal;
}
}
else {
for (let x = firstIndex + 1; x <= lastIndex; x++) {
currentEntry = dataSet.getEntryForIndex(x);
currentEntryXVal = dataSet.getEntryXValue(currentEntry, x);
currentEntryYVal = currentEntry[yKey];
if (currentEntryYVal === undefined || currentEntryYVal === null) {
continue;
}
float32arr[index++] = currentEntryXVal;
float32arr[index++] = currentEntryYVal * phaseY;
previousEntryYVal = currentEntryYVal;
}
}
const points = Utils.pointsFromBuffer(float32arr);
if (__ANDROID__ && Utils.supportsDirectArrayBuffers()) {
outputPath['setLinesBuffer'](points, 0, index);
}
else {
outputPath.setLines(points, 0, index);
}
return [points, index];
}
else {
outputPath.reset();
return [];
}
}
getMultiColorsShader(colors, points, trans, dataSet) {
const nbColors = colors.length;
const xKey = dataSet.xProperty;
if (nbColors > 0) {
trans.pointValuesToPixel(points);
const shaderColors = [];
const positions = [];
const firstIndex = Math.max(0, this.mXBounds.min);
const range = this.mXBounds.range;
const lastIndex = firstIndex + range;
const width = this.mViewPortHandler.chartWidth;
const chartRect = this.mViewPortHandler.chartRect;
let lastColor;
const gradientDelta = 0;
const posDelta = gradientDelta / width;
for (let index = 0; index < nbColors; index++) {
const color = colors[index];
let colorIndex = color[xKey || 'index'];
// if filtered we need to get the real index
if (dataSet.filtered) {
dataSet.ignoreFiltered = true;
const entry = dataSet.getEntryForIndex(colorIndex);
dataSet.ignoreFiltered = false;
if (entry) {
colorIndex = dataSet.getEntryIndexForXValue(dataSet.getEntryXValue(entry, colorIndex), NaN, Rounding.CLOSEST);
}
}
if (colorIndex < firstIndex) {
lastColor = color.color;
continue;
}
if (colorIndex > lastIndex) {
if (shaderColors.length === 0) {
shaderColors.push(lastColor);
positions.push(0);
shaderColors.push(lastColor);
positions.push(1);
}
break;
}
const posX = Math.floor(points[(colorIndex - firstIndex) * 2]);
const pos = (posX - chartRect.left) / width;
if (lastColor) {
if (shaderColors.length === 0) {
shaderColors.push(lastColor);
positions.push(0);
}
shaderColors.push(lastColor);
positions.push(pos - posDelta);
}
shaderColors.push(color.color);
positions.push(pos + posDelta);
lastColor = color.color;
}
if (shaderColors.length === 0) {
shaderColors.push(colors[0].color);
positions.push(0);
}
if (shaderColors.length === 1) {
shaderColors.push(colors[0].color);
positions.push(1);
}
return new LinearGradient(0, 0, width, 0, shaderColors, positions, TileMode.CLAMP);
}
return null;
}
draw(c, dataSet) {
const result = false;
const drawFilled = dataSet.drawFilledEnabled;
const drawLine = dataSet.lineWidth > 0;
if (!drawFilled && !drawLine) {
return result;
}
const trans = this.mChart.getTransformer(dataSet.axisDependency);
const linePath = Utils.getTempPath();
this.mXBounds.set(this.mChart, dataSet, this.animator);
let points;
switch (dataSet.mode) {
default:
case Mode.LINEAR:
case Mode.STEPPED:
points = this.generateLinearPath(dataSet, linePath)[0];
break;
case Mode.CUBIC_BEZIER:
points = this.generateCubicPath(dataSet, linePath)[0];
break;
case Mode.HORIZONTAL_BEZIER:
points = this.generateHorizontalBezierPath(dataSet, linePath)[0];
break;
}
if (!points) {
return result;
}
const colors = dataSet.colors || [dataSet.color];
const nbColors = colors.length;
const renderPaint = this.renderPaint;
let paintColorsShader;
if (nbColors > 1) {
// TODO: we transforms points in there. Could be dangerous if used after
paintColorsShader = this.getMultiColorsShader(colors, points, trans, dataSet);
}
let oldShader;
if (drawFilled) {
const useColorsForFill = dataSet.useColorsForFill;
if (paintColorsShader && useColorsForFill) {
oldShader = renderPaint.getShader();
renderPaint.setShader(paintColorsShader);
}
const fillPath = this.fillPath;
fillPath.reset();
fillPath.addPath(linePath);
const minEntryValue = dataSet.getEntryXValue(dataSet.getEntryForIndex(this.mXBounds.min), this.mXBounds.min);
const maxEntryValue = dataSet.getEntryXValue(dataSet.getEntryForIndex(this.mXBounds.min + this.mXBounds.range), this.mXBounds.min + this.mXBounds.range);
this.drawFill(c, dataSet, fillPath, trans, minEntryValue, maxEntryValue);
this.lastLinePath = linePath;
if (paintColorsShader && useColorsForFill) {
renderPaint.setShader(oldShader);
oldShader = null;
}
}
const customRender = this.mChart.customRenderer;
if (drawLine) {
const useColorsForLine = dataSet.useColorsForLine;
if (paintColorsShader && useColorsForLine) {
oldShader = renderPaint.getShader();
renderPaint.setShader(paintColorsShader);
}
trans.pathValueToPixel(linePath);
if (customRender?.drawLine) {
customRender.drawLine(c, linePath, renderPaint);
}
else {
this.drawPath(c, linePath, renderPaint);
}
if (paintColorsShader && useColorsForLine) {
renderPaint.setShader(oldShader);
oldShader = null;
}
}
return result;
}
drawLines(canvas, points, offest, length, paint, matrix) {
if (matrix) {
canvas.drawLines(points, offest, length, paint, matrix);
}
else {
canvas.drawLines(points, offest, length, paint);
}
}
drawFill(c, dataSet, spline, trans, min, max, color, fillMin) {
const fillFormatter = dataSet.fillFormatter;
if (fillFormatter.getFillLinePath) {
fillFormatter.getFillLinePath(dataSet, this.mChart, spline, this.lastLinePath);
}
else if (fillMin === undefined) {
fillMin = fillFormatter.getFillLinePosition(dataSet, this.mChart);
spline.lineTo(max, fillMin);
spline.lineTo(min, fillMin);
spline.close();
}
trans && trans.pathValueToPixel(spline);
const drawable = dataSet.fillDrawable;
if (drawable) {
this.drawFilledPathBitmap(c, spline, drawable, dataSet.fillShader);
}
else {
this.drawFilledPath(c, spline, color || dataSet.fillColor, dataSet.fillAlpha, dataSet.fillShader);
}
}
drawValuesForDataset(c, dataSet, dataSetIndex) {
const yKey = dataSet.yProperty;
// apply the text-styling defined by the DataSet
this.applyValueTextStyle(dataSet);
const chart = this.mChart;
const trans = chart.getTransformer(dataSet.axisDependency);
// make sure the values do not interfear with the circles
let valOffset = dataSet.circleRadius * 1.75;
if (!dataSet.drawCirclesEnabled)
valOffset = valOffset / 2;
this.mXBounds.set(chart, dataSet, this.animator);
const { points, count } = trans.generateTransformedValues(dataSet, this.animator.phaseX, this.animator.phaseY, this.mXBounds.min, this.mXBounds.max);
const formatter = dataSet.valueFormatter;
const iconsOffset = dataSet.iconsOffset;
const valuesOffset = dataSet.valuesOffset;
const drawIcons = dataSet.drawIconsEnabled;
const drawValues = dataSet.drawValuesEnabled;
const length = count;
const paint = this.valuePaint;
const customRender = chart.customRenderer;
for (let j = 0; j < length; j += 2) {
const x = points[j];
const y = points[j + 1];
if (!this.mViewPortHandler.isInBoundsRight(x))
break;
if (!this.mViewPortHandler.isInBoundsLeft(x) || !this.mViewPortHandler.isInBoundsY(y))
continue;
const index = j / 2 + this.mXBounds.min;
const entry = dataSet.getEntryForIndex(index);
if (!entry)
continue;
const yVal = entry[yKey];
if (yVal === undefined || yVal === null) {
continue;
}
if (drawValues) {
this.drawValue(c, chart, dataSet, dataSetIndex, entry, index, formatter.getFormattedValue(yVal, entry), valuesOffset.x + x, valuesOffset.y + y - valOffset, dataSet.getValueTextColor(j / 2), paint, customRender);
}
if (drawIcons) {
this.drawIcon(c, chart, dataSet, dataSetIndex, entry, index, dataSet.getEntryIcon(entry), x + iconsOffset.x, y + iconsOffset.y, customRender);
}
}
}
drawValues(c) {
const data = this.mChart.lineData;
const dataSets = data.dataSets;
if (!this.isDrawingValuesAllowed(this.mChart) || dataSets.some((d) => d.drawValuesEnabled || d.drawIconsEnabled) === false) {
return;
}
for (let i = 0; i < dataSets.length; i++) {
const dataSet = dataSets[i];
if (!this.shouldDrawValues(dataSet) || dataSet.entryCount < 1)
continue;
this.drawValuesForDataset(c, dataSet, i);
}
}
drawExtras(c) {
this.drawCircles(c);
}
drawCirclesForDataset(c, dataSet) {
const paint = this.circlePaintInner;
paint.setColor(dataSet.circleHoleColor);
const phaseY = this.animator.phaseY;
const yKey = dataSet.yProperty;
const trans = this.mChart.getTransformer(dataSet.axisDependency);
this.mXBounds.set(this.mChart, dataSet, this.animator);
const circleRadius = dataSet.circleRadius;
const circleHoleRadius = dataSet.circleHoleRadius;
const drawCircleHole = dataSet.drawCircleHoleEnabled && circleHoleRadius < circleRadius && circleHoleRadius > 0;
const drawTransparentCircleHole = drawCircleHole && dataSet.circleHoleColor === ColorTemplate.COLOR_NONE;
let imageCache;
if (this.mImageCaches.get(dataSet)) {
imageCache = this.mImageCaches.get(dataSet);
}
else {
imageCache = new DataSetImageCache();
this.mImageCaches.set(dataSet, imageCache);
}
const changeRequired = imageCache.init(dataSet);
// only fill the cache with new bitmaps if a change is required
if (changeRequired) {
const renderPaint = this.renderPaint;
imageCache.fill(dataSet, renderPaint, paint, drawCircleHole, drawTransparentCircleHole);
}
const boundsRangeCount = this.mXBounds.range + this.mXBounds.min;
const circleBuffer = Utils.getTempArray(2);
const destRect = Utils.getTempRectF();
for (let j = this.mXBounds.min; j <= boundsRangeCount; j++) {
const e = dataSet.getEntryForIndex(j);
if (!e)
continue;
const yVal = e[yKey];
if (yVal === null || yVal === undefined)
continue;
circleBuffer[0] = dataSet.getEntryXValue(e, j);
circleBuffer[1] = yVal * phaseY;
trans.pointValuesToPixel(circleBuffer);
// native buffer access is slow
const cx = circleBuffer[0];
const cy = circleBuffer[1];
if (!this.mViewPortHandler.isInBoundsRight(cx))
break;
if (!this.mViewPortHandler.isInBoundsLeft(cx) || !this.mViewPortHandler.isInBoundsY(cy))
continue;
const circleBitmap = imageCache.getBitmap(j);
if (circleBitmap) {
destRect.set(cx - circleRadius, cy - circleRadius, cx - circleRadius + 2 * circleRadius, cy - circleRadius + 2 * circleRadius);
c.drawBitmap(circleBitmap, null, destRect, null);
}
}
}
drawCircles(c) {
const dataSets = this.mChart.lineData.visibleDataSets;
if (dataSets.some((d) => d.drawCirclesEnabled) === false) {
return;
}
const renderPaint = this.renderPaint;
renderPaint.setStyle(Style.FILL);
const circleBuffer = Utils.getTempArray(2);
circleBuffer[0] = 0;
circleBuffer[1] = 0;
for (let i = 0; i < dataSets.length; i++) {
const dataSet = dataSets[i];
if (!dataSet.drawCirclesEnabled || dataSet.entryCount === 0)
continue;
this.drawCirclesForDataset(c, dataSet);
}
}
drawHighlighted(c, indices, actualDraw = true) {
const lineData = this.mChart.lineData;
const customRender = this.mChart.customRenderer;
const paint = this.highlightPaint;
for (const high of indices) {
const set = lineData.getDataSetByIndex(high.dataSetIndex);
if (!set || !set.highlightEnabled)
continue;
const { entry, index } = lineData.getEntryAndIndexForHighlight(high);
// let e = set.getEntryForXValue(high.x high.y);
if (!this.isInBoundsX(entry, set))
continue;
const yKey = set.yProperty;
const pix = this.mChart.getTransformer(set.axisDependency).getPixelForValues(set.getEntryXValue(entry, index), entry[yKey] * this.animator.phaseY);
high.drawX = pix.x;
high.drawY = pix.y;
if (!actualDraw) {
continue;
}
if (customRender && customRender.drawHighlight) {
customRender.drawHighlight(c, high, set, paint);
}
else {
this.drawHighlightLines(c, high.drawX, high.drawY, set);
}
}
}
/**
* Releases the drawing bitmap. This should be called when {@link LineChart#onDetachedFromWindow()}.
*/
releaseBitmap() {
if (this.mBitmapCanvas) {
this.mBitmapCanvas.setBitmap(null);
this.mBitmapCanvas = null;
}
if (this.mDrawBitmap) {
const drawBitmap = this.mDrawBitmap.get();
if (drawBitmap) {
releaseImage(drawBitmap);
}
this.mDrawBitmap = null;
}
}
}
//# sourceMappingURL=LineChartRenderer.js.map