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.

699 lines 48.1 kB
"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