1
votes

I've created a webpage with a highstock chart. The chart is populated with some data. The user then adds a couple of indicators and other annotations such as lines, measurements etc.

A most informative highstock chart

Is it possible to save the state of the chart, so that chart can be restored with all indicators and annotations at a later date?

Is it possible to save the indicators and annotations and apply them to a new chart with different data? (for example the user was viewing the BHP stock price and has changed to view the GOOG stock price)

2

2 Answers

3
votes

The key is to get and save the right userOptions properties from the chart.

For example, you can save the options in localStorage and restore the chart when the page is refreshed:

document.getElementById('save')
    .addEventListener('click', function() {
        var userOptions = chart.userOptions;

        if (chart.annotations.length) {
            userOptions.annotations = getOptions(chart.annotations);
        }

        if (chart.series.length) {
            userOptions.series = getOptions(chart.series);
        }

        userOptions.xAxis = getOptions(chart.xAxis);
        userOptions.yAxis = getOptions(chart.yAxis);

        localStorage.setItem(
            'customStockToolsChart',
            JSON.stringify(chart.userOptions)
        );
    });

document.getElementById('clear')
    .addEventListener('click', function() {
        localStorage.removeItem('customStockToolsChart');
    });

...

if (localStorage.customStockToolsChart) {
    var chart = Highcharts.stockChart(
        'container',
        JSON.parse(localStorage.customStockToolsChart)
    );

    addEvents(chart);

} else {
    $.getJSON(...)
}

Live demo: https://jsfiddle.net/BlackLabel/23qcfx9a/

If you want to change the data, just replace non-indicator series in userOptions.

2
votes

Is it possible to save the state of the chart, so that chart can be restored with all indicators and annotations at a later date?

Yes, it is possible. You can simply make a copy of the initial options that you pass to Highcharts constructor and extend it with indicators, flags, annotations and y-axes that are added by a user. Then you can use saved options to initialized the new chart. Also, note that there is an icon in the GUI toolbar (last one) that will save a chart in localStorage under highcharts-chart key. Check the demo and code posted below to find the solution.

Is it possible to save the indicators and annotations and apply them to a new chart with different data?

Yes, it shouldn't be a problem. Just use saved options and change data.

Code:

var data = [
  [1538400600000, 227.95, 229.42, 226.35, 227.26, 23600800],
  [1538487000000, 227.25, 230, 226.63, 229.28, 24788200],
  [1538573400000, 230.05, 233.47, 229.78, 232.07, 28654800],
  [1538659800000, 230.78, 232.35, 226.73, 227.99, 32042000],
  [1538746200000, 227.96, 228.41, 220.58, 224.29, 33580500],
  [1539005400000, 222.21, 224.8, 220.2, 223.77, 29663900],
  [1539091800000, 223.64, 227.27, 222.25, 226.87, 26891000],
  [1539178200000, 225.46, 226.35, 216.05, 216.36, 41990600],
  [1539264600000, 214.52, 219.5, 212.32, 214.45, 53124400],
  [1539351000000, 220.42, 222.88, 216.84, 222.11, 40337900],
  [1539610200000, 221.16, 221.83, 217.27, 217.36, 30791000],
  [1539696600000, 218.93, 222.99, 216.76, 222.15, 29184000],
  [1539783000000, 222.3, 222.64, 219.34, 221.19, 22885400],
  [1539869400000, 217.86, 219.74, 213, 216.02, 32581300],
  [1539955800000, 218.06, 221.26, 217.43, 219.31, 33078700],
  [1540215000000, 219.79, 223.36, 218.94, 220.65, 28792100],
  [1540301400000, 215.83, 223.25, 214.7, 222.73, 38767800],
  [1540387800000, 222.6, 224.23, 214.54, 215.09, 40925500],
  [1540474200000, 217.71, 221.38, 216.75, 219.8, 29855800],
  [1540560600000, 215.9, 220.19, 212.67, 216.3, 47258400],
  [1540819800000, 219.19, 219.69, 206.09, 212.24, 45935500],
  [1540906200000, 211.15, 215.18, 209.27, 213.3, 36660000],
  [1540992600000, 216.88, 220.45, 216.62, 218.86, 38358900],
  [1541079000000, 219.05, 222.36, 216.81, 222.22, 58323200],
  [1541165400000, 209.55, 213.65, 205.43, 207.48, 91328700],
  [1541428200000, 204.3, 204.39, 198.17, 201.59, 66163700],
  [1541514600000, 201.92, 204.72, 201.69, 203.77, 31882900],
  [1541601000000, 205.97, 210.06, 204.13, 209.95, 33424400],
  [1541687400000, 209.98, 210.12, 206.75, 208.49, 25362600],
  [1541773800000, 205.55, 206.01, 202.25, 204.47, 34365800],
  [1542033000000, 199, 199.85, 193.79, 194.17, 51135500],
  [1542119400000, 191.63, 197.18, 191.45, 192.23, 46882900],
  [1542205800000, 193.9, 194.48, 185.93, 186.8, 60801000],
  [1542292200000, 188.39, 191.97, 186.9, 191.41, 46478800],
  [1542378600000, 190.5, 194.97, 189.46, 193.53, 36928300],
  [1542637800000, 190, 190.7, 184.99, 185.86, 41925300],
  [1542724200000, 178.37, 181.47, 175.51, 176.98, 67825200],
  [1542810600000, 179.73, 180.27, 176.55, 176.78, 31124200],
  [1542983400000, 174.94, 176.6, 172.1, 172.29, 23624000],
  [1543242600000, 174.24, 174.95, 170.26, 174.62, 44998500],
  [1543329000000, 171.51, 174.77, 170.88, 174.24, 41387400],
  [1543415400000, 176.73, 181.29, 174.93, 180.94, 46062500],
  [1543501800000, 182.66, 182.8, 177.7, 179.55, 41770000],
  [1543588200000, 180.29, 180.33, 177.03, 178.58, 39531500],
  [1543847400000, 184.46, 184.94, 181.21, 184.82, 40802500],
  [1543933800000, 180.95, 182.39, 176.27, 176.69, 41344300],
  [1544106600000, 171.76, 174.78, 170.42, 174.72, 43098400],
  [1544193000000, 173.49, 174.49, 168.3, 168.49, 42281600],
  [1544452200000, 165, 170.09, 163.33, 169.6, 62026000],
  [1544538600000, 171.66, 171.79, 167, 168.63, 47281700]
];

// split the data set into ohlc and volume
var ohlc = [],
  volume = [],
  chartOptions,
  savedOptions,
  options,
  dataLength = data.length,
  i = 0;

for (i; i < dataLength; i += 1) {
  ohlc.push([
    data[i][0], // the date
    data[i][1], // open
    data[i][2], // high
    data[i][3], // low
    data[i][4] // close
  ]);

  volume.push([
    data[i][0], // the date
    data[i][5] // the volume
  ]);
}

chartOptions = {
  yAxis: [{
    labels: {
      align: 'left'
    },
    height: '80%',
    resize: {
      enabled: true
    }
  }, {
    labels: {
      align: 'left'
    },
    top: '80%',
    height: '20%',
    offset: 0
  }],
  tooltip: {
    shape: 'square',
    headerShape: 'callout',
    borderWidth: 0,
    shadow: false,
    positioner: function(width, height, point) {
      var chart = this.chart,
        position;

      if (point.isHeader) {
        position = {
          x: Math.max(
            // Left side limit
            chart.plotLeft,
            Math.min(
              point.plotX + chart.plotLeft - width / 2,
              // Right side limit
              chart.chartWidth - width - chart.marginRight
            )
          ),
          y: point.plotY
        };
      } else {
        position = {
          x: point.series.chart.plotLeft,
          y: point.series.yAxis.top - chart.plotTop
        };
      }

      return position;
    }
  },
  series: [{
    type: 'ohlc',
    id: 'aapl-ohlc',
    name: 'AAPL Stock Price',
    data: ohlc
  }, {
    type: 'column',
    id: 'aapl-volume',
    name: 'AAPL Volume',
    data: volume,
    yAxis: 1
  }],
  responsive: {
    rules: [{
      condition: {
        maxWidth: 800
      },
      chartOptions: {
        rangeSelector: {
          inputEnabled: false
        }
      }
    }]
  }
};

var chart = Highcharts.stockChart('container', chartOptions);

$('#saveOptionsBtn').on('click', function() {

  var navigation = chart.navigationBindings,
    navChart = navigation.chart,
    indicators = [],
    flags = [];

  options = JSON.parse(JSON.stringify(chartOptions));

  navChart.series.forEach(function(series) {
    if (
      series instanceof Highcharts.seriesTypes.sma ||
      series.type === 'flags'
    ) {
      options.series.push(series.userOptions);
    }
  });

  navChart.yAxis.forEach(function(yAxis) {
    if (navigation.utils.isNotNavigatorYAxis(yAxis)) {
      options.yAxis.push(yAxis.options);
    }
  });

  navChart.annotations.forEach(function(annotation, index) {
    if (!options.annotations) {
      options.annotations = []
    }

    options.annotations.push(annotation.userOptions);
  });
});

$('#makeCopyBtn').on('click', function() {
  Highcharts.stockChart('container2', options);
});
#container,
#container2 {
  max-height: 400px;
  height: 75vh;
}

/* Conflict with Bootstrap, not needed after v7.0.1 */

.highcharts-bindings-wrapper * {
  box-sizing: content-box;
}
<link rel="stylesheet" type="text/css" href="https://code.highcharts.com/css/stocktools/gui.css">
<link rel="stylesheet" type="text/css" href="https://code.highcharts.com/css/annotations/popup.css">

<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>

<script src="https://code.highcharts.com/stock/indicators/indicators-all.js"></script>
<script src="https://code.highcharts.com/stock/modules/drag-panes.js"></script>

<script src="https://code.highcharts.com/modules/annotations-advanced.js"></script>
<script src="https://code.highcharts.com/modules/price-indicator.js"></script>
<script src="https://code.highcharts.com/modules/full-screen.js"></script>

<script src="https://code.highcharts.com/modules/stock-tools.js"></script>

<div id="container" class="chart"></div>
<button id='saveOptionsBtn'>save chart options</button>
<button id='makeCopyBtn'>make a copy of the chart</button>
<div id="container2" class="chart"></div>

Demo: