import * as am5 from "@amcharts/amcharts5"; import * as am5xy from "@amcharts/amcharts5/xy"; import * as am5stock from "@amcharts/amcharts5/stock"; import am5themes_Animated from "@amcharts/amcharts5/themes/Animated"; import "./data/AAPL_day.js"; import "./data/AMD_day.js"; import "./data/AMZN_day.js"; import "./data/GOOG_day.js"; import "./data/MSFT_day.js"; import "./data/NFLX_day.js"; import "./data/NVDA_day.js"; import "./data/TSLA_day.js"; // Create root element // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/getting-started/#Root_element let root = am5.Root.new("chartdiv"); // Set themes // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/concepts/themes/ root.setThemes([ am5themes_Animated.new(root) ]); // Create a stock chart // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock-chart/#Instantiating_the_chart let stockChart = root.container.children.push(am5stock.StockChart.new(root, { })); // Set global number format // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/concepts/formatters/formatting-numbers/ root.numberFormatter.set("numberFormat", "#,###.00"); // Create a main stock panel (chart) // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock-chart/#Adding_panels let mainPanel = stockChart.panels.push(am5stock.StockPanel.new(root, { wheelY: "zoomX", panX: true, panY: true })); // Create value axis // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ let valueAxis = mainPanel.yAxes.push(am5xy.ValueAxis.new(root, { renderer: am5xy.AxisRendererY.new(root, { pan: "zoom" }), extraMin: 0.1, // adds some space for for main series tooltip: am5.Tooltip.new(root, {}), numberFormat: "#,###.00", extraTooltipPrecision: 2 })); let dateAxis = mainPanel.xAxes.push(am5xy.GaplessDateAxis.new(root, { baseInterval: { timeUnit: "day", count: 1 }, renderer: am5xy.AxisRendererX.new(root, { minorGridEnabled: true }), tooltip: am5.Tooltip.new(root, {}) })); // Add series // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/xy-chart/series/ let valueSeries = mainPanel.series.push(am5xy.CandlestickSeries.new(root, { name: "MSFT", clustered: false, valueXField: "Date", valueYField: "Close", highValueYField: "High", lowValueYField: "Low", openValueYField: "Open", calculateAggregates: true, xAxis: dateAxis, yAxis: valueAxis, legendValueText: "open: [bold]{openValueY}[/] high: [bold]{highValueY}[/] low: [bold]{lowValueY}[/] close: [bold]{valueY}[/]", legendRangeValueText: "" })); // Set main value series // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock-chart/#Setting_main_series stockChart.set("stockSeries", valueSeries); // Add a stock legend // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock-chart/stock-legend/ let valueLegend = mainPanel.plotContainer.children.push(am5stock.StockLegend.new(root, { stockChart: stockChart })); // Create volume axis // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/xy-chart/axes/ let volumeAxisRenderer = am5xy.AxisRendererY.new(root, { inside: true }); volumeAxisRenderer.labels.template.set("forceHidden", true); volumeAxisRenderer.grid.template.set("forceHidden", true); let volumeValueAxis = mainPanel.yAxes.push(am5xy.ValueAxis.new(root, { numberFormat: "#.#a", height: am5.percent(20), y: am5.percent(100), centerY: am5.percent(100), renderer: volumeAxisRenderer })); // Add series // https://www.amcharts.com/docs/v5/charts/xy-chart/series/ let volumeSeries = mainPanel.series.push(am5xy.ColumnSeries.new(root, { name: "Volume", clustered: false, valueXField: "Date", valueYField: "Volume", xAxis: dateAxis, yAxis: volumeValueAxis, legendValueText: "[bold]{valueY.formatNumber('#,###.0a')}[/]" })); volumeSeries.columns.template.setAll({ strokeOpacity: 0, fillOpacity: 0.5 }); // color columns by stock rules volumeSeries.columns.template.adapters.add("fill", function(fill, target) { let dataItem = target.dataItem; if (dataItem) { return stockChart.getVolumeColor(dataItem); } return fill; }) // Set main series // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock-chart/#Setting_main_series stockChart.set("volumeSeries", volumeSeries); valueLegend.data.setAll([valueSeries, volumeSeries]); // Add cursor(s) // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/ mainPanel.set("cursor", am5xy.XYCursor.new(root, { yAxis: valueAxis, xAxis: dateAxis, snapToSeries: [valueSeries], snapToSeriesBy: "y!" })); // Add scrollbar // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/ let scrollbar = mainPanel.set("scrollbarX", am5xy.XYChartScrollbar.new(root, { orientation: "horizontal", height: 50 })); stockChart.toolsContainer.children.push(scrollbar); let sbDateAxis = scrollbar.chart.xAxes.push(am5xy.GaplessDateAxis.new(root, { baseInterval: { timeUnit: "day", count: 1 }, renderer: am5xy.AxisRendererX.new(root, {}) })); let sbValueAxis = scrollbar.chart.yAxes.push(am5xy.ValueAxis.new(root, { renderer: am5xy.AxisRendererY.new(root, {}) })); let sbSeries = scrollbar.chart.series.push(am5xy.LineSeries.new(root, { valueYField: "Close", valueXField: "Date", xAxis: sbDateAxis, yAxis: sbValueAxis })); sbSeries.fills.template.setAll({ visible: true, fillOpacity: 0.3 }); // Function that dynamically loads data function loadData(ticker, series, granularity) { let data = (window)[ticker + "_" + granularity]; am5.array.each(series, function(item) { item.data.setAll(data); }); } // Load initial data for the first series let currentGranularity = "day"; loadData("MSFT", [valueSeries, volumeSeries, sbSeries], currentGranularity); // Add comparing series addComparingSeries("AAPL"); // Set up main indices selector // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock/toolbar/comparison-control/ var mainSeriesControl = am5stock.DropdownListControl.new(root, { stockChart: stockChart, name: valueSeries.get("name"), icon: am5stock.StockIcons.getIcon("Search"), fixedLabel: true, searchable: true, searchCallback: function(query) { var mainSeries = stockChart.get("stockSeries"); var mainSeriesID = mainSeries ? mainSeries.get("name") : ""; var list = getTicker(query); am5.array.each(list, function(item) { if (item.id == mainSeriesID) { item.disabled = true; } }) return list; } }); mainSeriesControl.events.on("selected", function(ev) { var valueSeries = stockChart.get("stockSeries"); var volumeSeries = stockChart.get("volumeSeries"); mainSeriesControl.set("name", ev.item.subLabel); valueSeries.set("name", ev.item.subLabel); loadData(ev.item.subLabel, [valueSeries, volumeSeries, sbSeries], currentGranularity); }); // Set up comparison control // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock/toolbar/comparison-control/ let comparisonControl = am5stock.ComparisonControl.new(root, { stockChart: stockChart, searchable: true, searchCallback: (query) => { var compared = stockChart.getPrivate("comparedSeries", []); var main = stockChart.get("stockSeries"); if (compared.length > 4) { return [{ label: "A maximum of 5 comparisons is already selected. Remove some to add new ones.", id: "count", info: true }]; }; var comparedIds = []; am5.array.each(compared, function(series) { comparedIds.push(series.get("name")); }); var list = getTicker(query); am5.array.each(list, function(item) { if (comparedIds.indexOf(item.id) !== -1 || main.get("name") == item.id) { item.disabled = true; } }) return list; } }); comparisonControl.events.on("selected", function(ev) { addComparingSeries(ev.item.subLabel); }); function addComparingSeries(label) { let series = am5xy.LineSeries.new(root, { name: label, valueYField: "Close", calculateAggregates: true, valueXField: "Date", xAxis: dateAxis, yAxis: valueAxis, legendValueText: "{valueY.formatNumber('#.00')}" }); let comparingSeries = stockChart.addComparingSeries(series); loadData(label, [comparingSeries], currentGranularity); } function getTicker(search) { search = search.toLowerCase(); let tickers = [ { label: "Apple", subLabel: "AAPL", id: "AAPL" }, { label: "Advanced Micro Devices", subLabel: "AMD", id: "AMD" }, { label: "Microsoft", subLabel: "MSFT", id: "MSFT" }, { label: "Alphabet (Google)", subLabel: "GOOG", id: "GOOG" }, { label: "Amazon", subLabel: "AMZN", id: "AMZN" }, { label: "Tesla", subLabel: "TSLA", id: "TSLA" }, { label: "NVIDIA", subLabel: "NVDA", id: "NVDA" }, { label: "Netflix", subLabel: "NFLX", id: "NFLX" } ]; return tickers.filter((item) => { return item.label.toLowerCase().match(search) || item.subLabel.toLowerCase().match(search); }); } // Set up series type switcher // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock/toolbar/series-type-control/ let seriesSwitcher = am5stock.SeriesTypeControl.new(root, { stockChart: stockChart }); seriesSwitcher.events.on("selected", function(ev) { setSeriesType(ev.item.id); }); function getNewSettings(series) { let newSettings = []; am5.array.each(["name", "valueYField", "highValueYField", "lowValueYField", "openValueYField", "calculateAggregates", "valueXField", "xAxis", "yAxis", "legendValueText", "stroke", "fill"], function(setting) { newSettings[setting] = series.get(setting); }); return newSettings; } function setSeriesType(seriesType) { // Get current series and its settings let currentSeries = stockChart.get("stockSeries"); let newSettings = getNewSettings(currentSeries); // Remove previous series let data = currentSeries.data.values; mainPanel.series.removeValue(currentSeries); // Create new series let series; switch (seriesType) { case "line": series = mainPanel.series.push(am5xy.LineSeries.new(root, newSettings)); break; case "candlestick": case "procandlestick": newSettings.clustered = false; series = mainPanel.series.push(am5xy.CandlestickSeries.new(root, newSettings)); if (seriesType == "procandlestick") { series.columns.template.get("themeTags").push("pro"); } break; case "ohlc": newSettings.clustered = false; series = mainPanel.series.push(am5xy.OHLCSeries.new(root, newSettings)); break; } // Set new series as stockSeries if (series) { valueLegend.data.removeValue(currentSeries); series.data.setAll(data); stockChart.set("stockSeries", series); let cursor = mainPanel.get("cursor"); if (cursor) { cursor.set("snapToSeries", [series]); } valueLegend.data.insertIndex(0, series); } } // Stock toolbar // ------------------------------------------------------------------------------- // https://www.amcharts.com/docs/v5/charts/stock/toolbar/ let toolbar = am5stock.StockToolbar.new(root, { container: document.getElementById("chartcontrols"), stockChart: stockChart, controls: [ mainSeriesControl, comparisonControl, am5stock.IndicatorControl.new(root, { stockChart: stockChart, legend: valueLegend }), am5stock.DateRangeSelector.new(root, { stockChart: stockChart }), am5stock.PeriodSelector.new(root, { stockChart: stockChart }), seriesSwitcher, am5stock.DrawingControl.new(root, { stockChart: stockChart }), am5stock.ResetControl.new(root, { stockChart: stockChart }), am5stock.SettingsControl.new(root, { stockChart: stockChart }) ] })