diff --git a/highcharts-api/highcharts-stock/1-live-data-tools/index.html b/highcharts-api/highcharts-stock/1-live-data-tools/index.html
index 6c800fd..1b38df5 100644
--- a/highcharts-api/highcharts-stock/1-live-data-tools/index.html
+++ b/highcharts-api/highcharts-stock/1-live-data-tools/index.html
@@ -6,12 +6,52 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/highcharts-api/highcharts-stock/1-live-data-tools/main.js b/highcharts-api/highcharts-stock/1-live-data-tools/main.js
index e69de29..a69ac9f 100644
--- a/highcharts-api/highcharts-stock/1-live-data-tools/main.js
+++ b/highcharts-api/highcharts-stock/1-live-data-tools/main.js
@@ -0,0 +1,233 @@
+/*
+QUICK EXPLANATION:
+
+1. We add a custom property "index" to each chart which enables to write less code later
+2. Random data is generated & fills the 0-series in each chart
+
+3. First chart has 2 indicators: LinearRegressionIntercept & DEMA which are linked to the 0-series
+
+4. Both charts have liveDataButton which turnes live data mode on and off
+- chart.liveInterval is the interval active on chart (or null if live data is disabled)
+
+5. function addPointsLive simulates a request to the server each 1500 miliseconds and
+returns an interval object which adds a randomly-generated point to the chart
+
+6. Second chart has changeDataGrouping button which consists of 3 button items
+- grouping-dense -> changes groupPixelWidth to small
+- group-normal -> changes groupPixelWidth to normal
+- group-wide -> changes groupPixelWidth to
+
+*/
+
+const defaultButtons = ['indicators', 'separator', 'simpleShapes', 'lines', 'crookedLines', 'measure', 'advanced', 'toggleAnnotations', 'separator', 'verticalLabels', 'flags', 'separator', 'zoomChange', 'fullScreen', 'typeChange', 'separator', 'currentPriceIndicator', 'saveChart'];
+
+const liveDataButton = {
+ className: 'live-data',
+ init(e) {
+ const chart = this.chart;
+
+ //if "liveData" is turned on (there is an interval) then turn off (clearInterval)
+ if(chart.liveInterval) {
+ clearInterval(chart.liveInterval);
+ chart.liveInterval = null;
+ }
+ //if was turned off, create an interval
+ else {
+ chart.liveInterval = addPointsLive(chart);
+ }
+
+ chart.liveLabel.attr({
+ text: `Live data: ${chart.liveInterval ? 'on' : 'off'}`
+ });
+
+ deselectButton(this, e);
+ },
+}
+
+function setDataGrouping(chart, newGroupWidth) {
+ chart.series[0].update({
+ dataGrouping: {
+ groupPixelWidth: newGroupWidth
+ }
+ });
+}
+
+function deselectButton(context, e) {
+ context.selectedButton = null;
+ context.selectedButtonElement = null;
+ if(e.classList) e.classList.remove('highcharts-active');
+}
+
+function generateData() {
+ const startDate = Date.UTC(2021, 4, 15) / 1000,
+ endDate = Date.UTC(2022, 4, 12) / 1000,
+ stepSize = 50000;
+
+ return Array.from({ length: (endDate - startDate) / stepSize + 1},
+ (_, i) => (
+ [startDate + i * stepSize, Math.floor(Math.random() * 100)]
+ ));
+}
+
+function addPointsLive(chart) {
+ return setInterval(() => {
+ chart.endDate += 5000;
+ const newPoint = [chart.endDate, Math.floor(Math.random() * 100)];
+ chart.series[0].addPoint(newPoint, true);
+ }, 1500);
+}
+
+Highcharts.stockChart('chart-one', {
+ chart: {
+ events: {
+ load() {
+ const chart = this;
+
+ chart.liveLabel = chart.renderer
+ .text('Live data: off', chart.plotLeft, chart.plotTop + 18)
+ .attr({ zIndex: 10 })
+ .add();
+
+ chart.index = 0;
+ chart.endDate = Date.UTC(2022, 4, 12) / 1000;
+ chart.liveInterval = null;
+ }
+ }
+ },
+
+ yAxis: [{
+ min: 0,
+ max: 100,
+ height: '75%',
+
+ }, {
+ top: '75%',
+ height: '25%'
+ }],
+
+ series: [{
+ name: 'AAPL',
+ id: 'main',
+ data: generateData(),
+ dataGrouping: {
+ groupPixelWidth: 15,
+ }
+ }, {
+ type: 'linearRegressionIntercept',
+ linkedTo: 'main',
+ yAxis: 1,
+ marker: {
+ enabled: false,
+ }
+ }, {
+ type: 'dema',
+ linkedTo: 'main',
+ yAxis: 1,
+ marker: {
+ enabled: false
+ },
+ }],
+
+ stockTools: {
+ gui: {
+ buttons: ['liveData', ...defaultButtons],
+
+ definitions: {
+ liveData: {
+ className: 'live-data',
+ }
+ }
+ }
+ },
+
+ navigation: {
+ bindings: {
+ liveData: liveDataButton
+ }
+ }
+});
+
+Highcharts.stockChart('chart-two', {
+ chart: {
+ events: {
+ load() {
+ const chart = this;
+
+ chart.liveLabel = chart.renderer
+ .text('Live data: off', chart.plotLeft, chart.plotTop + 18)
+ .attr({ zIndex: 10 })
+ .add();
+
+ chart.index = 1;
+ chart.endDate = Date.UTC(2022, 4, 12) / 1000;
+ chart.liveInterval = null;
+ }
+ }
+ },
+
+ series: [{
+ name: 'AAPL',
+ data: generateData(),
+ }],
+
+ stockTools: {
+ gui: {
+ buttons: ['changeDataGrouping', 'liveData', ...defaultButtons],
+
+ definitions: {
+ liveData: {
+ className: 'live-data',
+ },
+ changeDataGrouping: {
+ items: ['groupingDense', 'groupingNormal', 'groupingWide'],
+ className: 'change-data-grouping',
+ symbol: 'text.svg',
+
+ groupingDense: {
+ className: 'grouping-dense',
+ symbol: 'arrow-right.svg',
+ },
+ groupingNormal: {
+ className: 'grouping-normal',
+ symbol: 'circle.svg',
+ },
+ groupingWide: {
+ className: 'grouping-wide',
+ symbol: 'arrow-left.svg',
+ }
+ }
+ }
+ },
+ },
+
+ navigation: {
+ bindings: {
+ liveData: liveDataButton,
+ changeDataGrouping: {
+ className: 'change-data-grouping',
+ },
+ groupingDense: {
+ className: 'grouping-dense',
+ init(e) {
+ deselectButton(this, e)
+ setDataGrouping(this.chart, 2);
+ }
+ },
+ groupingNormal: {
+ className: 'grouping-normal',
+ init(e) {
+ deselectButton(this, e);
+ setDataGrouping(this.chart, 5)
+ }
+ },
+ groupingWide: {
+ className: 'grouping-wide',
+ init(e) {
+ deselectButton(this, e);
+ setDataGrouping(this.chart, 15);
+ }
+ }
+ }
+ }
+});
+
diff --git a/highcharts-api/highcharts-stock/1-live-data-tools/radio.svg b/highcharts-api/highcharts-stock/1-live-data-tools/radio.svg
new file mode 100644
index 0000000..f65b6f2
--- /dev/null
+++ b/highcharts-api/highcharts-stock/1-live-data-tools/radio.svg
@@ -0,0 +1 @@
+
\ No newline at end of file