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.

307 lines 16.5 kB
import { Align } from '@nativescript-community/ui-canvas'; import { HorizontalBarBuffer } from '../buffer/HorizontalBarBuffer'; import { Utils } from '../utils/Utils'; import { BarChartRenderer } from './BarChartRenderer'; export class HorizontalBarChartRenderer extends BarChartRenderer { get valuePaint() { if (!this.mValuePaint) { this.mValuePaint = Utils.getTemplatePaint('value'); this.mValuePaint.setTextAlign(Align.LEFT); } return this.mValuePaint; } initBuffers() { const barData = this.mChart.barData; this.mBarBuffers = []; for (let i = 0; i < barData.dataSetCount; i++) { const set = barData.getDataSetByIndex(i); this.mBarBuffers.push(new HorizontalBarBuffer(set.entryCount * 4 * (set.stacked ? set.stackSize : 1), barData.dataSetCount, set.stacked)); } } drawDataSet(c, dataSet, index) { const trans = this.mChart.getTransformer(dataSet.axisDependency); const drawBorder = dataSet.barBorderWidth > 0; let borderPaint; if (drawBorder) { borderPaint = this.barBorderPaint; borderPaint.setColor(dataSet.barBorderColor()); borderPaint.setStrokeWidth(dataSet.barBorderWidth); } const phaseX = this.animator.phaseX; const phaseY = this.animator.phaseY; // draw the bar shadow before the values if (this.mChart.drawBarShadowEnabled) { const paint = this.shadowPaint; paint.setColor(dataSet.barShadowColor); const barData = this.mChart.barData; const barWidth = barData.barWidth; const barWidthHalf = barWidth / 2; let x; const barShadowRectBuffer = Utils.getTempRectF(); for (let i = 0, count = Math.min(Math.ceil(dataSet.entryCount * phaseX), dataSet.entryCount); i < count; i++) { const e = dataSet.getEntryForIndex(i); x = dataSet.getEntryXValue(e, i); barShadowRectBuffer.top = x - barWidthHalf; barShadowRectBuffer.bottom = x + barWidthHalf; trans.rectValueToPixel(barShadowRectBuffer); if (!this.mViewPortHandler.isInBoundsTop(barShadowRectBuffer.bottom)) { continue; } if (!this.mViewPortHandler.isInBoundsBottom(barShadowRectBuffer.top)) { break; } barShadowRectBuffer.left = this.mViewPortHandler.contentLeft; barShadowRectBuffer.right = this.mViewPortHandler.contentRight; c.drawRect(barShadowRectBuffer, paint); } } // initialize the buffer const buffer = this.mBarBuffers[index]; buffer.setPhases(phaseX, phaseY); buffer.dataSetIndex = index; buffer.inverted = this.mChart.isInverted(dataSet.axisDependency); buffer.barWidth = this.mChart.barData.barWidth; buffer.yAxisMin = this.mChart.getAxis(dataSet.axisDependency).axisMinimum; buffer.yAxisMax = this.mChart.getAxis(dataSet.axisDependency).axisMaximum; const barsCount = buffer.feed(dataSet); trans.pointValuesToPixel(buffer.buffer); const isSingleColor = !dataSet.colors || dataSet.colors.length === 1; const renderPaint = this.renderPaint; const previousShader = renderPaint.getShader(); const shader = dataSet.fillShader; if (shader) { renderPaint.setShader(shader); } if (isSingleColor) { renderPaint.setColor(dataSet.color); } const customRender = this.mChart.customRenderer; for (let j = 0, pos = 0; j < barsCount; j += 1, pos++) { const left = buffer.buffer[j * 4]; const top = buffer.buffer[j * 4 + 1]; const right = buffer.buffer[j * 4 + 2]; const bottom = buffer.buffer[j * 4 + 3]; if (!this.mViewPortHandler.isInBoundsBottom(top) || (left === right && top === bottom)) { continue; } if (!this.mViewPortHandler.isInBoundsTop(bottom)) { break; } if (!isSingleColor) { // Set the color for the currently drawn value. If the index // is out of bounds, reuse colors. renderPaint.setColor(dataSet.getColor(j)); } if (customRender && customRender.drawBar) { const e = buffer.entries[j]; customRender.drawBar(c, e, dataSet, left, top, right, bottom, renderPaint); } else { c.drawRect(left, top, right, bottom, renderPaint); if (drawBorder) { c.drawRect(left, top, right, bottom, borderPaint); } } } renderPaint.setShader(previousShader); return true; } drawValues(c) { const chart = this.mChart; const data = chart.data; const dataSets = data.dataSets; if (!this.isDrawingValuesAllowed(chart) || dataSets.some((d) => d.drawValuesEnabled || d.drawIconsEnabled) === false) { return; } // if values are drawn const valueOffsetPlus = 5; let posOffset = 0; let negOffset = 0; const drawValueAboveBar = chart.drawValueAboveBarEnabled; const paint = this.valuePaint; const customRender = chart.customRenderer; for (let i = 0; i < chart.barData.dataSetCount; i++) { const dataSet = dataSets[i]; if (!this.shouldDrawValues(dataSet)) { continue; } const yKey = dataSet.yProperty; const isInverted = chart.isInverted(dataSet.axisDependency); // apply the text-styling defined by the DataSet this.applyValueTextStyle(dataSet); const halfTextHeight = Utils.calcTextHeight(paint, '10') / 2; const formatter = dataSet.valueFormatter; // get the buffer const buffer = this.mBarBuffers[i]; const phaseY = this.animator.phaseY; const iconsOffset = dataSet.iconsOffset; const valuesOffset = dataSet.valuesOffset; const isDrawValuesEnabled = dataSet.drawValuesEnabled; const isDrawIconsEnabled = dataSet.drawIconsEnabled; // if only single values are drawn (sum) if (!dataSet.stacked) { for (let j = 0; j < buffer.length * this.animator.phaseX; j += 4) { const y = (buffer.buffer[j + 1] + buffer.buffer[j + 3]) / 2; if (!this.mViewPortHandler.isInBoundsTop(buffer.buffer[j + 1])) { break; } const index = j / 4; const entry = dataSet.getEntryForIndex(index); const val = entry[yKey]; if (!this.mViewPortHandler.isInBoundsX(buffer.buffer[j + (val >= 0 ? 0 : 2)])) { continue; } if (!this.mViewPortHandler.isInBoundsBottom(buffer.buffer[j + 1])) { continue; } const formattedValue = (formatter.getBarLabel || formatter.getFormattedValue).call(formatter, val, entry); // calculate the correct offset depending on the draw position of the value const valueTextWidth = Utils.calcTextWidth(paint, formattedValue); posOffset = drawValueAboveBar ? valueOffsetPlus + valuesOffset.x : -(valueTextWidth + valueOffsetPlus + valuesOffset.x); negOffset = drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus + valuesOffset.x) : valueOffsetPlus + valuesOffset.x; if (isInverted) { posOffset = -posOffset - valueTextWidth; negOffset = -negOffset - valueTextWidth; } if (isDrawValuesEnabled) { this.drawValue(c, chart, dataSet, i, entry, index, formattedValue, val >= 0 ? buffer.buffer[j + 2] + posOffset : buffer.buffer[j + 0] + negOffset, y + valuesOffset.y + halfTextHeight, dataSet.getValueTextColor(j / 2), paint, customRender); } if (dataSet.drawIconsEnabled) { const icon = entry.icon; let px = val >= 0 ? buffer.buffer[j + 2] + posOffset : buffer.buffer[j + 0] + negOffset; let py = y; px += iconsOffset.x; py += iconsOffset.y; this.drawIcon(c, chart, dataSet, i, entry, index, dataSet.getEntryIcon(entry), px, py, customRender); } } // if each value of a potential stack should be drawn } else { const trans = chart.getTransformer(dataSet.axisDependency); let bufferIndex = 0; let index = 0; while (index < dataSet.entryCount * this.animator.phaseX) { const entry = dataSet.getEntryForIndex(index); const vals = entry.yVals; const color = dataSet.getValueTextColor(index); // we still draw stacked bars, but there is one // non-stacked // in between if (!vals) { if (!this.mViewPortHandler.isInBoundsTop(buffer.buffer[bufferIndex + 1])) { break; } if (!this.mViewPortHandler.isInBoundsX(buffer.buffer[bufferIndex + (entry[yKey] >= 0 ? 0 : 2)])) { continue; } if (!this.mViewPortHandler.isInBoundsBottom(buffer.buffer[bufferIndex + 1])) { continue; } const formattedValue = (formatter.getBarLabel || formatter.getFormattedValue).call(formatter, entry[yKey], entry); // calculate the correct offset depending on the draw position of the value const valueTextWidth = Utils.calcTextWidth(paint, formattedValue); posOffset = drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus); negOffset = drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus; if (isInverted) { posOffset = -posOffset - valueTextWidth; negOffset = -negOffset - valueTextWidth; } if (isDrawValuesEnabled) { this.drawValue(c, chart, dataSet, i, entry, index, formattedValue, entry[yKey] >= 0 ? buffer.buffer[bufferIndex + 2] + posOffset : buffer.buffer[bufferIndex + 0] + negOffset, buffer.buffer[bufferIndex + 1] + halfTextHeight + valuesOffset.y, color, paint, customRender); } if (isDrawIconsEnabled) { const icon = entry.icon; let px = entry[yKey] >= 0 ? buffer.buffer[bufferIndex + 2] + posOffset : buffer.buffer[bufferIndex + 0] + negOffset; let py = buffer.buffer[bufferIndex + 1]; px += iconsOffset.x; py += iconsOffset.y; this.drawIcon(c, chart, dataSet, i, entry, index, dataSet.getEntryIcon(entry), px, py, customRender); } // draw stack values } else { if (!this.mTransformedBuffer || this.mTransformedBuffer.length !== vals.length * 2) { this.mTransformedBuffer = Utils.createArrayBuffer(vals.length * 2); } const transformed = this.mTransformedBuffer; let posY = 0; let negY = -entry.negativeSum; for (let k = 0, idx = 0; k < transformed.length; k += 2, idx++) { const value = vals[idx]; let y; if (value === 0 && (posY === 0 || negY === 0)) { // Take care of the situation of a 0.0 value, which overlaps a non-zero bar y = value; } else if (value >= 0) { posY += value; y = posY; } else { y = negY; negY -= value; } transformed[k] = y * phaseY; } const points = Utils.pointsFromBuffer(transformed); trans.pointValuesToPixel(transformed); for (let k = 0; k < points.length; k += 2) { const val = vals[k / 2]; const formattedValue = (formatter.getBarStackedLabel || formatter.getFormattedValue).call(formatter, val, entry); // calculate the correct offset depending on the draw position of the value const valueTextWidth = Utils.calcTextWidth(paint, formattedValue); posOffset = drawValueAboveBar ? valueOffsetPlus + valuesOffset.x : -(valueTextWidth + valueOffsetPlus + valuesOffset.x); negOffset = drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus + valuesOffset.x) : valueOffsetPlus + valuesOffset.x; if (isInverted) { posOffset = -posOffset - valueTextWidth; negOffset = -negOffset - valueTextWidth; } const drawBelow = (val === 0 && negY === 0 && posY > 0) || val < 0; const x = points[k] + (drawBelow ? negOffset : posOffset); const y = (buffer.buffer[bufferIndex + 1] + buffer.buffer[bufferIndex + 3]) / 2; if (!this.mViewPortHandler.isInBoundsTop(y)) { break; } if (!this.mViewPortHandler.isInBoundsX(x)) { continue; } if (!this.mViewPortHandler.isInBoundsBottom(y)) { continue; } if (isDrawValuesEnabled) { this.drawValue(c, chart, dataSet, i, entry, index, formattedValue, x, y + halfTextHeight + valuesOffset.y, color, paint, customRender); } if (isDrawIconsEnabled) { this.drawIcon(c, chart, dataSet, i, entry, index, dataSet.getEntryIcon(entry), x + iconsOffset.x, y + iconsOffset.y, customRender); } } } bufferIndex = !vals ? bufferIndex + 4 : bufferIndex + 4 * vals.length; index++; } } } } prepareBarHighlight(x, y1, y2, barWidthHalf, trans, barRect) { const top = x - barWidthHalf; const bottom = x + barWidthHalf; const left = y1; const right = y2; barRect.set(left, top, right, bottom); trans.rectToPixelPhaseHorizontal(barRect, this.animator.phaseY); } /** * Sets the drawing position of the highlight object based on the riven bar-rect. * @param high * @param bar */ setHighlightDrawPos(high, bar) { high.drawX = bar.centerY(); high.drawY = bar.right; } isDrawingValuesAllowed(chart) { return chart.data.entryCount < chart.maxVisibleValueCount * this.mViewPortHandler.getScaleY(); } } //# sourceMappingURL=HorizontalBarChartRenderer.js.map