@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.
466 lines • 20.3 kB
JavaScript
import { Align } from '@nativescript-community/ui-canvas';
import { LimitLabelPosition } from '../components/LimitLine';
import { XAxisPosition } from '../components/XAxis';
import { Utils } from '../utils/Utils';
import { AxisRenderer } from './AxisRenderer';
export class XAxisRenderer extends AxisRenderer {
constructor(viewPortHandler, xAxis, trans) {
super(viewPortHandler, trans, xAxis);
this.mForceLongestLabelComputation = true;
this.xAxis = xAxis;
}
createAxisLabelsPaint() {
const paint = super.createAxisLabelsPaint();
paint.setTextAlign(Align.CENTER);
return paint;
}
getCurrentMinMax(min, max, inverted) {
if (min === undefined || max === undefined || inverted === undefined) {
const axis = this.mAxis;
if (min === undefined) {
min = axis.axisMinimum;
}
if (max === undefined) {
max = axis.axisMaximum;
}
if (inverted === undefined) {
inverted = axis['isInverted'] ? axis['isInverted']() : false;
}
}
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
if (rect.width() > 10 && !this.mViewPortHandler.isFullyZoomedOutX()) {
const p1 = this.transformer.getValuesByTouchPoint(rect.left, rect.top);
const p2 = this.transformer.getValuesByTouchPoint(rect.right, rect.top);
if (inverted) {
min = p2.x;
max = p1.x;
}
else {
min = p1.x;
max = p2.x;
}
}
return { min, max };
}
computeAxisValues(min, max) {
super.computeAxisValues(min, max);
this.computeSize();
}
computeSize() {
const axis = this.xAxis;
const rotation = axis.labelRotationAngle;
if (this.mForceLongestLabelComputation || rotation % 360 !== 0) {
const paint = this.axisLabelsPaint;
paint.setFont(axis.typeface);
const longest = axis.longestLabel;
const labelSize = Utils.calcTextSize(paint, longest);
const labelWidth = labelSize.width;
const labelHeight = Utils.calcTextHeight(paint, 'Q') + 2;
const labelRotatedSize = Utils.getSizeOfRotatedRectangleByDegrees(labelWidth, labelHeight, axis.labelRotationAngle);
axis.mLabelWidth = Math.round(labelWidth);
axis.mLabelHeight = Math.round(labelHeight);
axis.mLabelRotatedWidth = Math.round(labelRotatedSize.width);
axis.mLabelRotatedHeight = Math.round(labelRotatedSize.height);
}
else {
axis.mLabelWidth = 1;
axis.mLabelHeight = 1;
axis.mLabelRotatedWidth = 1;
axis.mLabelRotatedHeight = 1;
}
}
renderAxisLabels(c) {
const axis = this.xAxis;
if (!axis.enabled || !axis.drawLabels)
return;
const yoffset = axis.yOffset;
const paint = this.axisLabelsPaint;
// we cant remove that line right now of the ascent wont be computed...
// TODO: refactor this
const labelLineHeight = Utils.getLineHeight(paint);
paint.setFont(axis.typeface);
paint.setTextAlign(axis.labelTextAlign);
paint.setColor(axis.textColor);
// const align = this.mAxisLabelPaint.getTextAlign();
// this.mAxisLabelPaint.setTextAlign(Align.CENTER);
// TODO: fix this the right way.
// for now Utils.drawXAxisValue needs the font ascent
// but it is not calculated all the time (in the lightest of cases)
// we call this next line to ensure it is
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
const pointF = { x: 0, y: 0 };
if (axis.position === XAxisPosition.TOP) {
pointF.x = 0.5;
pointF.y = 1.0;
this.drawLabels(c, rect.top - yoffset, pointF);
this.drawMarkTick(c, rect.top, -yoffset / 2);
}
else if (axis.position === XAxisPosition.TOP_INSIDE) {
pointF.x = 0.5;
pointF.y = 1.0;
this.drawLabels(c, rect.top + yoffset + axis.mLabelRotatedHeight, pointF);
this.drawMarkTick(c, rect.top, -yoffset / 2);
}
else if (axis.position === XAxisPosition.BOTTOM) {
pointF.x = 0.5;
pointF.y = 0.0;
this.drawLabels(c, rect.bottom + yoffset, pointF);
this.drawMarkTick(c, rect.bottom, +yoffset / 2);
}
else if (axis.position === XAxisPosition.BOTTOM_INSIDE) {
pointF.x = 0.5;
pointF.y = 0.0;
this.drawLabels(c, rect.bottom - yoffset - axis.mLabelRotatedHeight, pointF);
this.drawMarkTick(c, rect.bottom, +yoffset / 2);
}
else {
// BOTH SIDED
pointF.x = 0.5;
pointF.y = 1.0;
this.drawLabels(c, rect.top - yoffset, pointF);
this.drawMarkTick(c, rect.top, -yoffset / 2);
pointF.x = 0.5;
pointF.y = 0.0;
this.drawLabels(c, rect.bottom + yoffset, pointF);
this.drawMarkTick(c, rect.bottom, +yoffset / 2);
}
}
renderAxisLine(c) {
const axis = this.xAxis;
if (!axis.drawAxisLine || !axis.enabled || axis.axisLineWidth === 0 || axis.mEntryCount === 0)
return;
const paint = this.axisLinePaint;
paint.setColor(axis.axisLineColor);
paint.setStrokeWidth(axis.axisLineWidth);
paint.setPathEffect(axis.axisLineDashPathEffect);
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
if (axis.position === XAxisPosition.TOP || axis.position === XAxisPosition.TOP_INSIDE || axis.position === XAxisPosition.BOTH_SIDED) {
c.drawLine(rect.left, rect.top, rect.right, rect.top, paint);
}
if (axis.position === XAxisPosition.BOTTOM || axis.position === XAxisPosition.BOTTOM_INSIDE || axis.position === XAxisPosition.BOTH_SIDED) {
c.drawLine(rect.left, rect.bottom, rect.right, rect.bottom, paint);
}
}
/**
* draws the x-labels on the specified y-position
*
* @param pos
*/
drawLabels(c, pos, anchor) {
const axis = this.xAxis;
const labelRotationAngleDegrees = axis.labelRotationAngle;
const centeringEnabled = axis.centerAxisLabels;
const entryCount = axis.mEntryCount;
if (entryCount <= 0) {
return;
}
const customRender = axis.customRenderer;
const customRenderFunction = customRender && customRender.drawLabel;
const length = entryCount * 2;
if (!this.mLabelsPositionsBuffer || this.mLabelsPositionsBuffer.length !== length) {
this.mLabelsPositionsBuffer = Utils.createArrayBuffer(length);
}
const positionsBuffer = this.mLabelsPositionsBuffer;
for (let i = 0; i < length; i += 2) {
// only fill x values
if (centeringEnabled) {
positionsBuffer[i] = axis.mCenteredEntries[i / 2];
}
else {
positionsBuffer[i] = axis.mEntries[i / 2];
}
if (i + 1 < length) {
positionsBuffer[i + 1] = 0;
}
}
const positions = Utils.pointsFromBuffer(positionsBuffer);
this.transformer.pointValuesToPixel(positions);
const chartWidth = this.mViewPortHandler.chartWidth;
let offsetRight = 0;
let offsetLeft = 0;
if (this.mAxis.ignoreOffsets) {
}
else {
offsetRight = this.mViewPortHandler.offsetRight;
offsetLeft = this.mViewPortHandler.offsetLeft;
}
const labels = axis.mLabels;
const paint = this.axisLabelsPaint;
for (let i = 0; i < length; i += 2) {
let x = positions[i];
if (this.mViewPortHandler.isInBoundsX(x)) {
const label = labels[i / 2];
if (!label) {
continue;
}
if (axis.avoidFirstLastClipping || axis.ensureVisible) {
// avoid clipping of the last
if (i / 2 === entryCount - 1 && entryCount > 1) {
const width = Utils.calcTextWidth(paint, label);
if (paint.getTextAlign() === Align.CENTER && width / 2 > offsetRight && x + width / 2 > chartWidth) {
x += chartWidth - x - width / 2;
}
else if (paint.getTextAlign() === Align.LEFT && width > offsetRight && x + width > chartWidth) {
x += chartWidth - x - width;
}
// avoid clipping of the first
}
else if (i === 0) {
const width = Utils.calcTextWidth(paint, label);
if (paint.getTextAlign() === Align.CENTER && width / 2 > offsetLeft && x < width / 2) {
x += x - width / 2;
}
else if (paint.getTextAlign() === Align.RIGHT && width > offsetLeft && x < width) {
x += x - width;
}
}
}
this.drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees, paint, customRenderFunction);
}
}
}
drawLabel(c, formattedLabel, x, y, anchor, angleDegrees, paint, customRenderFunction) {
if (customRenderFunction) {
customRenderFunction(c, this.xAxis, formattedLabel, x, y, paint, anchor, angleDegrees);
}
else {
Utils.drawXAxisValue(c, formattedLabel, x, y, paint, anchor, angleDegrees);
}
}
/**
* Draw the mark tickets on the specified y-position
* @param c
* @param pos
* @param length
*/
drawMarkTick(c, pos, ticklength) {
const axis = this.xAxis;
if (!axis.drawMarkTicks)
return;
const customRender = axis.customRenderer;
const customRenderFunction = customRender && customRender.drawMarkTick;
const length = this.mAxis.mEntryCount * 2;
if (!this.mRenderGridLinesBuffer || this.mRenderGridLinesBuffer.length !== length) {
this.mRenderGridLinesBuffer = Utils.createArrayBuffer(length);
}
const positionsBuffer = this.mRenderGridLinesBuffer;
for (let i = 0; i < length; i += 2) {
positionsBuffer[i] = axis.mEntries[i / 2];
if (i + 1 < length) {
positionsBuffer[i + 1] = 0;
}
}
const points = Utils.pointsFromBuffer(positionsBuffer);
this.transformer.pointValuesToPixel(points);
const paint = this.axisLinePaint;
for (let i = 0; i < length; i += 2) {
const x = points[i];
if (customRenderFunction) {
customRenderFunction(c, this, x, pos, x, pos + ticklength, paint);
}
else {
c.drawLine(x, pos, x, pos + ticklength, paint);
}
}
}
renderGridLines(c) {
const axis = this.xAxis;
if (!axis.drawGridLines || !axis.enabled)
return;
const clipRestoreCount = c.save();
c.clipRect(this.getGridClippingRect());
const length = this.mAxis.mEntryCount * 2;
if (!this.mRenderGridLinesBuffer || this.mRenderGridLinesBuffer.length !== length) {
this.mRenderGridLinesBuffer = Utils.createArrayBuffer(length);
}
const positionsBuffer = this.mRenderGridLinesBuffer;
for (let i = 0; i < length; i += 2) {
positionsBuffer[i] = axis.mEntries[i / 2];
positionsBuffer[i + 1] = axis.mEntries[i / 2];
}
const positions = Utils.pointsFromBuffer(positionsBuffer);
this.transformer.pointValuesToPixel(positions);
const paint = this.gridPaint;
paint.setColor(this.xAxis.gridColor);
paint.setStrokeWidth(this.xAxis.gridLineWidth);
paint.setPathEffect(this.xAxis.gridDashPathEffect);
// const gridLinePath = this.mRenderGridLinesPath;
// gridLinePath.reset();
const customRender = axis.customRenderer;
const customRenderFunction = customRender && customRender.drawGridLine;
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
for (let i = 0; i < positions.length; i += 2) {
this.drawGridLine(c, rect, positions[i], positions[i + 1], axis.mEntries[i / 2], paint, customRenderFunction);
}
c.restoreToCount(clipRestoreCount);
}
getGridClippingRect() {
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
const gridClippingRect = Utils.getTempRectF();
gridClippingRect.set(rect);
gridClippingRect.inset(-this.mAxis.gridLineWidth, 0);
return gridClippingRect;
}
/**
* Draws the grid line at the specified position using the provided path.
*
* @param c
* @param rect
* @param x
* @param y
* @param axisValue
*/
drawGridLine(c, rect, x, y, axisValue, paint, customRendererFunc) {
if (customRendererFunc) {
customRendererFunc(c, this.mAxis, rect, x, y, axisValue, paint);
}
else {
c.drawLine(x, rect.bottom, x, rect.top, paint);
}
}
/**
* Draws the LimitLines associated with this axis to the screen.
*
* @param c
*/
renderLimitLines(c) {
const axis = this.xAxis;
if (!axis.drawLimitLines || !axis.enabled) {
return;
}
const limitLines = axis.limitLines;
if (!limitLines || limitLines.length <= 0)
return;
const position = Utils.getTempArray(2);
position[0] = 0;
position[1] = 0;
const rect = axis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
const clipToContent = axis.clipLimitLinesToContent;
const customRender = axis.customRenderer;
const customRenderFunction = customRender && customRender.drawLimitLine;
for (let i = 0; i < limitLines.length; i++) {
const l = limitLines[i];
if (!l.enabled)
continue;
const lineWidth = l.lineWidth;
if (clipToContent) {
c.save();
const clipRect = Utils.getTempRectF();
clipRect.set(rect);
clipRect.inset(0, -lineWidth);
c.clipRect(clipRect);
}
position[0] = l.limit;
position[1] = 0;
this.transformer.pointValuesToPixel(position);
if (lineWidth > 0) {
this.renderLimitLineLine(c, l, rect, position[0], customRenderFunction);
}
this.renderLimitLineLabel(c, l, position, l.yOffset);
if (clipToContent) {
c.restore();
}
}
}
renderLimitLineLine(c, limitLine, rect, x, customRendererFunc) {
const paint = this.limitLinePaint;
paint.setColor(limitLine.lineColor);
paint.setStrokeWidth(limitLine.lineWidth);
paint.setPathEffect(limitLine.dashPathEffect);
if (customRendererFunc) {
customRendererFunc(c, this.mAxis, limitLine, rect, x, paint);
}
else {
c.drawLine(x, rect.bottom, x, rect.top, paint);
}
}
drawLimitLineLabel(c, limitLine, label, x, y, paint) {
if (typeof limitLine['drawLabel'] === 'function') {
limitLine.drawLabel(c, label, x, y, paint);
}
else {
c.drawText(label, x, y, paint);
}
}
renderLimitLineLabel(c, limitLine, position, yOffset) {
const label = limitLine.label;
// if drawing the limit-value label is enabled
if (label && label !== '') {
const rect = this.mAxis.ignoreOffsets ? this.mViewPortHandler.chartRect : this.mViewPortHandler.contentRect;
const paint = this.limitLinePaint;
paint.setFont(limitLine.typeface);
paint.setStyle(limitLine.textStyle);
paint.setPathEffect(null);
paint.setColor(limitLine.textColor);
paint.setStrokeWidth(0.5);
const xOffset = limitLine.lineWidth + limitLine.xOffset;
const labelPosition = limitLine.labelPosition;
const needsSize = limitLine.ensureVisible || labelPosition === LimitLabelPosition.CENTER_TOP || labelPosition === LimitLabelPosition.RIGHT_TOP || labelPosition === LimitLabelPosition.LEFT_TOP;
let size;
if (needsSize) {
size = Utils.calcTextSize(paint, label);
}
switch (labelPosition) {
case LimitLabelPosition.CENTER_TOP: {
paint.setTextAlign(Align.CENTER);
let x = position[0];
if (limitLine.ensureVisible) {
x = Math.max(size.width / 2, Math.min(rect.right - size.width / 2, x));
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.top + yOffset + size.height, paint);
break;
}
case LimitLabelPosition.CENTER_BOTTOM: {
paint.setTextAlign(Align.CENTER);
let x = position[0];
if (limitLine.ensureVisible) {
x = Math.max(size.width / 2, Math.min(rect.right - size.width / 2, x));
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.bottom - yOffset, paint);
break;
}
case LimitLabelPosition.RIGHT_TOP: {
paint.setTextAlign(Align.LEFT);
let x = position[0] + xOffset;
if (limitLine.ensureVisible && x > rect.right - size.width) {
x = position[0] - xOffset;
paint.setTextAlign(Align.RIGHT);
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.top + yOffset + size.height, paint);
break;
}
case LimitLabelPosition.RIGHT_BOTTOM: {
paint.setTextAlign(Align.LEFT);
let x = position[0] + xOffset;
if (limitLine.ensureVisible && x > rect.right - size.width) {
x = position[0] - xOffset;
paint.setTextAlign(Align.RIGHT);
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.bottom - yOffset, paint);
break;
}
case LimitLabelPosition.LEFT_TOP: {
paint.setTextAlign(Align.RIGHT);
let x = position[0] - xOffset;
if (limitLine.ensureVisible && x < size.width) {
x = position[0] + xOffset;
paint.setTextAlign(Align.LEFT);
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.top + yOffset + size.height, paint);
break;
}
case LimitLabelPosition.LEFT_BOTTOM: {
paint.setTextAlign(Align.RIGHT);
let x = position[0] - xOffset;
if (limitLine.ensureVisible && x < size.width) {
x = position[0] + xOffset;
paint.setTextAlign(Align.LEFT);
}
this.drawLimitLineLabel(c, limitLine, label, x, rect.bottom - yOffset, paint);
break;
}
}
}
}
}
//# sourceMappingURL=XAxisRenderer.js.map