@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.
699 lines • 48.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TradingChart = void 0;
var d3 = __importStar(require("d3"));
var events_1 = require("events");
var types_1 = require("./types");
var utils_1 = require("./utils");
var trimLines = function (string) { return string.replace(/\n\s+/g, ''); };
var toDevicePixel = function (number) { return (number * window.devicePixelRatio); };
var TradingChart = (function () {
function TradingChart(_config, _settings, theme) {
this.dataMat = [];
this.root = undefined;
this.table = undefined;
this.scaleRowMap = {};
this.mainCanvasMap = {};
this.mainUpdateCanvasMap = {};
this.scaleYCanvasMap = {};
this.scaleYUpdateCanvasMap = {};
this.scaleXCanvas = undefined;
this.scaleXUpdateCanvas = undefined;
this.zoomInterpolator = d3.interpolateNumber(0, 0);
this.panOffset = 0;
this.panOffsetSaved = 0;
this.d3xScale = d3.scaleBand();
this.d3yScaleMap = {};
this.zoomEventEmitter = new events_1.EventEmitter();
this.panEventEmitter = new events_1.EventEmitter();
this.annotations = [];
this.data = {};
this.config = _config;
var scaleIds = Object.keys(_config);
this.settings = Object.assign({}, (theme === 'dark' ? types_1.DarkThemeChartSettings : types_1.LightThemeChartSettings), _settings, { scaleSectionRatio: (1 / scaleIds.length) });
(0, types_1.debug)(this);
}
TradingChart.prototype.initialize = function () {
var _this = this;
var scaleIds = Object.keys(this.config);
this.root = d3.select(document.createElement('div')).style('position', 'relative');
var table = this.root.append('table').attr('class', 'chartTable').attr('style', trimLines("\n position: absolute;\n width: ".concat(this.settings.width, "px;\n height: ").concat(this.settings.height, "px;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n border: none;\n border-collapse: collapse;\n border-spacing: 0;\n line-height: 0px;\n margin: 0;\n padding: 0;\n background: ").concat(this.settings.background, "\n ")));
this.table = table;
var chartSectionWidth = this.settings.width * this.settings.plotSectionRatio;
var scaleSectionWidth = this.settings.width - chartSectionWidth;
var chartHeight = this.settings.height - scaleIds.length - types_1.X_AXIS_HEIGHT_PX;
scaleIds.map(function (scaleId, i, _) {
var _a, _b;
var sectionHeight = chartHeight * (((_a = (_this.settings.subGraph || {})[scaleId]) === null || _a === void 0 ? void 0 : _a.scaleSectionRatio) || _this.settings.scaleSectionRatio) +
(((_b = (_this.settings.subGraph || {})[scaleId]) === null || _b === void 0 ? void 0 : _b.deltaHeight) || 0);
var tr = table.append('tr').attr('class', "subChart ".concat(scaleId));
var graphTd = tr.append('td').attr('class', "section ".concat(scaleId)).attr('style', trimLines("\n border: none;\n line-height: 0px;\n margin: 0;\n padding: 0;\n text-align: left;\n vertical-align: top;\n cursor: crosshair;\n overflow: hidden;\n width: ".concat(chartSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n ")));
var graphContainer = graphTd.append('div').attr('class', "canvasContainer ".concat(scaleId)).attr('style', trimLines("\n width:100%;\n height:100%;\n position: relative;\n overflow: hidden;\n "));
_this.mainCanvasMap[scaleId] = graphContainer.append('canvas').attr('class', "mainCanvas ".concat(scaleId)).attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(chartSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n ")))
.attr('width', toDevicePixel(chartSectionWidth))
.attr('height', toDevicePixel(sectionHeight))
.attr('scaleId', scaleId).attr('canvasType', 'mainCanvas');
_this.mainUpdateCanvasMap[scaleId] = graphContainer.append('canvas').attr('class', "mainUpdateCanvas ".concat(scaleId)).attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(chartSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n z-index: 1;\n ")))
.attr('width', toDevicePixel(chartSectionWidth))
.attr('height', toDevicePixel(sectionHeight))
.attr('scaleId', scaleId)
.attr('canvasType', 'mainUpdateCanvas');
var rightScaleTd = tr.append('td').attr('class', "scale ".concat(scaleId)).attr('style', trimLines("\n border-left: 1px solid ".concat(_this.settings.graphSeparatorColor, ";\n line-height: 0px;\n margin: 0;\n padding: 0;\n text-align: left;\n vertical-align: top;\n width: ").concat(scaleSectionWidth, "px;\n min-width: ").concat(scaleSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n ")));
var rightScaleContainer = rightScaleTd.append('div').attr('class', "scaleContainer ".concat(scaleId)).attr('style', trimLines("\n width:100%;\n height:100%;\n position: relative;\n overflow: hidden;\n "));
_this.scaleYCanvasMap[scaleId] = rightScaleContainer.append('canvas').attr('class', "scaleYCanvas ".concat(scaleId)).attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(scaleSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n ")))
.attr('width', toDevicePixel(scaleSectionWidth))
.attr('height', toDevicePixel(sectionHeight))
.attr('scaleId', scaleId).attr('canvasType', 'scaleYCanvas');
_this.scaleYUpdateCanvasMap[scaleId] = rightScaleContainer.append('canvas').attr('class', "scaleYUpdateCanvas ".concat(scaleId)).attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(scaleSectionWidth, "px;\n height: ").concat(sectionHeight, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n z-index: 1;\n cursor: ns-resize;\n ")))
.attr('width', toDevicePixel(scaleSectionWidth))
.attr('height', toDevicePixel(sectionHeight))
.attr('scaleId', scaleId)
.attr('canvasType', 'scaleYUpdateCanvas');
_this.scaleRowMap[scaleId] = [graphTd, rightScaleTd];
if (i < _.length - 1) {
var separatorhandle = table
.append('tr').attr('style', "height: 1px;")
.append('td').attr('colspan', '2').attr('style', trimLines("\n margin: 0;\n padding: 0;\n position: relative;\n background: ".concat(_this.settings.graphSeparatorColor, "\n ")))
.append('div')
.attr('class', 'separatorHandle')
.attr('style', trimLines("\n height: 9px;\n left: 0;\n position: absolute;\n top: -4px;\n width: 100%;\n z-index: 50;\n cursor: row-resize;\n "))
.attr('draggable', true)
.attr('scale1', _[i])
.attr('scale2', _[i + 1]);
separatorhandle.on('drag', function (event) {
if (event.pageY && event.offsetY) {
var scale1 = _[i], scale2 = _[i + 1];
var scale1tds = _this.scaleRowMap[scale1];
var scale2tds = _this.scaleRowMap[scale2];
var scale1newH = scale1tds.reduce(function (_, td) {
var h = parseFloat(td.style('height'));
var newH = h + event.offsetY;
td.style('height', "".concat(newH, "px"));
return newH;
}, 0);
var scale2newH = scale2tds.reduce(function (_, td) {
var h = parseFloat(td.style('height'));
var newH = h - event.offsetY;
td.style('height', "".concat(newH, "px"));
return newH;
}, 0);
_this.mainCanvasMap[scale1].style('height', "".concat(scale1newH, "px")).attr('height', toDevicePixel(scale1newH));
_this.mainUpdateCanvasMap[scale1].style('height', "".concat(scale1newH, "px")).attr('height', toDevicePixel(scale1newH));
_this.scaleYCanvasMap[scale1].style('height', "".concat(scale1newH, "px")).attr('height', toDevicePixel(scale1newH));
_this.scaleYUpdateCanvasMap[scale1].style('height', "".concat(scale1newH, "px")).attr('height', toDevicePixel(scale1newH));
_this.mainCanvasMap[scale2].style('height', "".concat(scale2newH, "px")).attr('height', toDevicePixel(scale2newH));
_this.mainUpdateCanvasMap[scale2].style('height', "".concat(scale2newH, "px")).attr('height', toDevicePixel(scale2newH));
_this.scaleYCanvasMap[scale2].style('height', "".concat(scale2newH, "px")).attr('height', toDevicePixel(scale2newH));
_this.scaleYUpdateCanvasMap[scale2].style('height', "".concat(scale2newH, "px")).attr('height', toDevicePixel(scale2newH));
_this.redrawMainCanvas();
}
});
}
});
var tr = this.table
.append('tr').attr('style', trimLines("\n border-top: 1px solid ".concat(this.settings.graphSeparatorColor, ";\n ")));
var xdiv = tr.append('td').attr('style', trimLines("\n line-height: 0px;\n margin: 0;\n padding: 0;\n text-align: left;\n vertical-align: top;\n overflow: hidden;\n width: ".concat(chartSectionWidth, "px;\n min-width: ").concat(chartSectionWidth, "px;\n ")))
.append('div')
.attr('class', "xScaleContainer")
.attr('style', trimLines("\n width:100%;\n height:100%;\n position: relative;\n overflow: hidden;\n "));
this.scaleXCanvas = xdiv.append('canvas')
.attr('class', "scaleXCanvas")
.attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(chartSectionWidth, "px;\n height: ").concat(types_1.X_AXIS_HEIGHT_PX, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n ")))
.attr('width', toDevicePixel(chartSectionWidth))
.attr('height', toDevicePixel(types_1.X_AXIS_HEIGHT_PX));
this.scaleXUpdateCanvas = xdiv.append('canvas')
.attr('class', "scaleXUpdateCanvas")
.attr('style', trimLines("\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n width: ".concat(chartSectionWidth, "px;\n height: ").concat(types_1.X_AXIS_HEIGHT_PX, "px;\n position: absolute;\n left: 0px;\n top: 0px;\n z-index: 1;\n cursor: ew-resize;\n ")))
.attr('width', toDevicePixel(chartSectionWidth))
.attr('height', toDevicePixel(types_1.X_AXIS_HEIGHT_PX));
tr.append('td').attr('style', trimLines("\n border-left: 1px solid ".concat(this.settings.graphSeparatorColor, ";\n line-height: 0px;\n margin: 0;\n padding: 0;\n ")));
scaleIds.map(function (scaleId) {
var d3Canvas = _this.mainUpdateCanvasMap[scaleId];
d3Canvas.on('mousedown', function (event) {
event.preventDefault();
_this.currentMouseDownStart = { scaleId: scaleId, x: event.x, y: event.y };
_this.scaleRowMap[scaleId][0].style('cursor', 'grabbing');
});
d3Canvas.on('mouseup', function (event) {
event.preventDefault();
_this.currentMouseDownStart = undefined;
_this.panOffsetSaved = _this.panOffset;
_this.scaleRowMap[scaleId][0].style('cursor', 'crosshair');
});
d3Canvas.on('mousemove', function (event) {
event.preventDefault();
_this.clearUpdateCanvas();
if (_this.currentMouseDownStart) {
_this.currentMouseDownStart.dx = event.x - _this.currentMouseDownStart.x;
_this.currentMouseDownStart.dy = event.y - _this.currentMouseDownStart.y;
_this.pan(_this.currentMouseDownStart.dx || 0, _this.currentMouseDownStart.dy || 0);
}
else {
var canvas = _this.mainUpdateCanvasMap[scaleId].node();
var rect = canvas === null || canvas === void 0 ? void 0 : canvas.getBoundingClientRect();
var x = rect ? event.x - rect.left : event.x;
var y = rect ? event.y - rect.top : event.y;
if (!_this.mousePosition)
_this.mousePosition = { x: x, y: y, scaleId: scaleId };
else {
_this.mousePosition.x = x;
_this.mousePosition.y = y;
_this.mousePosition.scaleId = scaleId;
}
_this.redrawUpdateCanvas();
}
});
d3Canvas.on('mouseout', function () {
_this.clearUpdateCanvas();
});
});
this.table.on('wheel', function (event) {
event.preventDefault();
if (event.deltaY < 0)
_this.zoom(_this.settings.wheelZoomSensitivity);
else
_this.zoom(-_this.settings.wheelZoomSensitivity);
});
this.redrawMainCanvas();
if (this.settings.watermarkText) {
this.root.append('div')
.attr('class', 'watermark')
.attr('style', trimLines("\n position: absolute;\n width: ".concat(this.settings.width, "px;\n height: ").concat(this.settings.height, "px;\n text-align: center;\n vertical-align: middle;\n line-height: ").concat(this.settings.height, "px;\n font-size: 3rem;\n overflow: hidden;\n color: #00000012;\n ")))
.append('span')
.attr('style', 'white-space:break-spaces')
.html(this.settings.watermarkText);
}
};
TradingChart.prototype.setConfig = function (_config) {
var _this = this;
this.config = _config;
this.settings.scaleSectionRatio = 1 / Object.keys(_config).length;
if (this.settings.subGraph) {
Object.keys(this.settings.subGraph).map(function (sid) {
delete _this.settings.subGraph[sid].scaleSectionRatio;
});
}
};
TradingChart.prototype.updateTheme = function (theme) {
this.settings = Object.assign({}, this.settings, (theme === 'dark' ? types_1.DarkThemeChartSettings : types_1.LightThemeChartSettings));
this.redrawMainCanvas();
};
TradingChart.prototype.getColorPallet = function (scaleId) {
var colorpallet = (scaleId ? ((this.settings.subGraph || {})[scaleId] || {}).colorPallet : undefined) || this.settings.colorPallet || d3.schemePaired;
return colorpallet;
};
TradingChart.prototype.setData = function (_data) {
this.data = _data;
this.recalculateData();
this.zoomInterpolator = d3.interpolateNumber(this.dataMat.length, types_1.MIN_ZOOM_POINTS);
this.updateWindowFromZoomPan();
this.redrawMainCanvas();
};
TradingChart.prototype.recalculateData = function () {
var _this = this;
if (this.data && Object.keys(this.data).length > 0) {
var dtgrouped_1 = Object.keys(this.config).reduce(function (rv, scaleId) {
var subgraph = _this.config[scaleId];
Object.keys(subgraph).map(function (plotName, i) {
var plotConf = subgraph[plotName];
var d = _this.data[plotConf.dataId] || [];
var colorpallet = _this.getColorPallet(scaleId);
var defaultColor = colorpallet[i % colorpallet.length];
var lastBaseY = undefined;
d.map(function (d) {
var _a, _b, _e;
var ts = plotConf.tsValue(d);
var data = plotConf.data(d);
var color = (plotConf.color) ? (typeof (plotConf.color) === 'function' ? plotConf.color(d) : plotConf.color) : defaultColor;
var baseY = (typeof (plotConf.baseY) === 'undefined') ? lastBaseY : (typeof (plotConf.baseY) === 'function' ? plotConf.baseY(d) : plotConf.baseY);
lastBaseY = baseY;
if (!rv[ts.getTime()])
rv[ts.getTime()] = (_a = {}, _a[scaleId] = (_b = {}, _b[plotName] = { d: data, color: color, baseY: baseY }, _b), _a);
else if (!rv[ts.getTime()][scaleId])
rv[ts.getTime()][scaleId] = (_e = {}, _e[plotName] = { d: data, color: color, baseY: baseY }, _e);
else if (!rv[ts.getTime()][scaleId][plotName])
rv[ts.getTime()][scaleId][plotName] = { d: data, color: color, baseY: baseY };
});
});
return rv;
}, {});
var xDomainValus = Object.keys(dtgrouped_1).sort(function (a, b) { return ((+a) - (+b)); });
this.dataMat = xDomainValus.map(function (tsk) {
return { ts: new Date(+tsk), data: dtgrouped_1[tsk] };
});
}
};
TradingChart.prototype.setAnnotations = function (_annotations) {
var _this = this;
this.annotations = _annotations.map(function (a, i) {
var _a;
if (typeof (a.color) === 'undefined') {
var colorPallet = _this.getColorPallet(a.scaleId);
a.color = colorPallet[i];
if (typeof (a.areaColor) === 'undefined') {
a.areaColor = ((_a = d3.color(colorPallet[i])) === null || _a === void 0 ? void 0 : _a.copy({ opacity: 0.2 }).formatHex8()) || colorPallet[i];
}
}
if (typeof (a.text) === 'undefined')
a.text = "".concat(i + 1);
a.x = (a.x || []).map(function (x) { return ((Object.prototype.toString.call(x) === '[object Date]') ? x : new Date(x)); });
if (typeof (a.textColor) === 'undefined')
a.textColor = _this.settings.crossHairContrastColor;
return a;
});
this.redrawMainCanvas();
};
TradingChart.prototype.getWindowedData = function () {
return this.dataMat.slice(this.dataWindowStartIndex || 0, this.dataWindowEndIndex || this.dataMat.length);
};
TradingChart.prototype.updateWindowFromZoomPan = function () {
var windowLength = this.zoomInterpolator(this.settings.zoomLevel);
this.dataWindowStartIndex = Math.max(0, this.dataMat.length - 1 - this.panOffset - windowLength);
this.dataWindowEndIndex = Math.min(this.dataMat.length, this.dataMat.length - this.panOffset);
};
TradingChart.prototype.zoom = function (step) {
this.settings.zoomLevel = Math.max(Math.min(this.settings.zoomLevel + step, 1), 0.001);
this.updateWindowFromZoomPan();
this.redrawMainCanvas();
this.zoomEventEmitter.emit(types_1.EVENT_ZOOM);
};
TradingChart.prototype.zoomPanToRange = function (start, end) {
var _this = this;
if (this.dataMat && this.dataMat.length > 0) {
var _a = this.dataMat.reduce(function (rv, mat, i) {
if (mat.ts.getTime() <= start.getTime())
rv[0] = Math.max(i - 1, 0);
if (mat.ts.getTime() <= end.getTime())
rv[1] = Math.min(i + 1, _this.dataMat.length - 1);
return rv;
}, [0, this.dataMat.length - 1]), i1 = _a[0], i2 = _a[1];
this.panOffset = i2;
this.dataWindowEndIndex = i2 - 1;
this.dataWindowStartIndex = i1;
var windowLength = this.dataWindowEndIndex - this.dataWindowStartIndex;
var zoomLevel = (windowLength - this.dataMat.length) / (types_1.MIN_ZOOM_POINTS - this.dataMat.length);
(0, types_1.log)("Zoom level determined: ".concat(zoomLevel));
this.settings.zoomLevel = zoomLevel;
this.redrawMainCanvas();
this.zoomEventEmitter.emit(types_1.EVENT_ZOOM);
}
};
TradingChart.prototype.pan = function (dx, dy) {
var xBandW = this.d3xScale.step();
var maxBarsToscroll = Math.floor(dx / xBandW);
var windowLength = this.zoomInterpolator(this.settings.zoomLevel);
this.panOffset = Math.min(Math.max(0, this.panOffsetSaved + maxBarsToscroll), this.panOffset + windowLength);
this.updateWindowFromZoomPan();
this.redrawMainCanvas();
this.panEventEmitter.emit(types_1.EVENT_PAN);
};
TradingChart.prototype.updateSettings = function (_settings) {
this.recalculateData();
this.settings = Object.assign(this.settings || {}, _settings);
this.updateWindowFromZoomPan();
this.redrawMainCanvas();
};
TradingChart.prototype.updateDomains = function (windowedData) {
var _this = this;
this.d3xScale.domain(windowedData.map(function (_) { return _.ts; }));
var maxminMap = windowedData.reduce(function (rv, d) {
var scaleIds = Object.keys(d.data);
scaleIds.map(function (scaleId) {
var max = -Infinity, min = Infinity;
Object.keys(d.data[scaleId]).map(function (plotName) {
var plotd = d.data[scaleId][plotName];
max = Math.max(max, typeof (plotd.d) === 'object' ? plotd.d.l : plotd.d, plotd.baseY || -Infinity);
min = Math.min(min, typeof (plotd.d) === 'object' ? plotd.d.l : plotd.d, plotd.baseY || Infinity);
});
if (!rv[scaleId])
rv[scaleId] = { max: max, min: min };
else {
rv[scaleId].max = Math.max(rv[scaleId].max, max);
rv[scaleId].min = Math.min(rv[scaleId].min, min);
}
});
return rv;
}, {});
Object.keys(maxminMap).map(function (scaleId) {
var _a = maxminMap[scaleId], max = _a.max, min = _a.min;
var yScaleDomainPaddingLength = (max - min) * (((_this.settings.subGraph || {})[scaleId] || {}).yScalePaddingPct || _this.settings.yScalePaddingPct);
if (!_this.d3yScaleMap[scaleId])
_this.d3yScaleMap[scaleId] = d3.scaleLinear();
_this.d3yScaleMap[scaleId]
.domain([min - yScaleDomainPaddingLength, max + yScaleDomainPaddingLength]);
});
};
TradingChart.prototype.redrawMainCanvas = function () {
var _this = this;
if (this.dataMat && this.dataMat.length && this.scaleXCanvas) {
var windowedData_1 = this.getWindowedData();
this.updateDomains(windowedData_1);
var xScaleCanvas = this.scaleXCanvas.node();
var xScaleCanvasWidth_1 = +this.scaleXCanvas.attr('width');
var xScaleCanvasHeight_1 = +this.scaleXCanvas.attr('height');
var xScaleFormat_1 = d3.timeFormat(this.settings.xScaleFormat);
var xScaleCanvasCtx_1 = xScaleCanvas.getContext('2d');
(0, utils_1.clearCanvas)(xScaleCanvasCtx_1, 0, 0, xScaleCanvasWidth_1, xScaleCanvasHeight_1);
var callOnXTicks = function (direction, fn) {
var domain = _this.d3xScale.domain();
var totalDomainLength = domain.length;
var stepsize = Math.floor(totalDomainLength / _this.settings.xGridInterval);
for (var i = 0; i < _this.settings.xGridInterval; i++) {
var index = i * stepsize;
var j = (direction === 'backward') ? totalDomainLength - 1 - index : index;
fn(domain[j], i, domain);
}
};
Object.keys(this.config).map(function (scaleId) {
var subgraphConfig = _this.config[scaleId];
var canvas = _this.mainCanvasMap[scaleId].node();
var canvasWidth = +_this.mainCanvasMap[scaleId].attr('width');
var canvasHeight = +_this.mainCanvasMap[scaleId].attr('height');
var yScaleCanvas = _this.scaleYCanvasMap[scaleId].node();
var yScaleCanvasWidth = +_this.scaleYCanvasMap[scaleId].attr('width');
var yScaleCanvasHeight = +_this.scaleYCanvasMap[scaleId].attr('height');
_this.d3yScaleMap[scaleId].range([canvasHeight, 0]);
var d3yScale = _this.d3yScaleMap[scaleId];
_this.d3xScale
.range([0, xScaleCanvasWidth_1])
.padding(_this.settings.xScalePadding);
var mainCanvasCtx = canvas.getContext('2d');
var yScaleCanvasCtx = yScaleCanvas.getContext('2d');
(0, utils_1.clearCanvas)(mainCanvasCtx, 0, 0, canvasWidth, canvasHeight);
(0, utils_1.clearCanvas)(yScaleCanvasCtx, 0, 0, yScaleCanvasWidth, yScaleCanvasHeight);
Object.keys(subgraphConfig).map(function (plotName) {
var plotConfig = subgraphConfig[plotName];
var subGraphSettings = (_this.settings.subGraph || {})[scaleId] || {};
var bandW = _this.d3xScale.bandwidth();
var filteredWindowedData = windowedData_1.filter(function (d) { return (d.data[scaleId] && d.data[scaleId][plotName] && typeof (d.data[scaleId][plotName].d) !== 'undefined'); });
switch (plotConfig.type) {
case 'candle':
filteredWindowedData.map(function (d) {
var _d = d.data[scaleId][plotName];
if (typeof (_d.d) !== 'undefined') {
var _c = _d.d;
var x = _this.d3xScale(d.ts);
(0, utils_1.drawCandle)(mainCanvasCtx, _d.color, x, d3yScale(_c.o), d3yScale(_c.c), x + bandW / 2, d3yScale(_c.h), d3yScale(_c.l), bandW);
}
});
break;
case 'dashed-line':
case 'dotted-line':
case 'solid-line':
(0, utils_1.drawLine)(mainCanvasCtx, filteredWindowedData[filteredWindowedData.length - 1].data[scaleId][plotName].color, plotConfig.type, (subGraphSettings.lineWidth || _this.settings.lineWidth), filteredWindowedData.map(function (d) {
var _d = d.data[scaleId][plotName];
var x = _this.d3xScale(d.ts);
var y = d3yScale(_d.d);
return [x + bandW / 2, y];
}));
break;
case 'bar':
filteredWindowedData.map(function (d) {
var _d = d.data[scaleId][plotName];
var x = _this.d3xScale(d.ts);
var y = d3yScale(_d.d);
(0, utils_1.drawBar)(mainCanvasCtx, _d.color, x, y, bandW, canvasHeight - y);
});
break;
case 'var-bar':
filteredWindowedData.map(function (d) {
var _d = d.data[scaleId][plotName];
var x = _this.d3xScale(d.ts);
var y = d3yScale(_d.d);
var baseY = typeof (_d.baseY) !== 'undefined' ? d3yScale(_d.baseY) : canvasHeight;
(0, utils_1.drawBar)(mainCanvasCtx, _d.color, x, y, bandW, baseY - y);
});
break;
case 'area':
var color = d3.color(filteredWindowedData[filteredWindowedData.length - 1].data[scaleId][plotName].color);
var areaColor = plotConfig.areaColor ? [plotConfig.areaColor, plotConfig.areaColor] : [color.copy({ opacity: 0.6 }).formatHex8(), color.copy({ opacity: 0.2 }).formatHex8()];
(0, utils_1.drawArea)(mainCanvasCtx, color.formatHex8(), plotConfig.colorBaseY, (subGraphSettings.lineWidth || _this.settings.lineWidth), areaColor, filteredWindowedData.map(function (d) {
var _d = d.data[scaleId][plotName];
var x = _this.d3xScale(d.ts);
var y = d3yScale(_d.d);
var baseY = typeof (_d.baseY) !== 'undefined' ? d3yScale(_d.baseY) : canvasHeight;
return [x + bandW / 2, y, baseY];
}));
break;
}
});
var yScaleTicksCount = ((_this.settings.subGraph || {})[scaleId] || {}).yScaleTickCount || _this.settings.yScaleTickCount;
var ticks = d3yScale.ticks(yScaleTicksCount);
var yScaleDomainSize = d3yScale.domain().reduce(function (rv, d, i, _) {
if (i === 0)
rv = d;
else if (i === _.length - 1) {
return Math.abs(rv - d);
}
return rv;
}, 0);
var tickFormat = d3yScale.tickFormat(yScaleTicksCount, ((_this.settings.subGraph || {})[scaleId] || {}).yScaleFormat ||
((yScaleDomainSize > 1000) ? '~s' : (yScaleDomainSize < 10) ? '.2~f' : 'd'));
ticks.map(function (tick) {
var y = d3yScale(tick);
(0, utils_1.drawText)(yScaleCanvasCtx, tickFormat(tick), 1, y, 0, _this.settings.scaleFontColor, _this.settings.scaleFontSize, 'left');
});
var yScaleTitle = ((_this.settings.subGraph || {})[scaleId] || {}).yScaleTitle || _this.settings.yScaleTitle;
if (yScaleTitle)
(0, utils_1.drawCenterPivotRotatedText)(yScaleCanvasCtx, yScaleTitle, yScaleCanvasWidth - parseInt(_this.settings.scaleFontSize), yScaleCanvasHeight / 2, 270, _this.settings.scaleFontColor, _this.settings.scaleFontSize);
var title = ((_this.settings.subGraph || {})[scaleId] || {}).title;
if (title) {
var titleFontColor = ((_this.settings.subGraph || {})[scaleId] || {}).titleFontColor || _this.settings.scaleFontColor;
var titleFontSize = ((_this.settings.subGraph || {})[scaleId] || {}).titleFontSize || _this.settings.scaleFontSize;
var titlePosition = ((_this.settings.subGraph || {})[scaleId] || {}).titlePlacement || _this.settings.titlePlacement || 'top-right';
var legendMargin = ((_this.settings.subGraph || {})[scaleId] || {}).legendMargin || _this.settings.legendMargin || [10, 10, 10];
var legendMarginType = typeof (legendMargin);
switch (titlePosition) {
case 'top-center':
(0, utils_1.drawText)(mainCanvasCtx, title, canvasWidth / 2, legendMarginType === 'number' ? legendMargin : legendMargin[0], undefined, titleFontColor, titleFontSize, 'center', 'top');
break;
case 'top-right':
(0, utils_1.drawText)(mainCanvasCtx, title, canvasWidth - (legendMarginType === 'number' ? legendMargin : legendMargin[2]), legendMarginType === 'number' ? legendMargin : legendMargin[0], undefined, titleFontColor, titleFontSize, 'right', 'top');
break;
case 'top-left':
(0, utils_1.drawText)(mainCanvasCtx, title, legendMarginType === 'number' ? legendMargin : legendMargin[1], legendMarginType === 'number' ? legendMargin : legendMargin[0], undefined, titleFontColor, titleFontSize, 'left', 'top');
break;
}
}
var annotations = _this.annotations.filter(function (a) { return ((a.scaleId === scaleId) || (typeof (a.scaleId) === 'undefined')); });
if (annotations && annotations.length > 0) {
var lineWidth_1 = _this.settings.annotationLineWidth;
var annotationFontSize_1 = _this.settings.annotationFontSize;
var xBandW_1 = _this.d3xScale.bandwidth();
annotations.map(function (annotation) {
var x = annotation.x.map(function (_) { return _this.d3xScale(_); });
var y = annotation.y.map(function (_) { return d3yScale(_); });
switch (annotation.type) {
case 'xRange':
if (x.length > 1)
(0, utils_1.drawXRange)(mainCanvasCtx, x[0], x[1], canvasHeight, annotation.color, lineWidth_1, annotation.areaColor, annotation.text, annotationFontSize_1);
break;
case 'xSingle':
if (x.length > 0)
(0, utils_1.drawXSingle)(mainCanvasCtx, x[0] + xBandW_1 / 2, canvasHeight, annotation.color, lineWidth_1, annotation.text, annotationFontSize_1);
break;
case 'flag':
x.map(function (x, i) {
(0, utils_1.drawFlagMark)(mainCanvasCtx, x + xBandW_1 / 2, y[i], annotation.text, annotation.direction, annotation.color, annotation.textColor, annotationFontSize_1);
});
break;
case 'rect':
(0, utils_1.drawRectLimiterMark)(mainCanvasCtx, x[0], x[1], y[0], y[1], y[2], y[3], annotation.color, lineWidth_1, annotation.areaColor, annotation.text, annotationFontSize_1);
break;
}
});
}
});
callOnXTicks('forward', function (d, i, _) {
var xscaleY = xScaleCanvasHeight_1 / 2;
var x = _this.d3xScale(d);
(0, utils_1.drawText)(xScaleCanvasCtx_1, xScaleFormat_1(d), x, xscaleY, 0, _this.settings.scaleFontColor, _this.settings.scaleFontSize, 'left');
});
this.annotations.filter(function (a) { return ((a.type === 'xRange' || a.type === 'xSingle') && a.x.length > 0); }).map(function (annotation) {
if (annotation.showXValue) {
try {
var x = annotation.x.map(function (_) { return _this.d3xScale(_); });
var txt = annotation.x.map(function (_) { return xScaleFormat_1(_); });
var rh = parseInt(_this.settings.annotationFontSize);
switch (annotation.type) {
case 'xRange':
(0, utils_1.drawBoxFilledText)(xScaleCanvasCtx_1, txt[0], annotation.color, annotation.textColor, x[0], 5, x[0], 0, undefined, rh + 10, _this.settings.annotationFontSize, 'right', 'top');
(0, utils_1.drawBoxFilledText)(xScaleCanvasCtx_1, txt[1], annotation.color, annotation.textColor, x[1], 5, x[1], 0, undefined, rh + 10, _this.settings.annotationFontSize, 'left', 'top');
break;
case 'xSingle':
(0, utils_1.drawBoxFilledText)(xScaleCanvasCtx_1, txt[0], annotation.color, annotation.textColor, x[0] + _this.d3xScale.bandwidth() / 2, 5, undefined, 0, undefined, rh + 10, _this.settings.annotationFontSize, 'center', 'top');
break;
}
}
catch (e) {
(0, types_1.error)('Error while drawing annotation', e);
}
}
});
}
};
TradingChart.prototype.redrawUpdateCanvas = function () {
var _this = this;
var _a, _b;
if (this.scaleXUpdateCanvas)
try {
var windowedData_2 = this.getWindowedData();
var xScaleCtx = (_a = this.scaleXUpdateCanvas.node()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
var xstep = this.d3xScale.step();
var bandW = this.d3xScale.bandwidth();
var domainVal_1 = this.d3xScale.domain()[Math.floor(toDevicePixel(((_b = this.mousePosition) === null || _b === void 0 ? void 0 : _b.x) || 0) / xstep)];
var x_1 = (this.d3xScale(domainVal_1) || 0) + bandW / 2;
if (xScaleCtx) {
var text = " ".concat(d3.timeFormat(this.settings.xScaleCrossHairFormat)(domainVal_1), " ");
(0, utils_1.drawBoxFilledText)(xScaleCtx, text, this.settings.crossHairColor, this.settings.crossHairContrastColor, x_1, 5, undefined, 0, undefined, parseInt(this.settings.scaleFontSize) + 10, this.settings.scaleFontSize, 'center', 'top');
}
Object.keys(this.config).map(function (scaleId) {
var _a, _b, _e, _f;
var canvas = _this.mainUpdateCanvasMap[scaleId];
var ctx = (_a = canvas.node()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
var canvasWidth = +canvas.attr('width');
var canvasHeight = +canvas.attr('height');
if (ctx) {
(0, utils_1.drawLine)(ctx, _this.settings.crossHairColor, 'dotted-line', _this.settings.crossHairWidth, [[x_1, 0], [x_1, canvasHeight]]);
if (((_b = _this.mousePosition) === null || _b === void 0 ? void 0 : _b.scaleId) === scaleId) {
var y = toDevicePixel(((_e = _this.mousePosition) === null || _e === void 0 ? void 0 : _e.y) || 0);
var ydomainVal = _this.d3yScaleMap[scaleId].invert(y);
var yformatter = d3.format(((_this.settings.subGraph || {})[scaleId] || {}).crossHairYScaleFormat || _this.settings.crossHairYScaleFormat);
var yscalecanvas = _this.scaleYUpdateCanvasMap[scaleId];
var yscalectx = (_f = yscalecanvas.node()) === null || _f === void 0 ? void 0 : _f.getContext('2d');
var yscalecanvasWidth = +yscalecanvas.attr('width');
(0, utils_1.drawLine)(ctx, _this.settings.crossHairColor, 'dotted-line', _this.settings.crossHairWidth, [[0, y], [canvasWidth, y]]);
if (yscalectx) {
(0, utils_1.drawBoxFilledText)(yscalectx, yformatter(ydomainVal), _this.settings.crossHairColor, _this.settings.crossHairContrastColor, 5, y, 0, y - parseInt(_this.settings.scaleFontSize) / 2 - 5, toDevicePixel(yscalecanvasWidth), parseInt(_this.settings.scaleFontSize) + 10, _this.settings.scaleFontSize, 'left', 'middle');
}
}
var matData_1 = windowedData_2.find(function (v) { return v.ts.getTime() === domainVal_1.getTime(); });
if (matData_1) {
var plots_1 = Object.keys(_this.config[scaleId]);
var plotVals = plots_1.map(function (plot) { return ((matData_1.data[scaleId] || {})[plot] || {}); }).map(function (d, i) { return ({ name: plots_1[i], d: d }); });
if (plotVals.length > 0) {
var legendFontSize_1 = ((_this.settings.subGraph || {})[scaleId] || {}).legendFontSize || _this.settings.legendFontSize;
var legendPosition_1 = ((_this.settings.subGraph || {})[scaleId] || {}).legendPosition || _this.settings.legendPosition || 'top-left';
var legendMargin_1 = ((_this.settings.subGraph || {})[scaleId] || {}).legendMargin || _this.settings.legendMargin || [10, 10, 10];
var legendMarginType_1 = typeof (legendMargin_1);
var formatter_1 = d3.format(((_this.settings.subGraph || {})[scaleId] || {}).legendFormat || _this.settings.legendFormat);
plotVals.map(function (plotLegendVal, i) {
var y = (i + 1) * (legendMarginType_1 === 'number' ? legendMargin_1 : legendMargin_1[0]) + (i * parseInt(legendFontSize_1));
var legendText = typeof (plotLegendVal.d.d) === 'object' ? "O ".concat(plotLegendVal.d.d.o, " H ").concat(plotLegendVal.d.d.h, " L ").concat(plotLegendVal.d.d.l, " C ").concat(plotLegendVal.d.d.c) : "".concat(plotLegendVal.name, ": ").concat(formatter_1(plotLegendVal.d.d), " ").concat((typeof (plotLegendVal.d.baseY) !== 'undefined') ? " BaseY: ".concat(formatter_1(plotLegendVal.d.baseY)) : '');
switch (legendPosition_1) {
case 'top-right':
(0, utils_1.drawText)(ctx, legendText, canvasWidth - (legendMarginType_1 === 'number' ? legendMargin_1 : legendMargin_1[2]), y, undefined, plotLegendVal.d.color, legendFontSize_1, 'right', 'top');
break;
case 'top-left':
(0, utils_1.drawText)(ctx, legendText, legendMarginType_1 === 'number' ? legendMargin_1 : legendMargin_1[1], y, undefined, plotLegendVal.d.color, legendFontSize_1, 'left', 'top');
break;
}
});
}
}
}
});
}
catch (e) {
(0, types_1.log)(e);
}
};
TradingChart.prototype.clearUpdateCanvas = function () {
var _this = this;
var _a;
var scaleIds = Object.keys(this.config);
scaleIds.map(function (scaleId) {
var _a, _b;
var canvas = _this.mainUpdateCanvasMap[scaleId];
var canvasCtx = (_a = canvas.node()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
var canvasWidth = +canvas.attr('width');
var canvasHeight = +canvas.attr('height');
if (canvasCtx)
(0, utils_1.clearCanvas)(canvasCtx, 0, 0, canvasWidth, canvasHeight);
var yScalecanvas = _this.scaleYUpdateCanvasMap[scaleId];
var yScalecanvasCtx = (_b = yScalecanvas.node()) === null || _b === void 0 ? void 0 : _b.getContext('2d');
var yScalecanvasWidth = +yScalecanvas.attr('width');
var yScalecanvasHeight = +yScalecanvas.attr('height');
if (yScalecanvasCtx)
(0, utils_1.clearCanvas)(yScalecanvasCtx, 0, 0, yScalecanvasWidth, yScalecanvasHeight);
});
if (this.scaleXUpdateCanvas) {
var xScalecanvasCtx = (_a = this.scaleXUpdateCanvas.node()) === null || _a === void 0 ? void 0 : _a.getContext('2d');
var xScalecanvasWidth = +this.scaleXUpdateCanvas.attr('width');
var xScalecanvasHeight = +this.scaleXUpdateCanvas.attr('height');
if (xScalecanvasCtx)
(0, utils_1.clearCanvas)(xScalecanvasCtx, 0, 0, xScalecanvasWidth, xScalecanvasHeight);
}
};
TradingChart.prototype.detach = function () {
if (this.root)
this.root.remove();
this.zoomEventEmitter.removeAllListeners();
this.panEventEmitter.removeAllListeners();
};
TradingChart.prototype.attach = function (domRoot) {
if (this.root) {
var _root = this.root.node();
if (_root)
domRoot.appendChild(_root);
}
};
TradingChart.prototype.on = function (event, listener) {
switch (event) {
case 'zoom':
return this.zoomEventEmitter.on(types_1.EVENT_ZOOM, listener);
case 'pan':
return this.panEventEmitter.on(types_1.EVENT_PAN, listener);
}
};
TradingChart.prototype.once = function (event, listener) {
switch (event) {
case 'zoom':
return this.zoomEventEmitter.once(types_1.EVENT_ZOOM, listener);
case 'pan':
return this.panEventEmitter.once(types_1.EVENT_PAN, listener);
}
};
TradingChart.prototype.off = function (event, listener) {
switch (event) {
case 'zoom':
return this.zoomEventEmitter.off(types_1.EVENT_ZOOM, listener);
case 'pan':
return this.panEventEmitter.off(types_1.EVENT_PAN, listener);
}
};
TradingChart.prototype.offAll = function (event) {
switch (event) {
case 'zoom':
return this.zoomEventEmitter.removeAllListeners(types_1.EVENT_ZOOM);
case 'pan':
return this.panEventEmitter.removeAllListeners(types_1.EVENT_PAN);
}
};
return TradingChart;
}());
exports.TradingChart = TradingChart;
//# sourceMappingURL=TradingChart.js.map