This tutorial will show how we can create custom indicators for a stock chart.
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'sseries
property.
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 // ... } }
NOTESetting className
property of the indicator class is a good practice, as it is used when serializing and restoring indicators.
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 }));
NOTEThe stockChart
and stockSeries
parameters are mandatory, whereas legend
is optional, but we'll be using it anyway, since we'll need to create custom legend for our indicator.
Registering indicator class
IMPORTANTThis step is important if you allow users to save/serialize indicators.
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
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 // ... } }
NOTEDo not forget to call super._afterNew()
. We want stuff from original _afterNew()
to run, too.
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); } }
NOTEIndicator has a utility method _getDataArray()
which accepts a list of data items from series, and returns a new array, that has the same number of items with dates of the data items pre-set, but no values. We are using this array as a starting point for indicator data.
Legend
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
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 // ... } }
MORE INFOFor more information about the relation between a series and a legend, refer to "Legend label content".
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
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
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 settings export 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
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
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 parentIndicator
class, and is handled by its own change handling mechanism.
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" | Type of the input field. |
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
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(); } }
NOTEDo not forget to call super._dispose()
, which ensures that everything is cleaned up properly.
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 control let indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators let indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Use indicator control in the toolbar let 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 control var indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators var indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Use indicator control in the toolbar var 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
// Defining custom settings export interface MyIndicatorSettings extends am5stock.IIndicatorSettings { margin?: number; seriesStyle?: "Solid" | "Dashed"; showFill?: boolean; } // Define indicator class 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.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 indicator let myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); // Create indicator control let indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators let indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Stock toolbar let 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 class 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() { // 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 indicator var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); // Create indicator control var indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators var indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Stock toolbar var 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 }) ] });
See the Pen Stock Chart with custom indicator by amCharts team (@amcharts) on CodePen.
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
ChartIndicator
class (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
xAxis
andyAxis
properties.
Here's how updated code would look like:
// Defining custom settings export interface MyIndicatorSettings extends am5stock.IChartIndicatorSettings { margin?: number; seriesStyle?: "Solid" | "Dashed"; showFill?: boolean; } // Define indicator class class 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 indicator let myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); // Create indicator control let indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators let indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Stock toolbar let 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 class class 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 indicator var myIndicator = stockChart.indicators.push(MyIndicator.new(root, { stockChart: stockChart, stockSeries: stockChart.get("stockSeries"), legend: valueLegend })); // Create indicator control var indicatorControl = am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }); // Get current indicators var indicators = indicatorControl.get("indicators", []); // Add custom indicator to the top of the list indicators.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 back indicatorControl.set("indicators", indicators); // Stock toolbar var 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 }) ] });
See the Pen Stock Chart with custom indicator by amCharts team (@amcharts) on CodePen.