@amcharts/amcharts5
Version:
amCharts 5
458 lines (451 loc) • 18.2 kB
JavaScript
import { AxisRendererX } from "../../xy/axes/AxisRendererX";
import { ValueAxis } from "../../xy/axes/ValueAxis";
import { Indicator } from "./Indicator";
import { ColumnSeries } from "../../xy/series/ColumnSeries";
import { percent } from "../../../core/util/Percent";
import * as $type from "../../../core/util/Type";
/**
* An implementation of a Volume Profile indicator for a [[StockChart]].
*
* @see {@link https://www.amcharts.com/docs/v5/charts/stock/indicators/} for more info
* @since 5.7.0
*/
export class VolumeProfile extends Indicator {
constructor() {
super(...arguments);
Object.defineProperty(this, "_editableSettings", {
enumerable: true,
configurable: true,
writable: true,
value: [{
key: "countType",
name: this.root.language.translateAny("Count"),
type: "dropdown",
options: [
{ value: "rows", text: this.root.language.translateAny("number of rows") },
{ value: "ticks", text: this.root.language.translateAny("ticks per row") }
]
}, {
key: "count",
name: this.root.language.translateAny("Count"),
type: "number"
}, {
key: "valueArea",
name: this.root.language.translateAny("Value Area"),
type: "number"
}, {
key: "upColor",
name: this.root.language.translateAny("Up volume"),
type: "color"
}, {
key: "downColor",
name: this.root.language.translateAny("Down volume"),
type: "color"
}, {
key: "axisWidth",
name: this.root.language.translateAny("Width %"),
type: "number"
}]
});
Object.defineProperty(this, "xAxis", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "upSeries", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_previousColumn", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
_afterNew() {
super._afterNew();
const volumeSeries = this.get("volumeSeries");
const stockSeries = this.get("stockSeries");
if (volumeSeries) {
const chart = stockSeries.chart;
const root = this._root;
if (chart) {
const yAxis = stockSeries.get("yAxis");
const panelXAxis = stockSeries.get("xAxis");
panelXAxis.on("start", () => {
this.markDataDirty();
});
panelXAxis.on("end", () => {
this.markDataDirty();
});
const xRenderer = AxisRendererX.new(root, {});
xRenderer.grid.template.set("forceHidden", true);
xRenderer.labels.template.set("forceHidden", true);
this.xAxis = chart.xAxes.push(ValueAxis.new(root, {
zoomable: false,
strictMinMax: true,
panX: false,
panY: false,
renderer: xRenderer
}));
if (yAxis.get("renderer").get("opposite")) {
xRenderer.set("inversed", true);
this.xAxis.setAll({
x: percent(100),
centerX: percent(100)
});
}
this.series = chart.series.unshift(ColumnSeries.new(root, {
xAxis: this.xAxis,
yAxis: yAxis,
valueXField: "down",
openValueXField: "xOpen",
openValueYField: "yOpen",
valueYField: "y",
calculateAggregates: true,
themeTags: ["indicator", "volumeprofile"]
}));
this.upSeries = chart.series.unshift(ColumnSeries.new(root, {
xAxis: this.xAxis,
yAxis: yAxis,
valueXField: "total",
openValueXField: "down",
openValueYField: "yOpen",
valueYField: "y",
calculateAggregates: true,
themeTags: ["indicator", "volumeprofile"]
}));
this.upSeries.setPrivate("doNotUpdateLegend", true);
this.series.setPrivate("doNotUpdateLegend", true);
this.upSeries.setPrivate("baseValueSeries", stockSeries);
this.series.setPrivate("baseValueSeries", stockSeries);
this._handleLegend(this.series);
this._addInteractivity(this.series);
this._addInteractivity(this.upSeries);
}
}
}
_addInteractivity(series) {
series.columns.template.events.on("pointerover", (e) => {
let dataItem = e.target.dataItem;
if (dataItem) {
if (dataItem.component == this.upSeries) {
dataItem = this.series.dataItems[this.upSeries.dataItems.indexOf(dataItem)];
if (dataItem) {
const column = dataItem.get("graphics");
if (column) {
column.hover();
this._previousColumn = column;
}
}
}
else {
dataItem = this.upSeries.dataItems[this.series.dataItems.indexOf(dataItem)];
if (dataItem) {
const column = dataItem.get("graphics");
if (column) {
column.hover();
this._previousColumn = column;
}
}
}
this.series.updateLegendValue(dataItem);
}
});
series.columns.template.events.on("pointerout", () => {
this.series.updateLegendValue(undefined);
if (this._previousColumn) {
this._previousColumn.unhover();
}
});
series.columns.template.adapters.add("fillOpacity", (fillOpacity, target) => {
const dataItem = target.dataItem;
if (dataItem) {
const dataContext = dataItem.dataContext;
if (dataContext) {
if (dataContext.area) {
return this.get("valueAreaOpacity", .7);
}
}
}
return fillOpacity;
});
}
_updateChildren() {
if (this.series) {
super._updateChildren();
if (this.isDirty("count") || this.isDirty("countType") || this.isDirty("valueArea")) {
this.markDataDirty();
}
if (this.isDirty("upColor")) {
const upColor = this.get("upColor");
this.upSeries.set("fill", upColor);
this.upSeries.set("stroke", upColor);
this.setCustomData("upColor", upColor);
}
if (this.isDirty("downColor")) {
const downColor = this.get("downColor");
this.series.set("fill", downColor);
this.series.set("stroke", downColor);
this.setCustomData("downColor", downColor);
}
if (this.isDirty("axisWidth")) {
this.xAxis.set("width", percent(this.get("axisWidth", 40)));
}
}
}
/**
* @ignore
*/
prepareData() {
const volumeSeries = this.get("volumeSeries");
const stockSeries = this.get("stockSeries");
if (volumeSeries && this.series) {
let startIndex = volumeSeries.startIndex();
let endIndex = volumeSeries.endIndex();
const count = this.get("count", 20);
const type = this.get("countType");
let step = 1;
let min = Infinity;
let max = -Infinity;
for (let i = startIndex; i < endIndex; i++) {
const dataItem = stockSeries.dataItems[i];
if (dataItem) {
const close = dataItem.get("valueY");
if ($type.isNumber(close)) {
if (close < min) {
min = close;
}
if (close > max) {
max = close;
}
}
}
}
if (min != Infinity) {
let rows;
if (type == "ticks") {
step = count / 100;
min = Math.floor(min / step) * step;
max = Math.ceil(max / step) * step;
rows = (max - min) / step;
}
else {
step = (max - min) / count;
rows = count;
}
const rowDataDown = [];
const rowDataUp = [];
for (let i = 0; i < rows; i++) {
rowDataDown[i] = 0;
rowDataUp[i] = 0;
}
let previousDataItem;
for (let i = startIndex; i < endIndex; i++) {
const dataItem = stockSeries.dataItems[i];
const volumeDataItem = volumeSeries.dataItems[i];
if (dataItem && volumeDataItem) {
const close = dataItem.get("valueY");
const volume = volumeDataItem.get("valueY");
if ($type.isNumber(close) && $type.isNumber(volume)) {
let index = Math.floor((close - min) / step);
if (index == count) {
index = count - 1;
}
if ($type.isNumber(index)) {
if (previousDataItem && previousDataItem.get("valueY", close) < close) {
rowDataDown[index] += volume;
}
else {
rowDataUp[index] += volume;
}
}
}
previousDataItem = dataItem;
}
}
const dataDown = [];
let sum = 0;
for (let i = 0; i < rows; i++) {
let total = rowDataUp[i] + rowDataDown[i];
sum += total;
dataDown.push({
yOpen: min + i * step,
y: min + (i + 1) * step,
up: rowDataUp[i],
down: rowDataDown[i],
total: total,
xOpen: 0,
area: false
});
}
let len = this.series.data.length;
if (len && len == dataDown.length) {
for (let i = 0; i < len; i++) {
this.series.data.setIndex(i, dataDown[i]);
}
}
else {
this.series.data.setAll(dataDown);
}
const dataUp = [];
let highest = 0;
let hi = 0;
for (let i = 0; i < rows; i++) {
let total = rowDataUp[i] + rowDataDown[i];
dataUp.push({
yOpen: min + i * step,
y: min + (i + 1) * step,
up: rowDataUp[i],
down: rowDataDown[i],
total: total,
area: false
});
if (total > highest) {
highest = total;
hi = i;
}
}
let valueArea = sum * this.get("valueArea", 70) / 100;
let area = highest;
let cd = 1;
let cu = 1;
let dlen = dataUp.length;
dataUp[hi].area = true;
dataDown[hi].area = true;
/*
// with two rows
while (area < valueArea) {
let rowAbove1 = hi + cu;
let rowAbove2 = hi + cu + 1;
let sumAbove = 0
if (rowAbove1 < dlen) {
sumAbove += dataUp[rowAbove1].total;
}
if (rowAbove2 < dlen) {
sumAbove += dataUp[rowAbove2].total;
}
let rowBelow1 = hi - cd;
let rowBelow2 = hi - cd - 1;
let sumBelow = 0
if (rowBelow1 >= 0) {
sumBelow += dataUp[rowBelow1].total;
}
if (rowBelow2 >= 0) {
sumBelow += dataUp[rowBelow2].total;
}
if (sumBelow <= sumAbove) {
area += sumAbove;
if (rowAbove1 < dlen) {
dataDown[rowAbove1].area = true;
dataUp[rowAbove1].area = true;
cu++;
}
if (rowAbove2 < dlen) {
dataDown[rowAbove2].area = true;
dataUp[rowAbove2].area = true;
cu++;
}
}
else {
area += sumBelow;
if (rowBelow1 >= 0) {
dataDown[rowBelow1].area = true;
dataUp[rowBelow1].area = true;
cd++;
}
if (rowBelow2 >= 0) {
dataDown[rowBelow2].area = true;
dataUp[rowBelow2].area = true;
cd++;
}
}
if (sumBelow == 0) {
cd++;
}
if (sumAbove == 0) {
cu++;
}
if ((cd > dlen && cu > dlen)) {
break;
}
}
*/
// single row
while (area < valueArea) {
let rowAbove1 = hi + cu;
let sumAbove = 0;
if (rowAbove1 < dlen) {
sumAbove += dataUp[rowAbove1].total;
}
let rowBelow1 = hi - cd;
let sumBelow = 0;
if (rowBelow1 >= 0) {
sumBelow += dataUp[rowBelow1].total;
}
if (sumBelow <= sumAbove) {
area += sumAbove;
if (rowAbove1 < dlen) {
dataDown[rowAbove1].area = true;
dataUp[rowAbove1].area = true;
cu++;
}
}
else {
area += sumBelow;
if (rowBelow1 >= 0) {
dataDown[rowBelow1].area = true;
dataUp[rowBelow1].area = true;
cd++;
}
}
if (sumBelow == 0) {
cd++;
}
if (sumAbove == 0) {
cu++;
}
if ((cd > dlen && cu > dlen)) {
break;
}
}
area = Math.ceil(area);
len = this.upSeries.data.length;
if (len > 0 && len == dataUp.length) {
for (let i = 0; i < len; i++) {
this.upSeries.data.setIndex(i, dataUp[i]);
}
}
else {
this.upSeries.data.setAll(dataUp);
}
}
}
}
_dispose() {
super._dispose();
if (this.upSeries) {
this.upSeries.dispose();
}
if (this.xAxis) {
this.xAxis.dispose();
}
}
}
Object.defineProperty(VolumeProfile, "className", {
enumerable: true,
configurable: true,
writable: true,
value: "VolumeProfile"
});
Object.defineProperty(VolumeProfile, "classNames", {
enumerable: true,
configurable: true,
writable: true,
value: Indicator.classNames.concat([VolumeProfile.className])
});
//# sourceMappingURL=VolumeProfile.js.map