Skip to content

Commit

Permalink
feat: LimitLine custom renderer and more options
Browse files Browse the repository at this point in the history
  • Loading branch information
farfromrefug committed Apr 12, 2021
1 parent 9392d3a commit b2c0e2a
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 138 deletions.
7 changes: 7 additions & 0 deletions src/charting/components/AxisBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ export abstract class AxisBase extends ComponentBase {
* flag indicating the limit lines layer depth
*/
protected mDrawLimitLineBehindData = false;

/**
* When enabled, the limitlines will be clipped to contentRect,
* otherwise they can bleed outside the content rect.
*/
public clipLimitLinesToContent = true;

/**
* flag indicating the labels layer depth
*/
Expand Down
5 changes: 4 additions & 1 deletion src/charting/components/LimitLine.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ComponentBase } from './ComponentBase';
import { DashPathEffect, Paint, Style, parseDashEffect } from '@nativescript-community/ui-canvas';
import { Utils } from '../utils/Utils';
import { Color } from '@nativescript/core';

/** enum that indicates the position of the LimitLine label */
export enum LimitLabelPosition {
LEFT_TOP,
LEFT_BOTTOM,
CENTER_TOP,
CENTER_BOTTOM,
RIGHT_TOP,
RIGHT_BOTTOM
}
Expand All @@ -24,7 +27,7 @@ export class LimitLine extends ComponentBase {
private mLineWidth = 2;

/** the color of the limit line */
private mLineColor = '#ED5B5B';
private mLineColor: Color | string = '#ED5B5B';

/** the style of the label text */
private mTextStyle = Style.FILL_AND_STROKE;
Expand Down
2 changes: 1 addition & 1 deletion src/charting/components/XAxis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export enum XAxisPosition {
BOTTOM,
BOTH_SIDED,
TOP_INSIDE,
BOTTOM_INSIDE,
BOTTOM_INSIDE
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/charting/renderer/AxisRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { ViewPortHandler } from '../utils/ViewPortHandler';
import { Transformer } from '../utils/Transformer';
import { Utils } from '../utils/Utils';
import { BaseCustomRenderer } from './DataRenderer';
import { LimitLine } from '../components/LimitLine';

export type CustomRendererGridLineFunction = (c: Canvas, renderer: AxisRenderer, rect: RectF, x, y, axisValue, paint: Paint) => void;
export type CustomRendererLimitLineFunction = (c: Canvas, renderer: AxisRenderer, limitLine: LimitLine, rect: RectF, x: number, paint: Paint) => void;
export interface CustomRenderer extends BaseCustomRenderer {
drawGridLine?: CustomRendererGridLineFunction;
drawLimitLine?: CustomRendererLimitLineFunction;
}

/**
Expand Down
115 changes: 75 additions & 40 deletions src/charting/renderer/XAxisRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AxisRenderer, CustomRenderer, CustomRendererGridLineFunction } from './AxisRenderer';
import { AxisRenderer, CustomRenderer, CustomRendererGridLineFunction, CustomRendererLimitLineFunction } from './AxisRenderer';
import { XAxis, XAxisPosition } from '../components/XAxis';
import { ViewPortHandler } from '../utils/ViewPortHandler';
import { Transformer } from '../utils/Transformer';
Expand Down Expand Up @@ -316,70 +316,85 @@ export class XAxisRenderer extends AxisRenderer {
return this.mRenderLimitLinesBuffer;
}

protected mLimitLineClippingRect = new RectF(0, 0, 0, 0);

protected mLimitLineClippingRect: RectF;
protected get limitLineClippingRect() {
if (!this.mLimitLineClippingRect) {
this.mLimitLineClippingRect = new RectF(0, 0, 0, 0);
}
return this.mLimitLineClippingRect;
}
/**
* Draws the LimitLines associated with this axis to the screen.
*
* @param c
*/

public renderLimitLines(c: Canvas) {
const limitLines = this.mXAxis.getLimitLines();
const axis = this.mXAxis;
const limitLines = axis.getLimitLines();

if (limitLines == null || limitLines.length <= 0) return;

const position = this.renderLimitLinesBuffer;
position[0] = 0;
position[1] = 0;

const rect = this.mAxis.isIgnoringOffsets() ? this.mViewPortHandler.getChartRect() : this.mViewPortHandler.getContentRect();
const rect = axis.isIgnoringOffsets() ? this.mViewPortHandler.getChartRect() : this.mViewPortHandler.getContentRect();
const clipToContent = axis.clipLimitLinesToContent;
const customRender = axis.getCustomRenderer();
const customRenderFunction = customRender && customRender.drawLimitLine;
for (let i = 0; i < limitLines.length; i++) {
const l = limitLines[i];

if (!l.isEnabled()) continue;
const lineWidth = l.getLineWidth();

const clipRestoreCount = c.save();
this.mLimitLineClippingRect.set(rect);
this.mLimitLineClippingRect.inset(-lineWidth, 0);
c.clipRect(this.mLimitLineClippingRect);
if (clipToContent) {
c.save();
const clipRect = this.limitLineClippingRect;
clipRect.set(rect);
clipRect.inset(0, -lineWidth);
c.clipRect(clipRect);
}

position[0] = l.getLimit();
position[1] = 0;

this.mTrans.pointValuesToPixel(position);

if (lineWidth > 0) {
this.renderLimitLineLine(c, l, position);
this.renderLimitLineLine(c, l, rect, position[0], customRenderFunction);
}
this.renderLimitLineLabel(c, l, position, 2 + l.getYOffset());

c.restoreToCount(clipRestoreCount);
if (clipToContent) {
c.restore();
}
}
}

private mLimitLineSegmentsBuffer = [];
private mLimitLinePath = new Path();
// private mLimitLineSegmentsBuffer = [];
// private mLimitLinePath = new Path();

public renderLimitLineLine(c: Canvas, limitLine: LimitLine, position) {
const rect = this.mAxis.isIgnoringOffsets() ? this.mViewPortHandler.getChartRect() : this.mViewPortHandler.getContentRect();
this.mLimitLineSegmentsBuffer[0] = position[0];
this.mLimitLineSegmentsBuffer[1] = rect.top;
this.mLimitLineSegmentsBuffer[2] = position[0];
this.mLimitLineSegmentsBuffer[3] = rect.bottom;
public renderLimitLineLine(c: Canvas, limitLine: LimitLine, rect: RectF, x: number, customRendererFunc?: CustomRendererLimitLineFunction) {
// this.mLimitLineSegmentsBuffer[0] = position[0];
// this.mLimitLineSegmentsBuffer[1] = rect.top;
// this.mLimitLineSegmentsBuffer[2] = position[0];
// this.mLimitLineSegmentsBuffer[3] = rect.bottom;

this.mLimitLinePath.reset();
this.mLimitLinePath.moveTo(this.mLimitLineSegmentsBuffer[0], this.mLimitLineSegmentsBuffer[1]);
this.mLimitLinePath.lineTo(this.mLimitLineSegmentsBuffer[2], this.mLimitLineSegmentsBuffer[3]);
// this.mLimitLinePath.reset();
// this.mLimitLinePath.moveTo(this.mLimitLineSegmentsBuffer[0], this.mLimitLineSegmentsBuffer[1]);
// this.mLimitLinePath.lineTo(this.mLimitLineSegmentsBuffer[2], this.mLimitLineSegmentsBuffer[3]);

const paint = this.limitLinePaint;
paint.setStyle(Style.STROKE);
// paint.setStyle(Style.STROKE);
paint.setColor(limitLine.getLineColor());
paint.setStrokeWidth(limitLine.getLineWidth());
paint.setPathEffect(limitLine.getDashPathEffect());

c.drawPath(this.mLimitLinePath, paint);
if (customRendererFunc) {
customRendererFunc(c, this, limitLine, rect, x, paint);
} else {
c.drawLine(x, rect.bottom, x, rect.top, paint);
}
}

public renderLimitLineLabel(c: Canvas, limitLine: LimitLine, position, yOffset) {
Expand All @@ -399,20 +414,40 @@ export class XAxisRenderer extends AxisRenderer {

const labelPosition = limitLine.getLabelPosition();

if (labelPosition === LimitLabelPosition.RIGHT_TOP) {
const labelLineHeight = Utils.calcTextHeight(paint, label);
paint.setTextAlign(Align.LEFT);
c.drawText(label, position[0] + xOffset, rect.top + yOffset + labelLineHeight, paint);
} else if (labelPosition === LimitLabelPosition.RIGHT_BOTTOM) {
paint.setTextAlign(Align.LEFT);
c.drawText(label, position[0] + xOffset, rect.bottom - yOffset, paint);
} else if (labelPosition === LimitLabelPosition.LEFT_TOP) {
paint.setTextAlign(Align.RIGHT);
const labelLineHeight = Utils.calcTextHeight(paint, label);
c.drawText(label, position[0] - xOffset, rect.top + yOffset + labelLineHeight, paint);
} else {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, position[0] - xOffset, rect.bottom - yOffset, paint);
switch (labelPosition) {
case LimitLabelPosition.CENTER_TOP: {
const labelLineHeight = Utils.calcTextHeight(paint, label);
paint.setTextAlign(Align.CENTER);
c.drawText(label, position[0], rect.top + yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.CENTER_BOTTOM: {
paint.setTextAlign(Align.CENTER);
c.drawText(label, position[0], rect.bottom - yOffset, paint);
break;
}
case LimitLabelPosition.RIGHT_TOP: {
const labelLineHeight = Utils.calcTextHeight(paint, label);
paint.setTextAlign(Align.LEFT);
c.drawText(label, position[0] + xOffset, rect.top + yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.RIGHT_BOTTOM: {
paint.setTextAlign(Align.LEFT);
c.drawText(label, position[0] + xOffset, rect.bottom - yOffset, paint);
break;
}
case LimitLabelPosition.LEFT_TOP: {
paint.setTextAlign(Align.RIGHT);
const labelLineHeight = Utils.calcTextHeight(paint, label);
c.drawText(label, position[0] - xOffset, rect.top + yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.LEFT_BOTTOM: {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, position[0] - xOffset, rect.bottom - yOffset, paint);
break;
}
}
}
}
Expand Down
79 changes: 56 additions & 23 deletions src/charting/renderer/XAxisRendererHorizontalBarChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ export class XAxisRendererHorizontalBarChart extends XAxisRenderer {
*/

public renderLimitLines(c: Canvas) {
const limitLines = this.mXAxis.getLimitLines();
const axis = this.mXAxis;
const limitLines = axis.getLimitLines();

if (limitLines == null || limitLines.length <= 0) return;

Expand All @@ -200,35 +201,46 @@ export class XAxisRendererHorizontalBarChart extends XAxisRenderer {
limitLinePath.reset();
let offsetLeft = 0;
let rect: RectF;
if (this.mAxis.isIgnoringOffsets()) {
if (axis.isIgnoringOffsets()) {
rect = this.mViewPortHandler.getChartRect();
} else {
rect = this.mViewPortHandler.getContentRect();
offsetLeft = this.mViewPortHandler.offsetLeft();
}
const customRender = axis.getCustomRenderer();
const customRendererFunc = customRender && customRender.drawLimitLine;
const clipToContent = axis.clipLimitLinesToContent;
for (let i = 0; i < limitLines.length; i++) {
const l = limitLines[i];

if (!l.isEnabled()) {
continue;
}

const clipRestoreCount = c.save();
this.mLimitLineClippingRect.set(rect);
this.mLimitLineClippingRect.inset(0, -l.getLineWidth());
c.clipRect(this.mLimitLineClippingRect);
const lineWidth = l.getLineWidth();
if (clipToContent) {
const clipRect = this.limitLineClippingRect;
clipRect.set(rect);
clipRect.inset(0, -lineWidth);
c.clipRect(clipRect);
}

const paint = this.limitLinePaint;
paint.setStyle(Style.STROKE);
paint.setColor(l.getLineColor());
paint.setStrokeWidth(l.getLineWidth());
paint.setStrokeWidth(lineWidth);
paint.setPathEffect(l.getDashPathEffect());

pts[1] = l.getLimit();

this.mTrans.pointValuesToPixel(pts);

limitLinePath.moveTo(rect.left, pts[1]);
if (lineWidth > 0) {
if (customRendererFunc) {
customRendererFunc(c, this, l, rect, pts[1], paint);
} else {
c.drawLine(rect.left, pts[1], rect.right, pts[1], paint);
}
}

limitLinePath.lineTo(rect.right, pts[1]);

c.drawPath(limitLinePath, paint);
Expand All @@ -250,22 +262,43 @@ export class XAxisRendererHorizontalBarChart extends XAxisRenderer {

const position = l.getLabelPosition();

if (position === LimitLabelPosition.RIGHT_TOP) {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, rect.right - xOffset, pts[1] - yOffset + labelLineHeight, paint);
} else if (position === LimitLabelPosition.RIGHT_BOTTOM) {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, rect.right - xOffset, pts[1] + yOffset, paint);
} else if (position === LimitLabelPosition.LEFT_TOP) {
paint.setTextAlign(Align.LEFT);
c.drawText(label, rect.left + xOffset, pts[1] - yOffset + labelLineHeight, paint);
} else {
paint.setTextAlign(Align.LEFT);
c.drawText(label, offsetLeft + xOffset, pts[1] + yOffset, paint);
switch (position) {
case LimitLabelPosition.RIGHT_TOP: {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, rect.right - xOffset, pts[1] - yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.RIGHT_BOTTOM: {
paint.setTextAlign(Align.RIGHT);
c.drawText(label, rect.right - xOffset, pts[1] + yOffset, paint);
break;
}
case LimitLabelPosition.CENTER_TOP: {
paint.setTextAlign(Align.CENTER);
c.drawText(label, rect.right, pts[1] - yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.CENTER_BOTTOM: {
paint.setTextAlign(Align.CENTER);
c.drawText(label, rect.right, pts[1] + yOffset, paint);
break;
}
case LimitLabelPosition.LEFT_TOP: {
paint.setTextAlign(Align.LEFT);
c.drawText(label, rect.left + xOffset, pts[1] - yOffset + labelLineHeight, paint);
break;
}
case LimitLabelPosition.LEFT_BOTTOM: {
paint.setTextAlign(Align.LEFT);
c.drawText(label, offsetLeft + xOffset, pts[1] + yOffset, paint);
break;
}
}
}

c.restoreToCount(clipRestoreCount);
if (clipToContent) {
c.restore();
}
}
}
}
Loading

0 comments on commit b2c0e2a

Please sign in to comment.