ant-kline
Version:
对kline.js的自定义修改
1,320 lines (1,211 loc) • 97 kB
JavaScript
import Kline from './kline'
import { NamedObject } from './named_object'
import { ChartManager } from './chart_manager'
import { Util } from './util'
import { CPoint } from './cpoint'
import * as exprs from './exprs'
import * as themes from './themes'
import * as data_providers from './data_providers'
import * as data_sources from './data_sources'
import * as ctools from './ctools'
export class Plotter extends NamedObject {
static isChrome = (navigator.userAgent.toLowerCase().match(/chrome/) !== null);
constructor (name) {
super(name);
}
static drawLine (context, x1, y1, x2, y2) {
context.beginPath();
context.moveTo((x1 << 0) + 0.5, (y1 << 0) + 0.5);
context.lineTo((x2 << 0) + 0.5, (y2 << 0) + 0.5);
context.stroke();
}
static drawLines (context, points) {
let i, cnt = points.length;
context.beginPath();
context.moveTo(points[ 0 ].x, points[ 0 ].y);
for (i = 1; i < cnt; i++)
context.lineTo(points[ i ].x, points[ i ].y);
if (Plotter.isChrome) {
context.moveTo(points[ 0 ].x, points[ 0 ].y);
for (i = 1; i < cnt; i++)
context.lineTo(points[ i ].x, points[ i ].y);
}
context.stroke();
}
static drawDashedLine (context, x1, y1, x2, y2, dashLen, dashSolid) {
if (dashLen < 2) {
dashLen = 2;
}
let dX = x2 - x1;
let dY = y2 - y1;
context.beginPath();
if (dY === 0) {
let count = (dX / dashLen + 0.5) << 0;
for (let i = 0; i < count; i++) {
context.rect(x1, y1, dashSolid, 1);
x1 += dashLen;
}
context.fill();
} else {
let count = (Math.sqrt(dX * dX + dY * dY) / dashLen + 0.5) << 0;
dX = dX / count;
dY = dY / count;
let dashX = dX * dashSolid / dashLen;
let dashY = dY * dashSolid / dashLen;
for (let i = 0; i < count; i++) {
context.moveTo(x1 + 0.5, y1 + 0.5);
context.lineTo(x1 + 0.5 + dashX, y1 + 0.5 + dashY);
x1 += dX;
y1 += dY;
}
context.stroke();
}
}
static createHorzDashedLine (context, x1, x2, y, dashLen, dashSolid) {
if (dashLen < 2) {
dashLen = 2;
}
let dX = x2 - x1;
let count = (dX / dashLen + 0.5) << 0;
for (let i = 0; i < count; i++) {
context.rect(x1, y, dashSolid, 1);
x1 += dashLen;
}
}
static createRectangles (context, rects) {
context.beginPath();
let e, i, cnt = rects.length;
for (i = 0; i < cnt; i++) {
e = rects[ i ];
context.rect(e.x, e.y, e.w, e.h);
}
}
static createPolygon (context, points) {
context.beginPath();
context.moveTo(points[ 0 ].x + 0.5, points[ 0 ].y + 0.5);
let i, cnt = points.length;
for (i = 1; i < cnt; i++)
context.lineTo(points[ i ].x + 0.5, points[ i ].y + 0.5);
context.closePath();
}
static drawString (context, str, rect) {
let w = context.measureText(str).width;
if (rect.w < w) {
return false;
}
context.fillText(str, rect.x, rect.y);
rect.x += w;
rect.w -= w;
return true;
}
}
export class BackgroundPlotter extends Plotter {
constructor (name) {
super(name);
this._color = themes.Theme.Color.Background;
}
getColor () {
return this._color;
}
setColor (c) {
this._color = c;
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let theme = mgr.getTheme(this.getFrameName());
context.fillStyle = theme.getColor(this._color);
context.fillRect(area.getLeft(), area.getTop(), area.getWidth(), area.getHeight());
}
}
export class MainAreaBackgroundPlotter extends BackgroundPlotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
let theme = mgr.getTheme(this.getFrameName());
let rect = area.getRect();
if (!area.isChanged() && !timeline.isUpdated() && !range.isUpdated()) {
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex() - 2;
let start = Math.max(first, last);
rect.X = timeline.toColumnLeft(start);
rect.Width = area.getRight() - rect.X;
}
context.fillStyle = theme.getColor(this._color);
context.fillRect(rect.X, rect.Y, rect.Width, rect.Height);
}
}
export class RangeAreaBackgroundPlotter extends BackgroundPlotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let areaName = this.getAreaName();
let area = mgr.getArea(areaName);
let range = mgr.getRange(areaName.substring(0, areaName.lastIndexOf("Range")));
let isMainRange = range.getNameObject().getCompAt(2) === "main";
if (!isMainRange && !area.isChanged() && !range.isUpdated()) {
return;
}
let theme = mgr.getTheme(this.getFrameName());
context.fillStyle = theme.getColor(this._color);
context.fillRect(area.getLeft(), area.getTop(), area.getWidth(), area.getHeight());
}
}
export class TimelineAreaBackgroundPlotter extends BackgroundPlotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
if (!area.isChanged() && !timeline.isUpdated())
return;
let theme = mgr.getTheme(this.getFrameName());
context.fillStyle = theme.getColor(this._color);
context.fillRect(area.getLeft(), area.getTop(), area.getWidth(), area.getHeight());
}
}
export class CGridPlotter extends NamedObject {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
let clipped = false;
if (!area.isChanged() && !timeline.isUpdated() && !range.isUpdated()) {
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let start = Math.max(first, last - 2);
let left = timeline.toColumnLeft(start);
context.save();
context.rect(left, area.getTop(), area.getRight() - left, area.getHeight());
context.clip();
clipped = true;
}
let theme = mgr.getTheme(this.getFrameName());
context.fillStyle = theme.getColor(themes.Theme.Color.Grid0);
context.beginPath();
let dashLen = 4,
dashSolid = 1;
if (Plotter.isChrome) {
dashLen = 4;
dashSolid = 1;
}
let gradations = range.getGradations();
for (let n in gradations) {
Plotter.createHorzDashedLine(context, area.getLeft(), area.getRight(), range.toY(gradations[ n ]), dashLen, dashSolid);
}
context.fill();
if (clipped) {
context.restore();
}
}
}
export class CandlestickPlotter extends NamedObject {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let ds = mgr.getDataSource(this.getDataSourceName());
if (ds.getDataCount() < 1) {
return;
}
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
if (range.getRange() === 0.0) {
return;
}
let theme = mgr.getTheme(this.getFrameName());
let dark = Util.isInstance(theme, themes.DarkTheme);
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let start;
if (area.isChanged() || timeline.isUpdated() || range.isUpdated())
start = first;
else
start = Math.max(first, last - 2);
let cW = timeline.getColumnWidth();
let iW = timeline.getItemWidth();
let left = timeline.toItemLeft(start);
let center = timeline.toItemCenter(start);
let strokePosRects = [];
let fillPosRects = [];
let fillUchRects = [];
let fillNegRects = [];
for (let i = start; i < last; i++) {
let data = ds.getDataAt(i);
let high = range.toY(data.high);
let low = range.toY(data.low);
let open = data.open;
let close = data.close;
if (close > open) {
let top = range.toY(close);
let bottom = range.toY(open);
let iH = Math.max(bottom - top, 1);
if (iH > 1 && iW > 1 && dark)
strokePosRects.push({x : left + 0.5, y : top + 0.5, w : iW - 1, h : iH - 1});
else
fillPosRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
if (data.high > close) {
high = Math.min(high, top - 1);
fillPosRects.push({x : center, y : high, w : 1, h : top - high});
}
if (open > data.low) {
low = Math.max(low, bottom + 1);
fillPosRects.push({x : center, y : bottom, w : 1, h : low - bottom});
}
} else if (close === open) {
let top = range.toY(close);
fillUchRects.push({x : left, y : top, w : Math.max(iW, 1), h : 1});
if (data.high > close)
high = Math.min(high, top - 1);
if (open > data.low)
low = Math.max(low, top + 1);
if (high < low)
fillUchRects.push({x : center, y : high, w : 1, h : low - high});
} else {
let top = range.toY(open);
let bottom = range.toY(close);
let iH = Math.max(bottom - top, 1);
fillNegRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
if (data.high > open)
high = Math.min(high, top - 1);
if (close > data.low)
low = Math.max(low, bottom + 1);
if (high < low)
fillNegRects.push({x : center, y : high, w : 1, h : low - high});
}
left += cW;
center += cW;
}
if (strokePosRects.length > 0) {
context.strokeStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, strokePosRects);
context.stroke();
}
if (fillPosRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, fillPosRects);
context.fill();
}
if (fillUchRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillUchRects);
context.fill();
}
if (fillNegRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillNegRects);
context.fill();
}
}
}
export class CandlestickHLCPlotter extends Plotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let ds = mgr.getDataSource(this.getDataSourceName());
if (!Util.isInstance(ds, data_sources.MainDataSource) || ds.getDataCount() < 1) {
return;
}
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
if (range.getRange() === 0.0) {
return;
}
let theme = mgr.getTheme(this.getFrameName());
let dark = Util.isInstance(theme, themes.DarkTheme);
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let start;
if (area.isChanged() || timeline.isUpdated() || range.isUpdated()) {
start = first;
} else {
start = Math.max(first, last - 2);
}
let cW = timeline.getColumnWidth();
let iW = timeline.getItemWidth();
let left = timeline.toItemLeft(start);
let center = timeline.toItemCenter(start);
let strokePosRects = [];
let fillPosRects = [];
let fillUchRects = [];
let fillNegRects = [];
for (let i = start; i < last; i++) {
let data = ds.getDataAt(i);
let high = range.toY(data.high);
let low = range.toY(data.low);
let open = data.open;
if (i > 0) {
open = ds.getDataAt(i - 1).close;
}
let close = data.close;
if (close > open) {
let top = range.toY(close);
let bottom = range.toY(open);
let iH = Math.max(bottom - top, 1);
if (iH > 1 && iW > 1 && dark) {
strokePosRects.push({x : left + 0.5, y : top + 0.5, w : iW - 1, h : iH - 1});
} else {
fillPosRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
if (data.high > close) {
high = Math.min(high, top - 1);
fillPosRects.push({x : center, y : high, w : 1, h : top - high});
}
if (open > data.low) {
low = Math.max(low, bottom + 1);
fillPosRects.push({x : center, y : bottom, w : 1, h : low - bottom});
}
} else if (close === open) {
let top = range.toY(close);
fillUchRects.push({x : left, y : top, w : Math.max(iW, 1), h : 1});
if (data.high > close) {
high = Math.min(high, top - 1);
}
if (open > data.low) {
low = Math.max(low, top + 1);
}
if (high < low) {
fillUchRects.push({x : center, y : high, w : 1, h : low - high});
}
} else {
let top = range.toY(open);
let bottom = range.toY(close);
let iH = Math.max(bottom - top, 1);
fillNegRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
if (data.high > open) {
high = Math.min(high, top - 1);
}
if (close > data.low) {
low = Math.max(low, bottom + 1);
}
if (high < low) {
fillNegRects.push({x : center, y : high, w : 1, h : low - high});
}
}
left += cW;
center += cW;
}
if (strokePosRects.length > 0) {
context.strokeStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, strokePosRects);
context.stroke();
}
if (fillPosRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, fillPosRects);
context.fill();
}
if (fillUchRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillUchRects);
context.fill();
}
if (fillNegRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillNegRects);
context.fill();
}
}
}
export class OHLCPlotter extends Plotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let ds = mgr.getDataSource(this.getDataSourceName());
if (!Util.isInstance(ds, data_sources.MainDataSource) || ds.getDataCount() < 1) {
return;
}
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
if (range.getRange() === 0.0) {
return;
}
let theme = mgr.getTheme(this.getFrameName());
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let start;
if (area.isChanged() || timeline.isUpdated() || range.isUpdated()) {
start = first;
} else {
start = Math.max(first, last - 2);
}
let cW = timeline.getColumnWidth();
let iW = timeline.getItemWidth() >> 1;
let left = timeline.toItemLeft(start);
let center = timeline.toItemCenter(start);
let right = left + timeline.getItemWidth();
let fillPosRects = [];
let fillUchRects = [];
let fillNegRects = [];
for (let i = start; i < last; i++) {
let data = ds.getDataAt(i);
let high = range.toY(data.high);
let low = range.toY(data.low);
let iH = Math.max(low - high, 1);
if (data.close > data.open) {
let top = range.toY(data.close);
let bottom = range.toY(data.open);
fillPosRects.push({x : center, y : high, w : 1, h : iH});
fillPosRects.push({x : left, y : top, w : iW, h : 1});
fillPosRects.push({x : center, y : bottom, w : iW, h : 1});
} else if (data.close === data.open) {
let y = range.toY(data.close);
fillUchRects.push({x : center, y : high, w : 1, h : iH});
fillUchRects.push({x : left, y : y, w : iW, h : 1});
fillUchRects.push({x : center, y : y, w : iW, h : 1});
} else {
let top = range.toY(data.open);
let bottom = range.toY(data.close);
fillNegRects.push({x : center, y : high, w : 1, h : iH});
fillNegRects.push({x : left, y : top, w : iW, h : 1});
fillNegRects.push({x : center, y : bottom, w : iW, h : 1});
}
left += cW;
center += cW;
right += cW;
}
if (fillPosRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, fillPosRects);
context.fill();
}
if (fillUchRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillUchRects);
context.fill();
}
if (fillNegRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillNegRects);
context.fill();
}
};
}
export class MainInfoPlotter extends Plotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let ds = mgr.getDataSource(this.getDataSourceName());
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
context.textAlign = "left";
context.textBaseline = "top";
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
let rect = {
x : area.getLeft() + 4,
y : area.getTop() + 2,
w : area.getWidth() - 8,
h : 20
};
let selIndex = timeline.getSelectedIndex();
if (selIndex < 0)
return;
let data = ds.getDataAt(selIndex);
let digits = ds.getDecimalDigits();
let time = new Date(data.date);
let year = time.getFullYear();
let month = Util.formatTime(time.getMonth() + 1);
let date = Util.formatTime(time.getDate());
let hour = Util.formatTime(time.getHours());
let minute = Util.formatTime(time.getMinutes());
let lang = mgr.getLanguage();
if (lang === "zh-cn") {
// if (!Plotter.drawString(context, '时间: ' +
// year + '-' + month + '-' + date + ' ' + hour + ':' + minute, rect))
// return;
if (!Plotter.drawString(context, ' 开: ' + data.open.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 高: ' + data.high.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 低: ' + data.low.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 收: ' + data.close.toFixed(digits), rect))
return;
} else if (lang === "en-us") {
// if (!Plotter.drawString(context, 'DATE: ' +
// year + '-' + month + '-' + date + ' ' + hour + ':' + minute, rect))
// return;
if (!Plotter.drawString(context, ' O: ' + data.open.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' H: ' + data.high.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' L: ' + data.low.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' C: ' + data.close.toFixed(digits), rect))
return;
} else if (lang === "zh-tw") {
// if (!Plotter.drawString(context, '時間: ' +
// year + '-' + month + '-' + date + ' ' + hour + ':' + minute, rect))
// return;
if (!Plotter.drawString(context, ' 開: ' + data.open.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 高: ' + data.high.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 低: ' + data.low.toFixed(digits), rect))
return;
if (!Plotter.drawString(context, ' 收: ' + data.close.toFixed(digits), rect))
return;
}
if (selIndex > 0) {
if (lang === "zh-cn") {
if (!Plotter.drawString(context, ' 涨幅: ', rect))
return;
} else if (lang === "en-us") {
if (!Plotter.drawString(context, ' CHANGE: ', rect))
return;
} else if (lang === "zh-tw") {
if (!Plotter.drawString(context, ' 漲幅: ', rect))
return;
}
let prev = ds.getDataAt(selIndex - 1);
let change;
if ((data.close - prev.close) / prev.close * 100.0) {
change = (data.close - prev.close) / prev.close * 100.0;
} else {
change = 0.00;
}
if (change >= 0) {
change = ' ' + change.toFixed(2);
context.fillStyle = theme.getColor(themes.Theme.Color.TextPositive);
} else {
change = change.toFixed(2);
context.fillStyle = theme.getColor(themes.Theme.Color.TextNegative);
}
if (!Plotter.drawString(context, change, rect))
return;
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
if (!Plotter.drawString(context, ' %', rect))
return;
}
let amplitude;
if ((data.high - data.low) / data.low * 100.0) {
amplitude = (data.high - data.low) / data.low * 100.0;
} else {
amplitude = 0.00;
}
if (lang === "zh-cn") {
if (!Plotter.drawString(context, ' 振幅: ' + amplitude.toFixed(2) + ' %', rect)) {
return;
}
// if (!Plotter.drawString(context, ' 量: ' + data.volume.toFixed(2), rect)) {
// return;
// }
} else if (lang === "en-us") {
if (!Plotter.drawString(context, ' AMPLITUDE: ' + amplitude.toFixed(2) + ' %', rect)) {
return;
}
// if (!Plotter.drawString(context, ' V: ' + data.volume.toFixed(2), rect)) {
// return;
// }
} else if (lang === "zh-tw") {
if (!Plotter.drawString(context, ' 振幅: ' + amplitude.toFixed(2) + ' %', rect)) {
return;
}
// if (!Plotter.drawString(context, ' 量: ' + data.volume.toFixed(2), rect)) {
// return;
// }
}
let dp = mgr.getDataProvider(this.getAreaName() + ".secondary");
if (dp === undefined) {
return;
}
let indic = dp.getIndicator();
let n, cnt = indic.getOutputCount();
for (n = 0; n < cnt; n++) {
let out = indic.getOutputAt(n);
let v = out.execute(selIndex);
if (isNaN(v)) {
continue;
}
let info = " " + out.getName() + ": " + v.toFixed(digits);
let color = out.getColor();
if (color === undefined) {
color = themes.Theme.Color.Indicator0 + n;
}
context.fillStyle = theme.getColor(color);
if (!Plotter.drawString(context, info, rect)) {
return;
}
}
}
}
export class IndicatorPlotter extends NamedObject {
constructor (name) {
super(name);
}
Draw (context) {
// context, theme,
// out, start, last,
// timeline.toItemLeft(start), cW, timeline.getItemWidth(), range
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let range = mgr.getRange(this.getAreaName());
if (range.getRange() === 0.0)
return;
let dp = mgr.getDataProvider(this.getName());
if (!Util.isInstance(dp, data_providers.IndicatorDataProvider))
return;
let theme = mgr.getTheme(this.getFrameName());
let cW = timeline.getColumnWidth();
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let start;
if (area.isChanged() || timeline.isUpdated() || range.isUpdated())
start = first;
else
start = Math.max(first, last - 2);
let indic = dp.getIndicator();
let out, n, outCount = indic.getOutputCount();
for (n = 0; n < outCount; n++) {
out = indic.getOutputAt(n);
let style = out.getStyle();
if (style === exprs.OutputExpr.outputStyle.VolumeStick) {
this.drawVolumeStick(context, theme,
mgr.getDataSource(this.getDataSourceName()), start, last,
timeline.toItemLeft(start), cW, timeline.getItemWidth(), range);
} else if (style === exprs.OutputExpr.outputStyle.MACDStick) {
this.drawMACDStick(context, theme,
out, start, last,
timeline.toItemLeft(start), cW, timeline.getItemWidth(), range);
} else if (style === exprs.OutputExpr.outputStyle.SARPoint) {
this.drawSARPoint(context, theme,
out, start, last,
timeline.toItemCenter(start), cW, timeline.getItemWidth(), range);
} else if (style === exprs.OutputExpr.outputStyle.Custom) {
this.drawCustomStick(context, theme,
out, start, last,
timeline.toItemLeft(start), cW, timeline.getItemWidth(), range, mgr.getDataSource(this.getDataSourceName()));
}
}
let left = timeline.toColumnLeft(start);
let center = timeline.toItemCenter(start);
context.save();
context.rect(left, area.getTop(), area.getRight() - left, area.getHeight());
context.clip();
context.translate(0.5, 0.5);
for (n = 0; n < outCount; n++) {
let x = center;
out = indic.getOutputAt(n);
if (out.getStyle() === exprs.OutputExpr.outputStyle.Line) {
let v, points = [];
if (start > first) {
v = out.execute(start - 1);
if (isNaN(v) === false)
points.push({"x" : x - cW, "y" : range.toY(v)});
}
for (let i = start; i < last; i++, x += cW) {
v = out.execute(i);
if (isNaN(v) === false)
points.push({"x" : x, "y" : range.toY(v)});
}
if (points.length > 0) {
let color = out.getColor();
if (color === undefined)
color = themes.Theme.Color.Indicator0 + n;
context.strokeStyle = theme.getColor(color);
Plotter.drawLines(context, points);
}
}
}
context.restore();
}
drawVolumeStick (context, theme, ds, first, last, startX, cW, iW, range) {
let dark = Util.isInstance(theme, themes.DarkTheme);
let left = startX;
let bottom = range.toY(0);
let strokePosRects = [];
let fillPosRects = [];
let fillNegRects = [];
for (let i = first; i < last; i++) {
let data = ds.getDataAt(i);
let top = range.toY(data.volume);
let iH = range.toHeight(data.volume);
if (data.close > data.open) {
if (iH > 1 && iW > 1 && dark) {
strokePosRects.push({x : left + 0.5, y : top + 0.5, w : iW - 1, h : iH - 1});
} else {
fillPosRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
} else if (data.close === data.open) {
if (i > 0 && data.close >= ds.getDataAt(i - 1).close) {
if (iH > 1 && iW > 1 && dark) {
strokePosRects.push({x : left + 0.5, y : top + 0.5, w : iW - 1, h : iH - 1});
} else {
fillPosRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
} else {
fillNegRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
} else {
fillNegRects.push({x : left, y : top, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
left += cW;
}
if (strokePosRects.length > 0) {
context.strokeStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, strokePosRects);
context.stroke();
}
if (fillPosRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, fillPosRects);
context.fill();
}
if (fillNegRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillNegRects);
context.fill();
}
}
drawCustomStick (context, theme, output, first, last, startX, cW, iW, range, ds) {
let left = startX;
let middle = range.toY(0);
let prevMACD = (first > 0) ? output.execute(first - 1) : NaN;
for (let i = first ? first - 1 : 0; i < last; i++) {
let TRADE = output.execute(i);
let data = ds.getDataAt(i);
if (data.trade) {
let color = "rgb(255,255,255)"
if (data.trade.type === 'open') { // 开#19B34C 空心
color = '#19B34C'
context.strokeStyle = color
context.stroke();
} else if (data.trade.type === 'close') { //关 #B33121 空心
color = '#B33121'
context.strokeStyle = color
context.stroke();
} else if (data.trade.type === 'liquidation') { // 爆仓 #F6EB2F 实心
color = '#F6EB2F'
context.fillStyle = color
context.fill();
} else if (data.trade.type === 'both') { // BOTH #19B34C 实心
color = '#19B34C'
context.fillStyle = color
context.fill();
}
}
/*画线*/
context.beginPath();
context.rect(left, middle, Math.max(iW, 1), 50);
prevMACD = TRADE;
left += cW;
}
}
drawMACDStick (context, theme, output, first, last, startX, cW, iW, range) {
let left = startX;
let middle = range.toY(0);
let strokePosRects = [];
let strokeNegRects = [];
let fillPosRects = [];
let fillNegRects = [];
let prevMACD = (first > 0) ? output.execute(first - 1) : NaN;
for (let i = first; i < last; i++) {
let MACD = output.execute(i);
if (MACD >= 0) {
let iH = range.toHeight(MACD);
if ((i === 0 || MACD >= prevMACD) && iH > 1 && iW > 1)
strokePosRects.push({x : left + 0.5, y : middle - iH + 0.5, w : iW - 1, h : iH - 1});
else
fillPosRects.push({x : left, y : middle - iH, w : Math.max(iW, 1), h : Math.max(iH, 1)});
} else {
let iH = range.toHeight(-MACD);
if ((i === 0 || MACD >= prevMACD) && iH > 1 && iW > 1)
strokeNegRects.push({x : left + 0.5, y : middle + 0.5, w : iW - 1, h : iH - 1});
else
fillNegRects.push({x : left, y : middle, w : Math.max(iW, 1), h : Math.max(iH, 1)});
}
prevMACD = MACD;
left += cW;
}
if (strokePosRects.length > 0) {
context.strokeStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, strokePosRects);
context.stroke();
}
if (strokeNegRects.length > 0) {
context.strokeStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, strokeNegRects);
context.stroke();
}
if (fillPosRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Positive);
Plotter.createRectangles(context, fillPosRects);
context.fill();
}
if (fillNegRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Negative);
Plotter.createRectangles(context, fillNegRects);
context.fill();
}
}
drawSARPoint (context, theme, output, first, last, startX, cW, iW, range) {
let r = iW >> 1;
if (r < 0.5) r = 0.5;
if (r > 4) r = 4;
let center = startX;
let right = center + r;
let endAngle = 2 * Math.PI;
context.save();
context.translate(0.5, 0.5);
context.strokeStyle = theme.getColor(themes.Theme.Color.Indicator3);
context.beginPath();
for (let i = first; i < last; i++) {
let y = range.toY(output.execute(i));
context.moveTo(right, y);
context.arc(center, y, r, 0, endAngle);
center += cW;
right += cW;
}
context.stroke();
context.restore();
}
}
export class IndicatorInfoPlotter extends Plotter {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
let dp = mgr.getDataProvider(this.getAreaName() + ".secondary");
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
context.textAlign = "left";
context.textBaseline = "top";
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
let rect = {
x : area.getLeft() + 4,
y : area.getTop() + 2,
w : area.getWidth() - 8,
h : 20
};
let indic = dp.getIndicator();
let title;
switch (indic.getParameterCount()) {
case 0:
title = indic.getName();
break;
case 1:
title = indic.getName() + "(" +
indic.getParameterAt(0).getValue() +
")";
break;
case 2:
title = indic.getName() + "(" +
indic.getParameterAt(0).getValue() + "," +
indic.getParameterAt(1).getValue() +
")";
break;
case 3:
title = indic.getName() + "(" +
indic.getParameterAt(0).getValue() + "," +
indic.getParameterAt(1).getValue() + "," +
indic.getParameterAt(2).getValue() +
")";
break;
case 4:
title = indic.getName() + "(" +
indic.getParameterAt(0).getValue() + "," +
indic.getParameterAt(1).getValue() + "," +
indic.getParameterAt(2).getValue() + "," +
indic.getParameterAt(3).getValue() +
")";
break;
default:
return;
}
if (!Plotter.drawString(context, title, rect))
return;
let selIndex = timeline.getSelectedIndex();
if (selIndex < 0)
return;
let out, v, info, color;
let n, cnt = indic.getOutputCount();
for (n = 0; n < cnt; n++) {
out = indic.getOutputAt(n);
v = out.execute(selIndex);
if (!v && typeof(v) !== "string") {
continue;
}
if (typeof(v) === "number") {
info = " " + out.getName() + ": " + v.toFixed(2);
} else {
info = " " + out.getName() + ": " + v;
}
color = out.getColor();
if (color === undefined)
color = themes.Theme.Color.Indicator0 + n;
context.fillStyle = theme.getColor(color);
if (!Plotter.drawString(context, info, rect))
return;
}
}
}
export class MinMaxPlotter extends NamedObject {
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let ds = mgr.getDataSource(this.getDataSourceName());
if (ds.getDataCount() < 1)
return;
let timeline = mgr.getTimeline(this.getDataSourceName());
if (timeline.getInnerWidth() < timeline.getColumnWidth())
return;
let range = mgr.getRange(this.getAreaName());
if (range.getRange() === 0)
return;
let dp = mgr.getDataProvider(this.getAreaName() + ".main");
let first = timeline.getFirstIndex();
let center = (first + timeline.getLastIndex()) >> 1;
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
context.textBaseline = "middle";
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
context.strokeStyle = theme.getColor(themes.Theme.Color.Text4);
let digits = ds.getDecimalDigits();
this.drawMark(context, dp.getMinValue(), digits, range.toY(dp.getMinValue()),
first, center, dp.getMinValueIndex(), timeline);
this.drawMark(context, dp.getMaxValue(), digits, range.toY(dp.getMaxValue()),
first, center, dp.getMaxValueIndex(), timeline);
}
drawMark (context, v, digits, y, first, center, index, timeline) {
let arrowStart, arrowStop, _arrowStop;
let textStart;
if (index > center) {
context.textAlign = "right";
arrowStart = timeline.toItemCenter(index) - 4;
arrowStop = arrowStart - 7;
_arrowStop = arrowStart - 3;
textStart = arrowStop - 4;
} else {
context.textAlign = "left";
arrowStart = timeline.toItemCenter(index) + 4;
arrowStop = arrowStart + 7;
_arrowStop = arrowStart + 3;
textStart = arrowStop + 4;
}
Plotter.drawLine(context, arrowStart, y, arrowStop, y);
Plotter.drawLine(context, arrowStart, y, _arrowStop, y + 2);
Plotter.drawLine(context, arrowStart, y, _arrowStop, y - 2);
context.fillText(Util.fromFloat(v, digits), textStart, y);
}
}
export class TimelinePlotter extends Plotter {
static TP_MINUTE = 60 * 1000;
static TP_HOUR = 60 * TimelinePlotter.TP_MINUTE;
static TP_DAY = 24 * TimelinePlotter.TP_HOUR;
static TIME_INTERVAL = [
5 * TimelinePlotter.TP_MINUTE,
10 * TimelinePlotter.TP_MINUTE,
15 * TimelinePlotter.TP_MINUTE,
30 * TimelinePlotter.TP_MINUTE,
TimelinePlotter.TP_HOUR,
2 * TimelinePlotter.TP_HOUR,
3 * TimelinePlotter.TP_HOUR,
6 * TimelinePlotter.TP_HOUR,
12 * TimelinePlotter.TP_HOUR,
TimelinePlotter.TP_DAY,
2 * TimelinePlotter.TP_DAY
];
static MonthConvert = {
1 : "Jan.",
2 : "Feb.",
3 : "Mar.",
4 : "Apr.",
5 : "May.",
6 : "Jun.",
7 : "Jul.",
8 : "Aug.",
9 : "Sep.",
10 : "Oct.",
11 : "Nov.",
12 : "Dec."
};
constructor (name) {
super(name);
}
Draw (context) {
let mgr = ChartManager.instance;
let area = mgr.getArea(this.getAreaName());
let timeline = mgr.getTimeline(this.getDataSourceName());
if (!area.isChanged() && !timeline.isUpdated())
return;
let ds = mgr.getDataSource(this.getDataSourceName());
if (ds.getDataCount() < 2)
return;
let timeInterval = ds.getDataAt(1).date - ds.getDataAt(0).date;
let n, cnt = TimelinePlotter.TIME_INTERVAL.length;
for (n = 0; n < cnt; n++) {
if (timeInterval < TimelinePlotter.TIME_INTERVAL[ n ])
break;
}
for (; n < cnt; n++) {
if (TimelinePlotter.TIME_INTERVAL[ n ] % timeInterval === 0)
if ((TimelinePlotter.TIME_INTERVAL[ n ] / timeInterval) * timeline.getColumnWidth() > 60)
break;
}
let first = timeline.getFirstIndex();
let last = timeline.getLastIndex();
let d = new Date();
let local_utc_diff = d.getTimezoneOffset() * 60 * 1000;
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
context.textAlign = "center";
context.textBaseline = "middle";
let lang = mgr.getLanguage();
let gridRects = [];
let top = area.getTop();
let middle = area.getMiddle();
for (let i = first; i < last; i++) {
let utcDate = ds.getDataAt(i).date;
let localDate = utcDate - local_utc_diff;
let time = new Date(utcDate);
let year = time.getFullYear();
let month = time.getMonth() + 1;
let date = time.getDate();
let hour = time.getHours();
let minute = time.getMinutes();
let text = "";
if (n < cnt) {
let m = Math.max(
TimelinePlotter.TP_DAY,
TimelinePlotter.TIME_INTERVAL[ n ]);
if (localDate % m === 0) {
if (lang === "zh-cn")
text = month.toString() + "月" + date.toString() + "日";
else if (lang === "zh-tw")
text = month.toString() + "月" + date.toString() + "日";
else if (lang === "en-us")
text = TimelinePlotter.MonthConvert[ month ] + " " + date.toString();
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
} else if (localDate % TimelinePlotter.TIME_INTERVAL[ n ] === 0) {
let strMinute = minute.toString();
if (minute < 10)
strMinute = "0" + strMinute;
text = hour.toString() + ":" + strMinute;
context.fillStyle = theme.getColor(themes.Theme.Color.Text2);
}
} else if (date === 1 && (hour < (timeInterval / TimelinePlotter.TP_HOUR))) {
if (month === 1) {
text = year.toString();
if (lang === "zh-cn")
text += "年";
else if (lang === "zh-tw")
text += "年";
} else {
if (lang === "zh-cn")
text = month.toString() + "月";
else if (lang === "zh-tw")
text = month.toString() + "月";
else if (lang === "en-us")
text = TimelinePlotter.MonthConvert[ month ];
}
context.fillStyle = theme.getColor(themes.Theme.Color.Text4);
}
if (text.length > 0) {
let x = timeline.toItemCenter(i);
gridRects.push({x : x, y : top, w : 1, h : 4});
context.fillText(text, x, middle);
}
}
if (gridRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Grid1);
Plotter.createRectangles(context, gridRects);
context.fill();
}
}
}
export class RangePlotter extends NamedObject {
constructor (name) {
super(name);
}
getRequiredWidth (context, v) {
let mgr = ChartManager.instance;
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
return context.measureText((Math.floor(v) + 0.88).toString()).width + 16;
}
Draw (context) {
let mgr = ChartManager.instance;
let areaName = this.getAreaName();
let area = mgr.getArea(areaName);
let rangeName = areaName.substring(0, areaName.lastIndexOf("Range"));
let range = mgr.getRange(rangeName);
if (range.getRange() === 0.0)
return;
let isMainRange = range.getNameObject().getCompAt(2) === "main";
if (isMainRange) {
} else {
if (!area.isChanged() && !range.isUpdated())
return;
}
let gradations = range.getGradations();
if (gradations.length === 0)
return;
let left = area.getLeft();
let right = area.getRight();
let center = area.getCenter();
let theme = mgr.getTheme(this.getFrameName());
context.font = theme.getFont(themes.Theme.Font.Default);
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = theme.getColor(themes.Theme.Color.Text2);
let gridRects = [];
for (let n in gradations) {
let y = range.toY(gradations[ n ]);
gridRects.push({x : left, y : y, w : 6, h : 1});
gridRects.push({x : right - 6, y : y, w : 6, h : 1});
context.fillText(Util.fromFloat(gradations[ n ], 2), center, y);
}
if (gridRects.length > 0) {
context.fillStyle = theme.getColor(themes.Theme.Color.Grid1);
Plotter.createRectangles(context, gridRects);
context.fill();
}
}
}
export class COrderGraphPlotter extends NamedObject {
constructor (name) {
super(name);
}
Draw (context) {
return this._Draw_(context);
}
_Draw_ (context) {
if (this.Update() === false) return;
if (this.updateData() === false) return;
this.m_top = this.m_pArea.getTop();
this.m_bottom = this.m_pArea.getBottom();
this.m_left = this.m_pArea.getLeft();
this.m_right = this.m_pArea.getRight();
context.save();
context.rect(this.m_left, this.m_top, this.m_right - this.m_left, this.m_bottom