Creating custom indicators for a Stock Chart
This tutorial will show how we can create custom indicators for a stock chart.
Indicator class
Section titled “Indicator class”To begin creating a custom indicator, we need to define a new class that extends a built-in Indicator class.
Our class definition at the very least should contain three things:
_afterNew()method - it is used to set everything up, such as creating indicator series.prepareData()method - whenever data needs to be updated, e.g. on first load or target stock series data updates.- An instance of one of the
XYSeries, e.g.LineSeries, which needs to be set on indicator’sseriesproperty.
class MyIndicator extends am5stock.Indicator { declare className: string = "MyIndicator"; declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements // ... }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator { className = "MyIndicator";
_afterNew() { // Setting up indicator elements // ... }
prepareData() { // Setting up data // ... }}To test our new indicator, we will invoke it programmatically.
let myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));Registering indicator class
Section titled “Registering indicator class”Since the new indicator class we created is not bundled with amCharts, the serializer/parser routines cannot process it.
This means that indicators created using custom class will not be saved and restored when using Stock Chart’s serialization techniques.
To fix that we need to register our class:
am5stock.registerClass("MyIndicator", MyIndicator);am5stock.registerClass("MyIndicator", MyIndicator);Main functionality
Section titled “Main functionality”Setting up
Section titled “Setting up”Whenever indicator is instantiated, it will run its _afterNew() method, which is supposed to set everything up.
At the very least it should create a series and set it to own series property.
class MyIndicator extends am5stock.Indicator {
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements const stockSeries = this.get("stockSeries"); const chart = stockSeries.chart;
if (chart) { const series = chart.series.push(am5xy.LineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator" }));
this.series = series; }
// Don't forget inherited stuff super._afterNew(); }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Setting up indicator elements var stockSeries = this.get("stockSeries"); var chart = stockSeries.chart;
if (chart) { var series = chart.series.push(am5xy.LineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator" }));
this.series = series; }
// Don't forget inherited stuff super._afterNew(); }
prepareData() { // Setting up data // ... }}Populating data
Section titled “Populating data”We now have a series set up, but for it to be plotted, we need to populate its data.
We’ll use indicator’s prepareData() method, to grab source data from target series, and create our own derivative data set.
In most cases, we will use stock chart’s main series as a source of the data.
class MyIndicator extends am5stock.Indicator {
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements // ...
// Don't forget inherited stuff super._afterNew(); }
public prepareData() { // Setting up data const stockSeries = this.get("stockSeries"); const dataItems = stockSeries.dataItems; let data = this._getDataArray(dataItems);
let margin = 100;
am5.array.each(data, (item, i) => { let baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Setting up indicator elements // ...
// Don't forget inherited stuff super._afterNew(); }
prepareData() { // Setting up data var stockSeries = this.get("stockSeries"); var dataItems = stockSeries.dataItems; var data = this._getDataArray(dataItems);
var margin = 100;
am5.array.each(data, function(item, i) { var baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }}Legend
Section titled “Legend”Adding to legend
Section titled “Adding to legend”If we want our indicator to appear in legend, we need to pass in legend instance, when instantiating it.
let myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));To make indicator create a legend item for itself, all we need to do is call its _handleLegend() method in _afterNew(), passing in the indicator series as a parameter.
class MyIndicator extends am5stock.Indicator {
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements // ...
// Create a legend item this._handleLegend(series);
// Don't forget inherited stuff super._afterNew(); }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Setting up indicator elements // ...
// Create a legend item this._handleLegend(series);
// Don't forget inherited stuff super._afterNew(); }
prepareData() { // Setting up data // ... }}Legend content
Section titled “Legend content”A legend will show only indicator’s (series) name by default.
We can use indicator series’ legendLabelText and legendValueText to specify what we need to see there.
class MyIndicator extends am5stock.Indicator {
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements const stockSeries = this.get("stockSeries"); const chart = stockSeries.chart;
if (chart) { const series = chart.series.push(am5xy.LineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "" }));
this.series = series;
// Create a legend item this._handleLegend(series); }
// Don't forget inherited stuff super._afterNew(); }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
public _afterNew() { // Setting up indicator elements var stockSeries = this.get("stockSeries"); var chart = stockSeries.chart;
if (chart) { var series = chart.series.push(am5xy.LineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "" }));
this.series = series;
// Create a legend item this._handleLegend(series); }
// Don't forget inherited stuff super._afterNew(); }
prepareData() { // Setting up data // ... }}Editable settings
Section titled “Editable settings”Most indicators can be configured using modal popup.
For example, users might want to check colors, functionality, or other factors.
We can define those for custom indicators as well. Let’s look at how it works.
Defining indicator settings
Section titled “Defining indicator settings”Before we can implement setting editor, we need to set up custom settings for our indicator.
It usually involves these tasks:
- Defining settings interface (strongly typed languages only, e.g. TypeScript).
- Setting defaults.
- Handling changes.
Defining settings interface
Section titled “Defining settings interface”Skip on to “Setting defaults”, if you are using vanilla JavaScript.
Just so compiler knows we are adding new settings, we need to implement a settings interface, as well as declare its use via classe’s _settings:
// Defining custom settingsexport interface MyIndicatorSettings extends am5stock.IIndicatorSettings { margin?: number; seriesStyle?: "Solid" | "Dashed"; showFill?: boolean;}
class MyIndicator extends am5stock.Indicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements // ... }
public prepareData() { // Setting up data // ... }}Setting defaults
Section titled “Setting defaults”We will use indicator’s _setDefault() method for, well, setting default values for the custom settings:
class MyIndicator extends am5stock.Indicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
declare series: am5xy.LineSeries;
public _afterNew() { // Set defaults this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Setting up indicator elements // ...
// Don't forget inherited stuff super._afterNew(); }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Set defaults this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Setting up indicator elements // ...
// Don't forget inherited stuff super._afterNew(); }
prepareData() { // Setting up data // ... }}Handling changes
Section titled “Handling changes”Indicator needs to be set up to handle all changes to its custom settings - both through API and via settings modal.
For that we will need to employ another method: _beforeChanged().
class MyIndicator extends am5stock.Indicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator settings // ... }
public _beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { const style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Setting up indicator settings // ... }
_beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { var style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
prepareData() { // Setting up data // ... }}A few notes about the above code:
isDirty()method is used to check whether value of the specific setting changed. No need to update anything if there was no change.- We don’t need to worry about
seriesColor, because it’s a setting for parentIndicatorclass, and is handled by its own change handling mechanism.
Setting up the modal
Section titled “Setting up the modal”To enable settings modal, we need to set indicator’s _editableSettings property, which is an array of IIndicatorEditableSetting objects.
Each object has the following properties:
| Key | Type | Comment |
|---|---|---|
key |
String |
A key (name) of the indicator setting. |
name |
String |
A setting name to display next to input field in modal. |
type |
"color" | "number" | "dropdown" | "checkbox" | "text" |
Type of the input field. |
minValue |
Number |
Minimum numeric value allowable in the field (for type: "number"). Values below it are clamped. |
maxValue |
Number |
Maximum numeric value allowable in the field (for type: "number"). Values above it are clamped. |
step |
Number |
Step for a numeric input. A step of 1 also marks the setting as an integer, so typed decimals are rounded to whole numbers. |
scale |
Number |
Display multiplier for the modal only. The UI shows/edits stored value × scale and divides it back on save. Does not affect the stored/serialized value or the API. |
options |
Array |
An array of options to choose from if type is set to "dropdown" |
Let’s set up am interface for editing the three custom settings we added (margin, seriesStyle, showFill) plus the seriesColor which is an inherited setting from Indicator.
class MyIndicator extends am5stock.Indicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
public _editableSettings: am5stock.IIndicatorEditableSetting[] = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
declare series: am5xy.LineSeries;
public _afterNew() { // Setting up indicator settings // ... }
public _beforeChanged() { // Handling changes // ... }
public prepareData() { // Setting up data // ... }}class MyIndicator extends am5stock.Indicator {
_editableSettings = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
_afterNew() { // Setting up indicator settings // ... }
_beforeChanged() { // Handling changes // ... }
prepareData() { // Setting up data // ... }}Cleaning up
Section titled “Cleaning up”When indicator instance is destroyed, it cleans after itself, including disposing its series (one stored in the series property).
If indicator did not create any other objects, like additional series, we’re all set.
If, however, indicator has created other elements, that need to be destroyed together with the indicator, we will need to define a _dispose() method, which does that.
class MyIndicator extends am5stock.Indicator {
public series: am5xy.LineSeries; public anotherSeries: am5xy.LineSeries;
public _afterNew() { // Setting up indicator elements // ... }
public prepareData() { // Setting up data // ... }
public _dispose() { // this.series will be disposed automatically by parent class this.anotherSeries.dispose(); super._dispose(); }}class MyIndicator extends am5stock.Indicator {
_afterNew() { // Setting up indicator elements // ... }
prepareData() { // Setting up data // ... }
_dispose() { // this.series will be disposed automatically by parent class this.anotherSeries.dispose(); super._dispose(); }}Adding to toolbar
Section titled “Adding to toolbar”To enable users to add our new indicator via UI, we need to add it to the Indicator control.
To do that we need to use indicators setting, which is an array of names of built-in indicators, or objects implementing IIndicator interface.
We’ll be using the latter to add our custom indicator.
// Create indicator controllet indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorslet indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: () => { const myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Use indicator control in the toolbarlet toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols")!, stockChart: stockChart, controls: [ indicatorControl, am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});// Create indicator controlvar indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorsvar indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: function() { const myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Use indicator control in the toolbarvar toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols"), stockChart: stockChart, controls: [ indicatorControl, am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});The object, identifying custom indicator consists of these properties:
id- a unique ID of the indicator. It must not be the same as built-in indicators.name- a name that will be shown in the dropdown.callback- a function that will be called when user selects this indicator from the list. It must return an indicator object.
Full code and example
Section titled “Full code and example”// Defining custom settingsexport interface MyIndicatorSettings extends am5stock.IIndicatorSettings { margin?: number; seriesStyle?: "Solid" | "Dashed"; showFill?: boolean;}
// Define indicator classclass MyIndicator extends am5stock.Indicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
public _editableSettings: am5stock.IIndicatorEditableSetting[] = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
declare series: am5xy.SmoothedXLineSeries;
public _afterNew() {
// Set default indicator name this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Setting up indicator elements const stockSeries = this.get("stockSeries"); const chart = stockSeries.chart;
if (chart) { const series = chart.series.push(am5xy.SmoothedXLineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, calculateAggregates: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "", stroke: this.get("seriesColor"), fill: this.get("seriesColor") }));
series.fills.template.setAll({ fillOpacity: 0.3, visible: true });
this.series = series; this._handleLegend(series); }
// Don't forget inherited stuff super._afterNew(); }
public _beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { const style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
public prepareData() { // Setting up data const stockSeries = this.get("stockSeries"); const dataItems = stockSeries.dataItems; let data = this._getDataArray(dataItems);
const margin = this.get("margin", 0);
am5.array.each(data, function(item, i) { let baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }
}
// Add custom indicatorlet myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));
// Create indicator controllet indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorslet indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: function() { const myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Stock toolbarlet toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols")!, stockChart: stockChart, controls: [ indicatorControl, am5stock.DateRangeSelector.new(root, { stockChart: stockChart }), am5stock.PeriodSelector.new(root, { stockChart: stockChart }), am5stock.DrawingControl.new(root, { stockChart: stockChart }), am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});// Define indicator classclass MyIndicator extends am5stock.Indicator {
_editableSettings = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
_afterNew() {
// Set default indicator name this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Setting up indicator elements var stockSeries = this.get("stockSeries"); var chart = stockSeries.chart;
if (chart) { var series = chart.series.push(am5xy.SmoothedXLineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, calculateAggregates: true, xAxis: stockSeries.get("xAxis"), yAxis: stockSeries.get("yAxis"), themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "", stroke: this.get("seriesColor"), fill: this.get("seriesColor") }));
series.fills.template.setAll({ fillOpacity: 0.3, visible: true });
this.series = series; this._handleLegend(series); }
// Don't forget inherited stuff super._afterNew(); }
_beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { var style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
prepareData() { // Setting up data var stockSeries = this.get("stockSeries"); var dataItems = stockSeries.dataItems; var data = this._getDataArray(dataItems);
var margin = this.get("margin", 0);
am5.array.each(data, function(item, i) { let baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }
}
// Add indicatorvar myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));
// Create indicator controlvar indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorsvar indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: function() { var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Stock toolbarvar toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols"), stockChart: stockChart, controls: [ indicatorControl, am5stock.DateRangeSelector.new(root, { stockChart: stockChart }), am5stock.PeriodSelector.new(root, { stockChart: stockChart }), am5stock.DrawingControl.new(root, { stockChart: stockChart }), am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});Dedicated panel
Section titled “Dedicated panel”An indicator can be made to create and open in its own panel.
The process of defining such indicator is similar to regular indicators, with three differences:
- It needs to extend
ChartIndicatorclass (instead ofIndicator). - It needs to have a
_createSeries()method. We need a separate method for creating series, as we can’t create them in_afterNew()like regular indicators, because new panel is not ready at that point, yet. - For indicator series, we need to use indicator panel’s own axes accessible via its
xAxisandyAxisproperties.
Here’s how updated code would look like:
// Defining custom settingsexport interface MyIndicatorSettings extends am5stock.IChartIndicatorSettings { margin?: number; seriesStyle?: "Solid" | "Dashed"; showFill?: boolean;}
// Define indicator classclass MyIndicator extends am5stock.ChartIndicator {
// Declaring custom settings use declare public _settings: MyIndicatorSettings;
public _editableSettings: am5stock.IIndicatorEditableSetting[] = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
declare series: am5xy.SmoothedXLineSeries;
public _createSeries(): am5xy.SmoothedXLineSeries { const stockSeries = this.get("stockSeries");
const series = this.panel.series.push(am5xy.SmoothedXLineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, calculateAggregates: true, xAxis: this.xAxis, yAxis: this.yAxis, themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "", stroke: this.get("seriesColor"), fill: this.get("seriesColor") }));
series.fills.template.setAll({ fillOpacity: 0.3, visible: true });
return series; }
public _afterNew() { // Set default indicator name this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Don't forget inherited stuff super._afterNew(); }
public _beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { const style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
public prepareData() { // Setting up data const stockSeries = this.get("stockSeries"); const dataItems = stockSeries.dataItems; let data = this._getDataArray(dataItems);
const margin = this.get("margin", 0);
am5.array.each(data, function(item, i) { let baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }
}
// Add custom indicatorlet myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));
// Create indicator controllet indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorslet indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: function() { const myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Stock toolbarlet toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols")!, stockChart: stockChart, controls: [ indicatorControl, am5stock.DateRangeSelector.new(root, { stockChart: stockChart }), am5stock.PeriodSelector.new(root, { stockChart: stockChart }), am5stock.DrawingControl.new(root, { stockChart: stockChart }), am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});// Define indicator classclass MyIndicator extends am5stock.ChartIndicator {
_editableSettings = [{ key: "margin", name: "Margin", type: "number" }, { key: "seriesColor", name: "Color", type: "color" }, { key: "seriesStyle", name: "Line Style", type: "dropdown", options: ["Solid", "Dashed"] }, { key: "showFill", name: "Show fill", type: "checkbox" }];
_createSeries() { var stockSeries = this.get("stockSeries");
var series = this.panel.series.push(am5xy.SmoothedXLineSeries.new(this._root, { valueXField: "valueX", valueYField: "valueY1", openValueYField: "valueY2", groupDataDisabled: true, calculateAggregates: true, xAxis: this.xAxis, yAxis: this.yAxis, themeTags: ["indicator"], name: "My indicator", legendLabelText: "{name}", legendValueText: "high: [bold]{valueY}[/] - low: [bold]{openValueY}[/]", legendRangeValueText: "", stroke: this.get("seriesColor"), fill: this.get("seriesColor") }));
series.fills.template.setAll({ fillOpacity: 0.3, visible: true });
return series; }
_afterNew() { // Set default indicator name this._setDefault("name", "My Indicator"); this._setDefault("margin", 100); this._setDefault("seriesColor", am5.color(0x045153)); this._setDefault("seriesStyle", "Solid"); this._setDefault("showFill", true);
// Don't forget inherited stuff super._afterNew(); }
_beforeChanged() {
if (this.isDirty("margin")) { this.markDataDirty(); }
if (this.isDirty("seriesStyle")) { var style = this.get("seriesStyle"); if (style == "Dashed") { this.series.strokes.template.set("strokeDasharray", [4, 4]); } else { this.series.strokes.template.remove("strokeDasharray"); } }
if (this.isDirty("showFill")) { this.series.fills.template.set("visible", this.get("showFill")); }
// Don't forget inherited stuff super._beforeChanged(); }
prepareData() { // Setting up data var stockSeries = this.get("stockSeries"); var dataItems = stockSeries.dataItems; var data = this._getDataArray(dataItems);
var margin = this.get("margin", 0);
am5.array.each(data, function(item, i) { let baseValue = dataItems[i].get("valueY", 0); item.valueY1 = baseValue + Math.round(Math.random() * margin); item.valueY2 = baseValue - Math.round(Math.random() * margin); });
this.series.data.setAll(data); }
_dispose() { this.series.dispose(); super._dispose(); }}
// Add indicatorvar myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend}));
// Create indicator controlvar indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend});
// Get current indicatorsvar indicators = indicatorControl.get("indicators", []);
// Add custom indicator to the top of the listindicators.unshift({ id: "myIndicator", name: "My indicator", callback: function() { var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); return myIndicator; }});
// Set indicator list backindicatorControl.set("indicators", indicators);
// Stock toolbarvar toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols"), stockChart: stockChart, controls: [ indicatorControl, am5stock.DateRangeSelector.new(root, { stockChart: stockChart }), am5stock.PeriodSelector.new(root, { stockChart: stockChart }), am5stock.DrawingControl.new(root, { stockChart: stockChart }), am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ]});