Icard/angular-clarity-master(work.../node_modules/highcharts/indicators/volume-by-price.src.js

662 lines
28 KiB
JavaScript

/**
* @license Highstock JS v11.4.1 (2024-04-04)
*
* Indicator series type for Highcharts Stock
*
* (c) 2010-2024 Paweł Dalek
*
* License: www.highcharts.com/license
*/
(function (factory) {
if (typeof module === 'object' && module.exports) {
factory['default'] = factory;
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define('highcharts/indicators/volume-by-price', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) {
factory(Highcharts);
factory.Highcharts = Highcharts;
return factory;
});
} else {
factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
}
}(function (Highcharts) {
'use strict';
var _modules = Highcharts ? Highcharts._modules : {};
function _registerModule(obj, path, args, fn) {
if (!obj.hasOwnProperty(path)) {
obj[path] = fn.apply(null, args);
if (typeof CustomEvent === 'function') {
window.dispatchEvent(new CustomEvent(
'HighchartsModuleLoaded',
{ detail: { path: path, module: obj[path] } }
));
}
}
}
_registerModule(_modules, 'Stock/Indicators/VBP/VBPPoint.js', [_modules['Core/Series/SeriesRegistry.js']], function (SeriesRegistry) {
/* *
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* *
*
* Imports
*
* */
const { sma: { prototype: { pointClass: SMAPoint } } } = SeriesRegistry.seriesTypes;
/* *
*
* Class
*
* */
class VBPPoint extends SMAPoint {
// Required for destroying negative part of volume
destroy() {
// @todo: this.negativeGraphic doesn't seem to be used anywhere
if (this.negativeGraphic) {
this.negativeGraphic = this.negativeGraphic.destroy();
}
super.destroy.apply(this, arguments);
}
}
/* *
*
* Default Export
*
* */
return VBPPoint;
});
_registerModule(_modules, 'Stock/Indicators/VBP/VBPIndicator.js', [_modules['Stock/Indicators/VBP/VBPPoint.js'], _modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (VBPPoint, A, H, SeriesRegistry, U) {
/* *
*
* (c) 2010-2024 Paweł Dalek
*
* Volume By Price (VBP) indicator for Highcharts Stock
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
const { animObject } = A;
const { noop } = H;
const { column: { prototype: columnProto }, sma: SMAIndicator } = SeriesRegistry.seriesTypes;
const { addEvent, arrayMax, arrayMin, correctFloat, defined, error, extend, isArray, merge } = U;
/* *
*
* Constants
*
* */
const abs = Math.abs;
/* *
*
* Functions
*
* */
// Utils
/**
* @private
*/
function arrayExtremesOHLC(data) {
const dataLength = data.length;
let min = data[0][3], max = min, i = 1, currentPoint;
for (; i < dataLength; i++) {
currentPoint = data[i][3];
if (currentPoint < min) {
min = currentPoint;
}
if (currentPoint > max) {
max = currentPoint;
}
}
return {
min: min,
max: max
};
}
/* *
*
* Class
*
* */
/**
* The Volume By Price (VBP) series type.
*
* @private
* @class
* @name Highcharts.seriesTypes.vbp
*
* @augments Highcharts.Series
*/
class VBPIndicator extends SMAIndicator {
/* *
*
* Functions
*
* */
init(chart, options) {
const indicator = this;
// Series.update() sends data that is not necessary as everything is
// calculated in getValues(), #17007
delete options.data;
super.init.apply(indicator, arguments);
// Only after series are linked add some additional logic/properties.
const unbinder = addEvent(this.chart.constructor, 'afterLinkSeries', function () {
// Protection for a case where the indicator is being updated,
// for a brief moment the indicator is deleted.
if (indicator.options) {
const params = indicator.options.params, baseSeries = indicator.linkedParent, volumeSeries = chart.get(params.volumeSeriesID);
indicator.addCustomEvents(baseSeries, volumeSeries);
}
unbinder();
}, {
order: 1
});
return indicator;
}
// Adds events related with removing series
addCustomEvents(baseSeries, volumeSeries) {
const indicator = this, toEmptyIndicator = () => {
indicator.chart.redraw();
indicator.setData([]);
indicator.zoneStarts = [];
if (indicator.zoneLinesSVG) {
indicator.zoneLinesSVG = indicator.zoneLinesSVG.destroy();
}
};
// If base series is deleted, indicator series data is filled with
// an empty array
indicator.dataEventsToUnbind.push(addEvent(baseSeries, 'remove', function () {
toEmptyIndicator();
}));
// If volume series is deleted, indicator series data is filled with
// an empty array
if (volumeSeries) {
indicator.dataEventsToUnbind.push(addEvent(volumeSeries, 'remove', function () {
toEmptyIndicator();
}));
}
return indicator;
}
// Initial animation
animate(init) {
const series = this, inverted = series.chart.inverted, group = series.group, attr = {};
if (!init && group) {
const position = inverted ? series.yAxis.top : series.xAxis.left;
if (inverted) {
group['forceAnimate:translateY'] = true;
attr.translateY = position;
}
else {
group['forceAnimate:translateX'] = true;
attr.translateX = position;
}
group.animate(attr, extend(animObject(series.options.animation), {
step: function (val, fx) {
series.group.attr({
scaleX: Math.max(0.001, fx.pos)
});
}
}));
}
}
drawPoints() {
const indicator = this;
if (indicator.options.volumeDivision.enabled) {
indicator.posNegVolume(true, true);
columnProto.drawPoints.apply(indicator, arguments);
indicator.posNegVolume(false, false);
}
columnProto.drawPoints.apply(indicator, arguments);
}
// Function responsible for dividing volume into positive and negative
posNegVolume(initVol, pos) {
const indicator = this, signOrder = pos ?
['positive', 'negative'] :
['negative', 'positive'], volumeDivision = indicator.options.volumeDivision, pointLength = indicator.points.length;
let posWidths = [], negWidths = [], i = 0, pointWidth, priceZone, wholeVol, point;
if (initVol) {
indicator.posWidths = posWidths;
indicator.negWidths = negWidths;
}
else {
posWidths = indicator.posWidths;
negWidths = indicator.negWidths;
}
for (; i < pointLength; i++) {
point = indicator.points[i];
point[signOrder[0] + 'Graphic'] = point.graphic;
point.graphic = point[signOrder[1] + 'Graphic'];
if (initVol) {
pointWidth = point.shapeArgs.width;
priceZone = indicator.priceZones[i];
wholeVol = priceZone.wholeVolumeData;
if (wholeVol) {
posWidths.push(pointWidth / wholeVol * priceZone.positiveVolumeData);
negWidths.push(pointWidth / wholeVol * priceZone.negativeVolumeData);
}
else {
posWidths.push(0);
negWidths.push(0);
}
}
point.color = pos ?
volumeDivision.styles.positiveColor :
volumeDivision.styles.negativeColor;
point.shapeArgs.width = pos ?
indicator.posWidths[i] :
indicator.negWidths[i];
point.shapeArgs.x = pos ?
point.shapeArgs.x :
indicator.posWidths[i];
}
}
translate() {
const indicator = this, options = indicator.options, chart = indicator.chart, yAxis = indicator.yAxis, yAxisMin = yAxis.min, zoneLinesOptions = indicator.options.zoneLines, priceZones = (indicator.priceZones);
let yBarOffset = 0, volumeDataArray, maxVolume, primalBarWidth, barHeight, barHeightP, oldBarHeight, barWidth, pointPadding, chartPlotTop, barX, barY;
columnProto.translate.apply(indicator);
const indicatorPoints = indicator.points;
// Do translate operation when points exist
if (indicatorPoints.length) {
pointPadding = options.pointPadding < 0.5 ?
options.pointPadding :
0.1;
volumeDataArray = indicator.volumeDataArray;
maxVolume = arrayMax(volumeDataArray);
primalBarWidth = chart.plotWidth / 2;
chartPlotTop = chart.plotTop;
barHeight = abs(yAxis.toPixels(yAxisMin) -
yAxis.toPixels(yAxisMin + indicator.rangeStep));
oldBarHeight = abs(yAxis.toPixels(yAxisMin) -
yAxis.toPixels(yAxisMin + indicator.rangeStep));
if (pointPadding) {
barHeightP = abs(barHeight * (1 - 2 * pointPadding));
yBarOffset = abs((barHeight - barHeightP) / 2);
barHeight = abs(barHeightP);
}
indicatorPoints.forEach(function (point, index) {
barX = point.barX = point.plotX = 0;
barY = point.plotY = (yAxis.toPixels(priceZones[index].start) -
chartPlotTop -
(yAxis.reversed ?
(barHeight - oldBarHeight) :
barHeight) -
yBarOffset);
barWidth = correctFloat(primalBarWidth *
priceZones[index].wholeVolumeData / maxVolume);
point.pointWidth = barWidth;
point.shapeArgs = indicator.crispCol.apply(// eslint-disable-line no-useless-call
indicator, [barX, barY, barWidth, barHeight]);
point.volumeNeg = priceZones[index].negativeVolumeData;
point.volumePos = priceZones[index].positiveVolumeData;
point.volumeAll = priceZones[index].wholeVolumeData;
});
if (zoneLinesOptions.enabled) {
indicator.drawZones(chart, yAxis, indicator.zoneStarts, zoneLinesOptions.styles);
}
}
}
getExtremes() {
const prevCompare = this.options.compare, prevCumulative = this.options.cumulative;
let ret;
// Temporarily disable cumulative and compare while getting the extremes
if (this.options.compare) {
this.options.compare = void 0;
ret = super.getExtremes();
this.options.compare = prevCompare;
}
else if (this.options.cumulative) {
this.options.cumulative = false;
ret = super.getExtremes();
this.options.cumulative = prevCumulative;
}
else {
ret = super.getExtremes();
}
return ret;
}
getValues(series, params) {
const indicator = this, xValues = series.processedXData, yValues = series.processedYData, chart = indicator.chart, ranges = params.ranges, VBP = [], xData = [], yData = [], volumeSeries = chart.get(params.volumeSeriesID);
// Checks if base series exists
if (!series.chart) {
error('Base series not found! In case it has been removed, add ' +
'a new one.', true, chart);
return;
}
// Checks if volume series exists and if it has data
if (!volumeSeries ||
!volumeSeries.processedXData.length) {
const errorMessage = volumeSeries && !volumeSeries.processedXData.length ?
' does not contain any data.' :
' not found! Check `volumeSeriesID`.';
error('Series ' +
params.volumeSeriesID + errorMessage, true, chart);
return;
}
// Checks if series data fits the OHLC format
const isOHLC = isArray(yValues[0]);
if (isOHLC && yValues[0].length !== 4) {
error('Type of ' +
series.name +
' series is different than line, OHLC or candlestick.', true, chart);
return;
}
// Price zones contains all the information about the zones (index,
// start, end, volumes, etc.)
const priceZones = indicator.priceZones = indicator.specifyZones(isOHLC, xValues, yValues, ranges, volumeSeries);
priceZones.forEach(function (zone, index) {
VBP.push([zone.x, zone.end]);
xData.push(VBP[index][0]);
yData.push(VBP[index][1]);
});
return {
values: VBP,
xData: xData,
yData: yData
};
}
// Specifying where each zone should start ans end
specifyZones(isOHLC, xValues, yValues, ranges, volumeSeries) {
const indicator = this, rangeExtremes = (isOHLC ? arrayExtremesOHLC(yValues) : false), zoneStarts = indicator.zoneStarts = [], priceZones = [];
let lowRange = rangeExtremes ?
rangeExtremes.min :
arrayMin(yValues), highRange = rangeExtremes ?
rangeExtremes.max :
arrayMax(yValues), i = 0, j = 1;
// If the compare mode is set on the main series, change the VBP
// zones to fit new extremes, #16277.
const mainSeries = indicator.linkedParent;
if (!indicator.options.compareToMain &&
mainSeries.dataModify) {
lowRange = mainSeries.dataModify.modifyValue(lowRange);
highRange = mainSeries.dataModify.modifyValue(highRange);
}
if (!defined(lowRange) || !defined(highRange)) {
if (this.points.length) {
this.setData([]);
this.zoneStarts = [];
if (this.zoneLinesSVG) {
this.zoneLinesSVG = this.zoneLinesSVG.destroy();
}
}
return [];
}
const rangeStep = indicator.rangeStep =
correctFloat(highRange - lowRange) / ranges;
zoneStarts.push(lowRange);
for (; i < ranges - 1; i++) {
zoneStarts.push(correctFloat(zoneStarts[i] + rangeStep));
}
zoneStarts.push(highRange);
const zoneStartsLength = zoneStarts.length;
// Creating zones
for (; j < zoneStartsLength; j++) {
priceZones.push({
index: j - 1,
x: xValues[0],
start: zoneStarts[j - 1],
end: zoneStarts[j]
});
}
return indicator.volumePerZone(isOHLC, priceZones, volumeSeries, xValues, yValues);
}
// Calculating sum of volume values for a specific zone
volumePerZone(isOHLC, priceZones, volumeSeries, xValues, yValues) {
const indicator = this, volumeXData = volumeSeries.processedXData, volumeYData = volumeSeries.processedYData, lastZoneIndex = priceZones.length - 1, baseSeriesLength = yValues.length, volumeSeriesLength = volumeYData.length;
let previousValue, startFlag, endFlag, value, i;
// Checks if each point has a corresponding volume value
if (abs(baseSeriesLength - volumeSeriesLength)) {
// If the first point don't have volume, add 0 value at the
// beginning of the volume array
if (xValues[0] !== volumeXData[0]) {
volumeYData.unshift(0);
}
// If the last point don't have volume, add 0 value at the end
// of the volume array
if (xValues[baseSeriesLength - 1] !==
volumeXData[volumeSeriesLength - 1]) {
volumeYData.push(0);
}
}
indicator.volumeDataArray = [];
priceZones.forEach(function (zone) {
zone.wholeVolumeData = 0;
zone.positiveVolumeData = 0;
zone.negativeVolumeData = 0;
for (i = 0; i < baseSeriesLength; i++) {
startFlag = false;
endFlag = false;
value = isOHLC ? yValues[i][3] : yValues[i];
previousValue = i ?
(isOHLC ?
yValues[i - 1][3] :
yValues[i - 1]) :
value;
// If the compare mode is set on the main series,
// change the VBP zones to fit new extremes, #16277.
const mainSeries = indicator.linkedParent;
if (!indicator.options.compareToMain &&
mainSeries.dataModify) {
value = mainSeries.dataModify.modifyValue(value);
previousValue = mainSeries.dataModify
.modifyValue(previousValue);
}
// Checks if this is the point with the
// lowest close value and if so, adds it calculations
if (value <= zone.start && zone.index === 0) {
startFlag = true;
}
// Checks if this is the point with the highest
// close value and if so, adds it calculations
if (value >= zone.end && zone.index === lastZoneIndex) {
endFlag = true;
}
if ((value > zone.start || startFlag) &&
(value < zone.end || endFlag)) {
zone.wholeVolumeData += volumeYData[i];
if (previousValue > value) {
zone.negativeVolumeData += volumeYData[i];
}
else {
zone.positiveVolumeData += volumeYData[i];
}
}
}
indicator.volumeDataArray.push(zone.wholeVolumeData);
});
return priceZones;
}
// Function responsible for drawing additional lines indicating zones
drawZones(chart, yAxis, zonesValues, zonesStyles) {
const indicator = this, renderer = chart.renderer, leftLinePos = 0, rightLinePos = chart.plotWidth, verticalOffset = chart.plotTop;
let zoneLinesSVG = indicator.zoneLinesSVG, zoneLinesPath = [], verticalLinePos;
zonesValues.forEach(function (value) {
verticalLinePos = yAxis.toPixels(value) - verticalOffset;
zoneLinesPath = zoneLinesPath.concat(chart.renderer.crispLine([[
'M',
leftLinePos,
verticalLinePos
], [
'L',
rightLinePos,
verticalLinePos
]], zonesStyles.lineWidth));
});
// Create zone lines one path or update it while animating
if (zoneLinesSVG) {
zoneLinesSVG.animate({
d: zoneLinesPath
});
}
else {
zoneLinesSVG = indicator.zoneLinesSVG =
renderer
.path(zoneLinesPath)
.attr({
'stroke-width': zonesStyles.lineWidth,
'stroke': zonesStyles.color,
'dashstyle': zonesStyles.dashStyle,
'zIndex': indicator.group.zIndex + 0.1
})
.add(indicator.group);
}
}
}
/* *
*
* Static Properties
*
* */
/**
* Volume By Price indicator.
*
* This series requires `linkedTo` option to be set.
*
* @sample stock/indicators/volume-by-price
* Volume By Price indicator
*
* @extends plotOptions.sma
* @since 6.0.0
* @product highstock
* @requires stock/indicators/indicators
* @requires stock/indicators/volume-by-price
* @optionparent plotOptions.vbp
*/
VBPIndicator.defaultOptions = merge(SMAIndicator.defaultOptions, {
/**
* @excluding index, period
*/
params: {
// Index and period are unchangeable, do not inherit (#15362)
index: void 0,
period: void 0,
/**
* The number of price zones.
*/
ranges: 12,
/**
* The id of volume series which is mandatory. For example using
* OHLC data, volumeSeriesID='volume' means the indicator will be
* calculated using OHLC and volume values.
*/
volumeSeriesID: 'volume'
},
/**
* The styles for lines which determine price zones.
*/
zoneLines: {
/**
* Enable/disable zone lines.
*/
enabled: true,
/**
* Specify the style of zone lines.
*
* @type {Highcharts.CSSObject}
* @default {"color": "#0A9AC9", "dashStyle": "LongDash", "lineWidth": 1}
*/
styles: {
/** @ignore-option */
color: '#0A9AC9',
/** @ignore-option */
dashStyle: 'LongDash',
/** @ignore-option */
lineWidth: 1
}
},
/**
* The styles for bars when volume is divided into positive/negative.
*/
volumeDivision: {
/**
* Option to control if volume is divided.
*/
enabled: true,
styles: {
/**
* Color of positive volume bars.
*
* @type {Highcharts.ColorString}
*/
positiveColor: 'rgba(144, 237, 125, 0.8)',
/**
* Color of negative volume bars.
*
* @type {Highcharts.ColorString}
*/
negativeColor: 'rgba(244, 91, 91, 0.8)'
}
},
// To enable series animation; must be animationLimit > pointCount
animationLimit: 1000,
enableMouseTracking: false,
pointPadding: 0,
zIndex: -1,
crisp: true,
dataGrouping: {
enabled: false
},
dataLabels: {
allowOverlap: true,
enabled: true,
format: 'P: {point.volumePos:.2f} | N: {point.volumeNeg:.2f}',
padding: 0,
style: {
/** @internal */
fontSize: '0.5em'
},
verticalAlign: 'top'
}
});
extend(VBPIndicator.prototype, {
nameBase: 'Volume by Price',
nameComponents: ['ranges'],
calculateOn: {
chart: 'render',
xAxis: 'afterSetExtremes'
},
pointClass: VBPPoint,
markerAttribs: noop,
drawGraph: noop,
getColumnMetrics: columnProto.getColumnMetrics,
crispCol: columnProto.crispCol
});
SeriesRegistry.registerSeriesType('vbp', VBPIndicator);
/* *
*
* Default Export
*
* */
/* *
*
* API Options
*
* */
/**
* A `Volume By Price (VBP)` series. If the [type](#series.vbp.type) option is
* not specified, it is inherited from [chart.type](#chart.type).
*
* @extends series,plotOptions.vbp
* @since 6.0.0
* @product highstock
* @excluding dataParser, dataURL, compare, compareBase, compareStart
* @requires stock/indicators/indicators
* @requires stock/indicators/volume-by-price
* @apioption series.vbp
*/
''; // To include the above in the js output
return VBPIndicator;
});
_registerModule(_modules, 'masters/indicators/volume-by-price.src.js', [_modules['Core/Globals.js']], function (Highcharts) {
return Highcharts;
});
}));