UNPKG

@rongmz/trading-charts

Version:

This is a d3 based charting library for stocks and finance world. If the question is, why another chart library? - Coz, I find no "open-source" library fits my requirements.

452 lines (426 loc) 12.7 kB
import { PlotLineType, debug } from "./types"; /** * Clear the given canvas based on given dim * @param context * @param x * @param y * @param w * @param h * @param color */ export function clearCanvas(context: CanvasRenderingContext2D | null, x: number, y: number, w: number, h: number) { // console.log('clear canvas area', x, y, w, h, color); if (context !== null) { context.save(); context.clearRect(x, y, w, h); context.restore(); } } /** * Draw a single Candle * @param context * @param color candle color * @param ocx open-close x coordinate * @param oy open y * @param cy close y * @param hlx high-low x * @param hy high y * @param ly low y * @param bw candle width * @param sw shadow line width deafult: 1 */ export function drawCandle(context: CanvasRenderingContext2D | null, color: string, ocx: number, oy: number, cy: number, hlx: number, hy: number, ly: number, bw: number, sw: number = 1) { // console.log('clear canvas area', x, y, w, h, color); if (context !== null) { context.save(); context.fillStyle = color; context.fillRect(ocx, oy, bw, (cy - oy) || 1); context.fillRect(hlx, hy, sw, ly - hy); context.restore(); } } /** * Draw a bar * @param context * @param color * @param x * @param y * @param w * @param h */ export function drawBar(context: CanvasRenderingContext2D | null, color: string, x: number, y: number, w: number, h: number) { if (context !== null) { context.save(); context.fillStyle = color; context.fillRect(x, y, w, h); context.restore(); } } /** * Draw a line with given coordinates * @param context * @param color * @param lineType * @param lineWidth * @param coordinates The [x,y] array of points */ export function drawLine(context: CanvasRenderingContext2D | null, color: string, lineType: PlotLineType, lineWidth: number, coordinates: number[][]) { if (context !== null && coordinates.length > 1) { context.save(); context.lineWidth = lineWidth; if (lineType === 'dashed-line') context.setLineDash([5, 10]); else if (lineType === 'dotted-line') context.setLineDash([2, 4]); else context.setLineDash([]); context.strokeStyle = color; context.lineJoin = 'round'; context.lineCap = 'round'; context.beginPath(); context.moveTo(coordinates[0][0], coordinates[0][1]); for (let i = 1; i < coordinates.length; i++) { const xy2 = coordinates[i]; context.lineTo(xy2[0], xy2[1]); } context.stroke(); context.restore(); } } /** * Draw a area graph * @param context * @param lineColor the line graph color * @param lineWidth line graph line width * @param areaColors area color gradient stops. * @param coordinates including base y [x, y, baseY][] */ export function drawArea(context: CanvasRenderingContext2D | null, lineColor: string, lineColorBaseY: string | undefined, lineWidth: number, areaColors: string[], coordinates: number[][]) { if (context !== null && coordinates.length > 1) { context.save(); context.lineWidth = lineWidth; context.setLineDash([]); context.strokeStyle = lineColor; context.lineJoin = 'round'; context.lineCap = 'round'; context.beginPath(); context.moveTo(coordinates[0][0], coordinates[0][1]); let miny = coordinates[0][1], maxy = coordinates[0][1]; for (let i = 1; i < coordinates.length; i++) { const xy2 = coordinates[i]; miny = Math.min(miny, xy2[1], xy2[2]); maxy = Math.max(maxy, xy2[1], xy2[2]); context.lineTo(xy2[0], xy2[1]); } context.stroke(); // create a gradient with given stops const gdt = context.createLinearGradient(0, miny, 0, maxy); areaColors.map((c, i) => gdt.addColorStop(i, c)); context.fillStyle = gdt; context.lineTo(coordinates[coordinates.length - 1][0], coordinates[coordinates.length - 1][2]); for (let i = coordinates.length - 1; i > -1; i--) { const x = coordinates[i][0]; const baseY = coordinates[i][2]; context.lineTo(x, baseY); } context.globalCompositeOperation = 'destination-over'; context.fill(); if (typeof (lineColorBaseY) !== 'undefined') { context.beginPath(); context.moveTo(coordinates[0][0], coordinates[0][2]); context.strokeStyle = lineColorBaseY; for (let i = 0; i < coordinates.length; i++) { const x = coordinates[i][0]; const baseY = coordinates[i][2]; context.lineTo(x, baseY); } context.stroke(); } context.restore(); } } /** * Draws text * @param context * @param color * @param x * @param y * @param maxWidth */ export function drawText(context: CanvasRenderingContext2D | null, text: string, x: number, y: number, maxWidth?: number, color?: string, font?: string, align?: CanvasTextAlign, baseline?: CanvasTextBaseline, direction?: CanvasDirection) { if (context !== null) { context.save(); if (align) context.textAlign = align; if (font) context.font = font; if (baseline) context.textBaseline = baseline; if (direction) context.direction = direction; if (color) context.fillStyle = color; if (!maxWidth) { const m = context.measureText(text); maxWidth = m.width; } context.fillText(text, x, y, maxWidth); context.restore(); } } export function drawCenterPivotRotatedText(context: CanvasRenderingContext2D | null, text: string, x: number, y: number, angleDegree: number, color?: string, font?: string) { if (context !== null) { context.save(); if (font) context.font = font; if (color) context.fillStyle = color; context.textAlign = 'center'; context.textBaseline = 'middle'; context.translate(x, y); context.rotate(angleDegree * Math.PI / 180); context.fillText(text, 0, 0) context.restore(); } } /** * Draw grid lines * @param context * @param x * @param y * @param w * @param h * @param type */ export function drawGridLine(context: CanvasRenderingContext2D | null, color: string, x: number, y: number, w: number, h: number, type: 'vert' | 'horiz' | 'both') { if (context !== null) { context.save(); context.lineWidth = 1; context.strokeStyle = color; if (['both', 'vert'].indexOf(type) > -1) { context.moveTo(x, 0); context.lineTo(x, h); } if (['both', 'horiz'].indexOf(type) > -1) { context.moveTo(0, y); context.lineTo(0, w); } context.stroke(); context.restore(); } } /** * Draw Filled box text * @param context * @param text * @param backColor * @param textColor * @param x * @param y * @param h * @param font * @param align * @param baseline */ export function drawBoxFilledText(context: CanvasRenderingContext2D | null, text: string, backColor: string, textColor: string, tx: number, ty: number, rx?: number, ry?: number, rw?: number, rh?: number, font?: string, align?: CanvasTextAlign, baseline?: CanvasTextBaseline) { if (context !== null) { context.save(); if (align) context.textAlign = align; if (font) context.font = font; if (baseline) context.textBaseline = baseline; context.fillStyle = backColor; const m = context.measureText(text); if (typeof (rx) === 'undefined') { switch (align) { case 'center': rx = tx - m.width / 2; break; default: rx = tx; } } context.fillRect(rx, ry || 0, (rw || m.width) * (align === 'right' ? -1 : 1), rh || 0); context.fillStyle = textColor; context.fillText(text, tx, ty); context.restore(); } } /** * Draw x Range annotation * @param context * @param x1 * @param x2 * @param h * @param lineColor * @param lineWidth * @param areaColor * @param text */ export function drawXRange( context: CanvasRenderingContext2D | null, x1: number, x2: number, h: number, lineColor: string, lineWidth: number, areaColor: string, text?: string, fontSize?: string, ) { if (context !== null) { context.save(); context.lineWidth = lineWidth; context.strokeStyle = lineColor; context.lineJoin = 'round'; context.lineCap = 'round'; context.beginPath(); context.moveTo(x1, 0); context.lineTo(x1, h); context.moveTo(x2, 0); context.lineTo(x2, h); context.stroke(); context.fillStyle = areaColor; context.fillRect(x1, 0, x2 - x1, h); if (typeof (text) !== 'undefined' && !!fontSize) { context.textAlign = 'center'; context.textBaseline = 'top'; context.font = fontSize; context.fillStyle = lineColor; context.fillText(text, x1 + (x2 - x1) / 2, 10); } context.restore(); } } /** * Draw x Single annotation * @param context * @param x1 * @param x2 * @param h * @param lineColor * @param lineWidth * @param areaColor * @param text */ export function drawXSingle( context: CanvasRenderingContext2D | null, x: number, h: number, lineColor: string, lineWidth: number, text?: string, fontSize?: string ) { if (context !== null) { context.save(); context.lineWidth = lineWidth; context.strokeStyle = lineColor; context.lineJoin = 'round'; context.lineCap = 'round'; context.beginPath(); context.moveTo(x, 0); context.lineTo(x, h); context.stroke(); if (typeof (text) !== 'undefined' && !!fontSize) { drawCenterPivotRotatedText(context, text, x - parseInt(fontSize) / 2 - 5, h / 2, -90, lineColor, fontSize); // context.textAlign = 'center'; // context.textBaseline = 'top'; // context.font = fontSize; // context.fillStyle = lineColor; // context.fillText(text, x, 10); } context.restore(); } } /** * Draw a Flag mark in the given chart context * @param context * @param x * @param y * @param text * @param direction * @param color * @param textColor * @param fontSize */ export function drawFlagMark( context: CanvasRenderingContext2D | null, x: number, y: number, text: string, direction: 'up' | 'down', color: string, textColor: string, fontSize: string, ) { if (context !== null) { context.save(); const dir = direction === 'up' ? -1 : 1; const markerH = parseFloat(fontSize) * 2; const markerW = parseFloat(fontSize) * 1.4; context.fillStyle = color; context.beginPath(); context.moveTo(x, y); context.lineTo(x - markerW, y + dir * markerH); context.lineTo(x + markerW, y + dir * markerH); context.fill(); context.fillStyle = textColor; context.textAlign = 'center'; context.textBaseline = direction === 'up' ? 'bottom' : 'top'; context.fillText(text[0] || '', x, y + dir * markerH * 0.4); context.restore(); } } /** * Draw a Rect type annotation. * see https://github.com/rongmz/rongmz-trading-charts/issues/1 * @param context * @param x1 * @param x2 * @param y11 * @param y12 * @param y21 * @param y22 * @param lineColor * @param lineWidth * @param areaColor * @param text * @param textColor * @param fontSize */ export function drawRectLimiterMark( context: CanvasRenderingContext2D | null, x1: number, x2: number, y11: number, y12: number, y21: number, y22: number, lineColor: string, lineWidth: number, areaColor: string, text?: string, fontSize?: string, ) { if (context !== null) { context.save(); context.lineCap = 'round'; context.strokeStyle = lineColor; context.lineWidth = lineWidth; context.beginPath(); context.moveTo(x1, y11); context.lineTo(x2, y21); context.moveTo(x1, y12); context.lineTo(x2, y22); context.stroke(); context.fillStyle = areaColor; context.beginPath(); context.moveTo(x1, y11); context.lineTo(x1, y12); context.lineTo(x2, y22); context.lineTo(x2, y21); context.fill(); if (text && fontSize) { context.font = fontSize; context.textAlign = 'center'; context.textBaseline = 'bottom'; context.fillStyle = lineColor; const tx = x1 + (x2 - x1) / 2, ty = y12 - (y22 - y12) / 2; context.translate(tx, ty); context.rotate((y22 - y12) / (x2 - x1)); context.fillText(text, 0, lineWidth / 2 + 1) } context.restore(); } }