mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
208 lines
6.1 KiB
TypeScript
208 lines
6.1 KiB
TypeScript
import { VizDisplayMode, ColorStrategy, CandleStyle } from './models.gen';
|
|
import uPlot from 'uplot';
|
|
import { colorManipulator } from '@grafana/data';
|
|
|
|
const { alpha } = colorManipulator;
|
|
|
|
export type FieldIndices = Record<string, number>;
|
|
|
|
interface RendererOpts {
|
|
mode: VizDisplayMode;
|
|
candleStyle: CandleStyle;
|
|
fields: FieldIndices;
|
|
colorStrategy: ColorStrategy;
|
|
upColor: string;
|
|
downColor: string;
|
|
flatColor: string;
|
|
volumeAlpha: number;
|
|
flatAsUp: boolean;
|
|
}
|
|
|
|
export function drawMarkers(opts: RendererOpts) {
|
|
let { mode, candleStyle, fields, colorStrategy, upColor, downColor, flatColor, volumeAlpha, flatAsUp = true } = opts;
|
|
|
|
const drawPrice = mode !== VizDisplayMode.Volume && fields.high != null && fields.low != null;
|
|
const asCandles = drawPrice && candleStyle === CandleStyle.Candles;
|
|
const drawVolume = mode !== VizDisplayMode.Candles && fields.volume != null;
|
|
|
|
function selectPath(priceDir: number, flatPath: Path2D, upPath: Path2D, downPath: Path2D, flatAsUp: boolean) {
|
|
return priceDir > 0 ? upPath : priceDir < 0 ? downPath : flatAsUp ? upPath : flatPath;
|
|
}
|
|
|
|
let tIdx = 0,
|
|
oIdx = fields.open,
|
|
hIdx = fields.high,
|
|
lIdx = fields.low,
|
|
cIdx = fields.close,
|
|
vIdx = fields.volume;
|
|
|
|
return (u: uPlot) => {
|
|
// split by discrete color to reduce draw calls
|
|
let downPath, upPath, flatPath;
|
|
// with adjusted reduced
|
|
let downPathVol, upPathVol, flatPathVol;
|
|
|
|
if (drawPrice) {
|
|
flatPath = new Path2D();
|
|
upPath = new Path2D();
|
|
downPath = new Path2D();
|
|
}
|
|
|
|
if (drawVolume) {
|
|
downPathVol = new Path2D();
|
|
upPathVol = new Path2D();
|
|
flatPathVol = new Path2D();
|
|
}
|
|
|
|
let hollowPath = new Path2D();
|
|
|
|
let ctx = u.ctx;
|
|
|
|
let tData = u.data[tIdx!];
|
|
|
|
let oData = u.data[oIdx!];
|
|
let cData = u.data[cIdx!];
|
|
|
|
let hData = drawPrice ? u.data[hIdx!] : null;
|
|
let lData = drawPrice ? u.data[lIdx!] : null;
|
|
let vData = drawVolume ? u.data[vIdx!] : null;
|
|
|
|
let zeroPx = vIdx != null ? Math.round(u.valToPos(0, u.series[vIdx!].scale!, true)) : null;
|
|
|
|
let [idx0, idx1] = u.series[0].idxs!;
|
|
|
|
let dataX = u.data[0];
|
|
let dataY = oData;
|
|
|
|
let colWidth = u.bbox.width;
|
|
|
|
if (dataX.length > 1) {
|
|
// prior index with non-undefined y data
|
|
let prevIdx = null;
|
|
|
|
// scan full dataset for smallest adjacent delta
|
|
// will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end
|
|
for (let i = 0, minDelta = Infinity; i < dataX.length; i++) {
|
|
if (dataY[i] !== undefined) {
|
|
if (prevIdx != null) {
|
|
let delta = Math.abs(dataX[i] - dataX[prevIdx]);
|
|
|
|
if (delta < minDelta) {
|
|
minDelta = delta;
|
|
colWidth = Math.abs(u.valToPos(dataX[i], 'x') - u.valToPos(dataX[prevIdx], 'x'));
|
|
}
|
|
}
|
|
|
|
prevIdx = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
let barWidth = Math.round(0.6 * colWidth);
|
|
|
|
let stickWidth = 2;
|
|
let outlineWidth = 2;
|
|
|
|
if (barWidth <= 12) {
|
|
stickWidth = outlineWidth = 1;
|
|
}
|
|
|
|
let halfWidth = Math.floor(barWidth / 2);
|
|
|
|
for (let i = idx0; i <= idx1; i++) {
|
|
let tPx = Math.round(u.valToPos(tData[i]!, 'x', true));
|
|
|
|
// current close vs prior close
|
|
let interDir = i === idx0 ? 0 : Math.sign(cData[i]! - cData[i - 1]!);
|
|
// current close vs current open
|
|
let intraDir = Math.sign(cData[i]! - oData[i]!);
|
|
|
|
// volume
|
|
if (drawVolume) {
|
|
let outerPath = selectPath(
|
|
colorStrategy === ColorStrategy.CloseClose ? interDir : intraDir,
|
|
flatPathVol as Path2D,
|
|
upPathVol as Path2D,
|
|
downPathVol as Path2D,
|
|
i === idx0 && ColorStrategy.CloseClose ? false : flatAsUp
|
|
);
|
|
|
|
let vPx = Math.round(u.valToPos(vData![i]!, u.series[vIdx!].scale!, true));
|
|
outerPath.rect(tPx - halfWidth, vPx, barWidth, zeroPx! - vPx);
|
|
}
|
|
|
|
if (drawPrice) {
|
|
let outerPath = selectPath(
|
|
colorStrategy === ColorStrategy.CloseClose ? interDir : intraDir,
|
|
flatPath as Path2D,
|
|
upPath as Path2D,
|
|
downPath as Path2D,
|
|
i === idx0 && ColorStrategy.CloseClose ? false : flatAsUp
|
|
);
|
|
|
|
// stick
|
|
let hPx = Math.round(u.valToPos(hData![i]!, u.series[hIdx!].scale!, true));
|
|
let lPx = Math.round(u.valToPos(lData![i]!, u.series[lIdx!].scale!, true));
|
|
outerPath.rect(tPx - Math.floor(stickWidth / 2), hPx, stickWidth, lPx - hPx);
|
|
|
|
let oPx = Math.round(u.valToPos(oData[i]!, u.series[oIdx!].scale!, true));
|
|
let cPx = Math.round(u.valToPos(cData[i]!, u.series[cIdx!].scale!, true));
|
|
|
|
if (asCandles) {
|
|
// rect
|
|
let top = Math.min(oPx, cPx);
|
|
let btm = Math.max(oPx, cPx);
|
|
let hgt = Math.max(1, btm - top);
|
|
outerPath.rect(tPx - halfWidth, top, barWidth, hgt);
|
|
|
|
if (colorStrategy === ColorStrategy.CloseClose) {
|
|
if (intraDir >= 0 && hgt > outlineWidth * 2) {
|
|
hollowPath.rect(
|
|
tPx - halfWidth + outlineWidth,
|
|
top + outlineWidth,
|
|
barWidth - outlineWidth * 2,
|
|
hgt - outlineWidth * 2
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
outerPath.rect(tPx - halfWidth, oPx, halfWidth, stickWidth);
|
|
outerPath.rect(tPx, cPx, halfWidth, stickWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.save();
|
|
|
|
ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
|
ctx.clip();
|
|
|
|
if (drawVolume) {
|
|
ctx.fillStyle = alpha(upColor, volumeAlpha);
|
|
ctx.fill(upPathVol as Path2D);
|
|
|
|
ctx.fillStyle = alpha(downColor, volumeAlpha);
|
|
ctx.fill(downPathVol as Path2D);
|
|
|
|
ctx.fillStyle = alpha(flatColor, volumeAlpha);
|
|
ctx.fill(flatPathVol as Path2D);
|
|
}
|
|
|
|
if (drawPrice) {
|
|
ctx.fillStyle = upColor;
|
|
ctx.fill(upPath as Path2D);
|
|
|
|
ctx.fillStyle = downColor;
|
|
ctx.fill(downPath as Path2D);
|
|
|
|
ctx.fillStyle = flatColor;
|
|
ctx.fill(flatPath as Path2D);
|
|
|
|
ctx.globalCompositeOperation = 'destination-out';
|
|
ctx.fill(hollowPath);
|
|
}
|
|
|
|
ctx.restore();
|
|
};
|
|
}
|