UNPKG

highcharts

Version:
661 lines (590 loc) 20.2 kB
/** * @license Highcharts JS v6.2.0 (2018-10-17) * * Indicator series type for Highstock * * (c) 2010-2017 Sebastian Bochan * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else if (typeof define === 'function' && define.amd) { define(function () { return factory; }); } else { factory(Highcharts); } }(function (Highcharts) { (function (H) { var UNDEFINED, seriesType = H.seriesType, each = H.each, merge = H.merge, color = H.color, isArray = H.isArray, defined = H.defined, SMA = H.seriesTypes.sma; // Utils: function maxHigh(arr) { return arr.reduce(function (max, res) { return Math.max(max, res[1]); }, -Infinity); } function minLow(arr) { return arr.reduce(function (min, res) { return Math.min(min, res[2]); }, Infinity); } function highlowLevel(arr) { return { high: maxHigh(arr), low: minLow(arr) }; } function getClosestPointRange(axis) { var closestDataRange, loopLength, distance, xData, i; each(axis.series, function (series) { if (series.xData) { xData = series.xData; loopLength = series.xIncrement ? 1 : xData.length - 1; for (i = loopLength; i > 0; i--) { distance = xData[i] - xData[i - 1]; if ( closestDataRange === UNDEFINED || distance < closestDataRange ) { closestDataRange = distance; } } } }); return closestDataRange; } // Data integrity in Ichimoku is different than default "averages": // Point: [undefined, value, value, ...] is correct // Point: [undefined, undefined, undefined, ...] is incorrect H.approximations['ichimoku-averages'] = function () { var ret = [], isEmptyRange; each(arguments, function (arr, i) { ret.push(H.approximations.average(arr)); isEmptyRange = !isEmptyRange && ret[i] === undefined; }); // Return undefined when first elem. is undefined and let // sum method handle null (#7377) return isEmptyRange ? undefined : ret; }; /** * The IKH series type. * * @constructor seriesTypes.ikh * @augments seriesTypes.sma */ seriesType('ikh', 'sma', /** * Ichimoku Kinko Hyo (IKH). This series requires `linkedTo` option to be * set. * * @extends plotOptions.sma * @product highstock * @sample {highstock} stock/indicators/ichimoku-kinko-hyo * Ichimoku Kinko Hyo indicator * @since 6.0.0 * @excluding * allAreas,colorAxis,compare,compareBase,joinBy,keys,stacking, * showInNavigator,navigatorOptions,pointInterval, * pointIntervalUnit,pointPlacement,pointRange,pointStart * @optionparent plotOptions.ikh */ { params: { period: 26, /** * The base period for Tenkan calculations. * * @type {Number} * @since 6.0.0 * @product highstock */ periodTenkan: 9, /** * The base period for Senkou Span B calculations * * @type {Number} * @since 6.0.0 * @product highstock */ periodSenkouSpanB: 52 }, marker: { enabled: false }, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + 'TENKAN SEN: {point.tenkanSen:.3f}<br/>' + 'KIJUN SEN: {point.kijunSen:.3f}<br/>' + 'CHIKOU SPAN: {point.chikouSpan:.3f}<br/>' + 'SENKOU SPAN A: {point.senkouSpanA:.3f}<br/>' + 'SENKOU SPAN B: {point.senkouSpanB:.3f}<br/>' }, /** * The styles for Tenkan line * * @since 6.0.0 * @product highstock */ tenkanLine: { styles: { /** * Pixel width of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineWidth: 1, /** * Color of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineColor: undefined } }, /** * The styles for Kijun line * * @since 6.0.0 * @product highstock */ kijunLine: { styles: { /** * Pixel width of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineWidth: 1, /** * Color of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineColor: undefined } }, /** * The styles for Chikou line * * @since 6.0.0 * @product highstock */ chikouLine: { styles: { /** * Pixel width of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineWidth: 1, /** * Color of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineColor: undefined } }, /** * The styles for Senkou Span A line * * @since 6.0.0 * @product highstock */ senkouSpanA: { styles: { /** * Pixel width of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineWidth: 1, /** * Color of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineColor: undefined } }, /** * The styles for Senkou Span B line * * @since 6.0.0 * @product highstock */ senkouSpanB: { styles: { /** * Pixel width of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineWidth: 1, /** * Color of the line. * * @type {Number} * @since 6.0.0 * @product highstock */ lineColor: undefined } }, /** * The styles for fill between Senkou Span A and B * * @since 6.0.0 * @product highstock */ senkouSpan: { styles: { /** * Color of the area between Senkou Span A and B. * * @type {Number} * @since 6.0.0 * @product highstock */ fill: 'rgba(255, 0, 0, 0.5)' } }, dataGrouping: { approximation: 'ichimoku-averages' } }, { pointArrayMap: [ 'tenkanSen', 'kijunSen', 'chikouSpan', 'senkouSpanA', 'senkouSpanB' ], pointValKey: 'tenkanSen', nameComponents: ['periodSenkouSpanB', 'period', 'periodTenkan'], init: function () { SMA.prototype.init.apply(this, arguments); // Set default color for lines: this.options = merge({ tenkanLine: { styles: { lineColor: this.color } }, kijunLine: { styles: { lineColor: this.color } }, chikouLine: { styles: { lineColor: this.color } }, senkouSpanA: { styles: { lineColor: this.color, fill: color(this.color).setOpacity(0.5).get() } }, senkouSpanB: { styles: { lineColor: this.color, fill: color(this.color).setOpacity(0.5).get() } }, senkouSpan: { styles: { fill: color(this.color).setOpacity(0.2).get() } } }, this.options); }, toYData: function (point) { return [ point.tenkanSen, point.kijunSen, point.chikouSpan, point.senkouSpanA, point.senkouSpanB ]; }, translate: function () { var indicator = this; SMA.prototype.translate.apply(indicator); each(indicator.points, function (point) { each(indicator.pointArrayMap, function (value) { if (defined(point[value])) { point['plot' + value] = indicator.yAxis.toPixels( point[value], true ); // add extra parameters for support tooltip in moved // lines point.plotY = point['plot' + value]; point.tooltipPos = [point.plotX, point['plot' + value]]; point.isNull = false; } }); }); }, // One does not simply // Render five lines // And an arearange // In just one series.. drawGraph: function () { var indicator = this, mainLinePoints = indicator.points, pointsLength = mainLinePoints.length, mainLineOptions = indicator.options, mainLinePath = indicator.graph, mainColor = indicator.color, gappedExtend = { options: { gapSize: mainLineOptions.gapSize } }, pointArrayMapLength = indicator.pointArrayMap.length, allIchimokuPoints = [[], [], [], [], [], []], position, point, i; // Generate points for all lines and spans lines: while (pointsLength--) { point = mainLinePoints[pointsLength]; for (i = 0; i < pointArrayMapLength; i++) { position = indicator.pointArrayMap[i]; if (defined(point[position])) { allIchimokuPoints[i].push({ plotX: point.plotX, plotY: point['plot' + position], isNull: false }); } } } // Modify options and generate lines: each([ 'tenkanLine', 'kijunLine', 'chikouLine', 'senkouSpanA', 'senkouSpanB', 'senkouSpan' ], function (lineName, i) { // First line is rendered by default option indicator.points = allIchimokuPoints[i]; indicator.options = merge( mainLineOptions[lineName].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName]; // For span, we need an access to the next points, used in // getGraphPath() indicator.nextPoints = allIchimokuPoints[i - 1]; if (i === 5) { indicator.points = allIchimokuPoints[i - 1]; indicator.options = merge( mainLineOptions[lineName].styles, gappedExtend ); indicator.graph = indicator['graph' + lineName]; indicator.nextPoints = allIchimokuPoints[i - 2]; indicator.fillGraph = true; indicator.color = indicator.options.fill; SMA.prototype.drawGraph.call(indicator); } else { indicator.fillGraph = false; indicator.color = mainColor; SMA.prototype.drawGraph.call(indicator); } // Now save lines: indicator['graph' + lineName] = indicator.graph; }); // Clean temporary properties: delete indicator.nextPoints; delete indicator.fillGraph; // Restore options and draw the Tenkan line: indicator.points = mainLinePoints; indicator.options = mainLineOptions; indicator.graph = mainLinePath; }, getGraphPath: function (points) { var indicator = this, path = [], spanA, fillArray = [], spanAarr = []; points = points || this.points; // Render Senkou Span if (indicator.fillGraph && indicator.nextPoints) { spanA = SMA.prototype.getGraphPath.call( indicator, // Reverse points, so Senkou Span A will start from the end: indicator.nextPoints ); spanA[0] = 'L'; path = SMA.prototype.getGraphPath.call( indicator, points ); spanAarr = spanA.slice(0, path.length); for (var i = (spanAarr.length - 1); i > 0; i -= 3) { fillArray.push( spanAarr[i - 2], spanAarr[i - 1], spanAarr[i] ); } path = path.concat(fillArray); } else { path = SMA.prototype.getGraphPath.apply(indicator, arguments); } return path; }, getValues: function (series, params) { var period = params.period, periodTenkan = params.periodTenkan, periodSenkouSpanB = params.periodSenkouSpanB, xVal = series.xData, yVal = series.yData, xAxis = series.xAxis, yValLen = (yVal && yVal.length) || 0, closestPointRange = getClosestPointRange(xAxis), IKH = [], xData = [], dateStart, date, slicedTSY, slicedKSY, slicedSSBY, pointTS, pointKS, pointSSB, i, TS, KS, CS, SSA, SSB; // ikh requires close value if ( xVal.length <= period || !isArray(yVal[0]) || yVal[0].length !== 4 ) { return false; } // add timestamps at the beginning dateStart = xVal[0] - (period * closestPointRange); for (i = 0; i < period; i++) { xData.push(dateStart + i * closestPointRange); } for (i = 0; i < yValLen; i++) { // Tenkan Sen if (i >= periodTenkan) { slicedTSY = yVal.slice(i - periodTenkan, i); pointTS = highlowLevel(slicedTSY); TS = (pointTS.high + pointTS.low) / 2; } if (i >= period) { slicedKSY = yVal.slice(i - period, i); pointKS = highlowLevel(slicedKSY); KS = (pointKS.high + pointKS.low) / 2; SSA = (TS + KS) / 2; } if (i >= periodSenkouSpanB) { slicedSSBY = yVal.slice(i - periodSenkouSpanB, i); pointSSB = highlowLevel(slicedSSBY); SSB = (pointSSB.high + pointSSB.low) / 2; } CS = yVal[i][0]; date = xVal[i]; if (IKH[i] === UNDEFINED) { IKH[i] = []; } if (IKH[i + period] === UNDEFINED) { IKH[i + period] = []; } IKH[i + period][0] = TS; IKH[i + period][1] = KS; IKH[i + period][2] = UNDEFINED; if (i >= period) { IKH[i - period][2] = CS; } else { IKH[i + period][3] = UNDEFINED; IKH[i + period][4] = UNDEFINED; } if (IKH[i + 2 * period] === UNDEFINED) { IKH[i + 2 * period] = []; } IKH[i + 2 * period][3] = SSA; IKH[i + 2 * period][4] = SSB; xData.push(date); } // add timestamps for further points for (i = 1; i <= period; i++) { xData.push(date + i * closestPointRange); } return { values: IKH, xData: xData, yData: IKH }; } }); /** * A `IKH` series. If the [type](#series.ikh.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @type {Object} * @since 6.0.0 * @extends series,plotOptions.ikh * @excluding data,dataParser,dataURL * @product highstock * @apioption series.ikh */ /** * @since 6.0.0 * @extends series.sma.data * @product highstock * @apioption series.ikh.data */ }(Highcharts)); return (function () { }()); }));