import {generateAxisBreaks, getProfitOrLoss, timespans} from "./SharePrices_Common.js";

export function createLongChart(Instrument) {
  let rowNo = Instrument.DisplayOrder;
  let ytdStartDate = new Date(new Date().getFullYear(), 0, 1).getTime();

  // Set start date to 2020-01-01 to include pre-pandemic prices
  ytdStartDate = new Date("2020-01-01T00:00:00");

  let ytdEndDate = new Date().getTime();
  //if (Instrument.LongChart) {
  try { Instrument.LongChart.destroy(); } catch (ex) { }
  Instrument.LongChart = createChart(Instrument, ytdStartDate, ytdEndDate, 'divLongChart' + rowNo.toString(), 'divLongSummary' + rowNo.toString());
}
export function createMidChart(Instrument) {
  let rowNo = Instrument.DisplayOrder;
  let quarterStartDate = new Date().setHours(0, 0, 0) - (timespans.oneMonth * 3);
  let quarterEndDate = new Date().getTime();
  try { Instrument.MidChart.destroy(); } catch (ex) { }
  Instrument.MidChart = createChart(Instrument, quarterStartDate, quarterEndDate, 'divMidChart' + rowNo.toString(), 'divMidSummary' + rowNo.toString());
}
export function createShortChart(Instrument) {
  let rowNo = Instrument.DisplayOrder;
  let fortnightStartDate = new Date().setHours(0, 0, 0) - (timespans.oneWeek * 2);
  let fortnightEndDate = new Date().getTime();
  try { Instrument.ShortChart.destroy(); } catch (ex) { }
  Instrument.ShortChart = createChart(Instrument, fortnightStartDate, fortnightEndDate, 'divShortChart' + rowNo.toString(), 'divShortSummary' + rowNo.toString());
}
export function createSingleDayChart(Instrument) {
  let rowNo = Instrument.DisplayOrder;
  let dayStartDate = 0;
  let dayEndDate = 0;
  let dt = new Date().getTime();
  dayStartDate = Instrument.SingleDayStartDate;
  dayEndDate = Instrument.SingleDayEndDate;
  let previousClose = Instrument.SingleDayPreviousClose;

  try { Instrument.SingleDayChart.destroy(); } catch (ex) { }

  Instrument.SingleDayChart = createChart(Instrument, dayStartDate, dayEndDate, 'divDayChart' + rowNo.toString(), 'divDaySummary' + rowNo.toString(), previousClose);
  //createChart_Flot(Instrument, dayStartDate, dayEndDate, 'divDayFlotChart' + rowNo.toString(), 'divDaySummary' + rowNo.toString(), previousClose);
}
export function createChart_Flot(Instrument, startDate, endDate, chartContainerID, summaryContainerID, previousClose) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
      //return Instrument.SingleDayData;
    } else {
      //Anything over one month should only use daily data
      if (dateTo - dateFrom > (timespans.oneMonth + (timespans.oneDay * 4))) {
        for (let i = 0; i < Instrument.DailyData.length; i++) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            result.push(quote);
          }
        }
      } else {
        //Get all in-date-range intraday data
        let foundDates = { min: new Date().getTime(), max: 0 }
        for (let i = 0; i < Instrument.IntradayData.length; i++) {
          let quote = Instrument.IntradayData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            if (quote.DT > foundDates.max) foundDates.max = quote.DT;
            if (quote.DT < foundDates.min) foundDates.min = quote.DT;
            result.push(quote);
          }
        }
        //Back-fill with daily data if the intraday data doesn't cover the whole date range
        let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);
        if (minFoundDate > dateFrom) {
          for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
            let quote = Instrument.DailyData[i];
            if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
              result.unshift(quote);
            }
          }
        }
      }
    }
    return result;
  }
  function getBreakevenLine(Instrument, dateFrom, dateTo) {
    let result = [];
    let currentPrice = 0;
    for (let i = 0; i < Instrument.HoldingsHistory.length; i++) {
      let h = Instrument.HoldingsHistory[i];
      if (currentPrice > 0) { result.push([h.DT - 1, currentPrice]) };
      if (h.TotalUnits > 0) {
        currentPrice = h.TotalCost / h.TotalUnits;
        result.push([h.DT, (h.TotalCost / h.TotalUnits)]);
      } else {
        currentPrice = 0;
        result.push([h.DT, null]);
      }
    }
    if (currentPrice > 0) {
      result.push([dateTo, currentPrice]);
    }

    //Remove all points before the start of the date range
    let i = result.length - 1;
    while (i >= 0 && result[i][0] >= startDate) i--;
    while (i > 0) { result.shift(); i--; }

    return result;
  }
  function transformAndSummariseChartData(cd) {
    let seriesData = [];
    //Find the first entry within the date range
    let x = 0;
    let summaryData = {
      StartDT: cd[x].DT,
      EndDT: cd[cd.length - 1].DT,
      Open: cd[x].open,
      High: cd[x].high,
      HighDT: cd[x].DT,
      Low: cd[x].low,
      LowDT: cd[x].DT,
      Close: cd[cd.length - 1].close
    };
    while (x < cd.length && cd[x].DT < startDate) {
      summaryData.StartDT = cd[x].DT;
      summaryData.EndDT = cd[cd.length - 1].DT;
      summaryData.Open = cd[x].open;
      summaryData.High = cd[x].high;
      summaryData.HighDT = cd[x].DT;
      summaryData.Low = cd[x].low;
      summaryData.LowDT = cd[x].DT;
      summaryData.Close = cd[cd.length - 1].close;
      x++;
    };
    let basePrice = (!previousClose) ? summaryData.Open : previousClose;
    for (let i = 0; i < cd.length; i++) {
      let entry = cd[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = cd[x].DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = cd[x].DT;
      }
      if (i >= x) {
        if (priceType == 'percent') {
          seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
        } else {
          seriesData.push([entry.DT, entry.close]);
        }
      }
    }
    return { summaryData: summaryData, seriesData: seriesData, basePrice: basePrice };
  }

  let chartContainer = $("#" + chartContainerID);
  let priceType = $('#displayAsPercent').prop('checked') ? 'percent' : '';
  let chartData = [];
  if (endDate - endDate <= timespans.oneDay) {
    chartData = getChartDataSubset(Instrument, startDate, endDate);
  } else {
    chartData = getChartDataSubset(Instrument, startDate - (timespans.oneDay * 3), endDate);
  }

  let seriesData = [];
  let volumeData = [];
  if (chartData.length > 1) {
    //Summarise the data and transform values to percentage if required
    let summaryData = {
      StartDT: chartData[0].DT,
      EndDT: chartData[chartData.length - 1].DT,
      Open: chartData[0].open,
      High: chartData[0].high,
      HighDT: chartData[0].DT,
      Low: chartData[0].low,
      LowDT: chartData[0].DT,
      Close: chartData[chartData.length - 1].close
    };
    let basePrice = (!previousClose) ? summaryData.Open : previousClose;
    for (let i = 0; i < chartData.length; i++) {
      let entry = chartData[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = entry.DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = entry.DT;
      }
      if (priceType == 'percent') {
        seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
      } else {
        seriesData.push([entry.DT, entry.close]);
      }
      volumeData.push([entry.DT, entry.volume]);
    }

    //let weekendMarkings = getWeekendMarkings(startDate, endDate);
    let color = (summaryData.Close > basePrice) ? "#00cc00" : (summaryData.Close < basePrice) ? "red" : "blue";
    let series = [{ data: volumeData, yaxis: 2, color: "#304e57", bars: { show: true } }];

    //Add the breakeven price line
    if ((Instrument.ShowBreakevenLine | 0) == 1) {
      if (priceType == 'percent') {
        series.unshift({ data: [[startDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100], [endDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100]], color: '#24BCC7', shadowSize: 0, lines: { lineWidth: 1 } });
      } else {
        let b = getBreakevenLine(Instrument, startDate, endDate);
        if (b.length > 0 && b[0][0] < endDate && b[b.length - 1][0] > startDate) { //Only add the series if it falls within the chart date range
          series.push({ data: b, color: 'green', shadowSize: 0, lines: { lineWidth: 1.5 } });
        }
      }
    }

    //Add the previousClose line to the series
    if (previousClose) {
      if (priceType == 'percent') {
        series.unshift({ data: [[startDate, ((previousClose / basePrice) - 1) * 100], [endDate, ((previousClose / basePrice) - 1) * 100]], color: '#3b88ff', shadowSize: 0, lines: { lineWidth: 1 } });
      } else {
        series.unshift({ data: [[startDate, previousClose], [endDate, previousClose]], color: '#3b88ff', shadowSize: 0, lines: { lineWidth: 1 } });
      }
    }

    //Add the volume bars to the end of the series, so it is plotted behind all other lines
    series.push({ data: seriesData, color: color, shadowSize: 0, lines: { lineWidth: 1 } });

    //Dispose of any previous chart to prevent memory leaks (orphaaned DOM nodes)
    let oldChart = chartContainer.data('plot');
    if (oldChart) {// If it's destroyed, then data('plot') will be undefined
      oldChart.destroy();
    }

    //Draw the chart
    let chartTimespan = endDate - startDate;
    let gapMap = [];
    if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
      gapMap = generateChartGapMap(seriesData, timespans.oneHour);
    }
    function volumeAxisFormatter(val, axis) {
      if (val >= 1000000000)
        return (val / 1000000000).toFixed(axis.tickDecimals) + "b";
      else if (val >= 1000000)
        return (val / 1000000).toFixed(axis.tickDecimals) + "m";
      else if (val >= 1000)
        return (val / 1000).toFixed(axis.tickDecimals) + "k";
      else
        return val.toFixed(axis.tickDecimals);
    }
    var plot;
    try {
      plot = $.plot(chartContainer, series, {
        series: { downsample: { threshold: 100 } },
        grid: { show: true, hoverable: true, color: 'white', borderColor: '#505050' /*, markings: weekendMarkings*/ },
        /*crosshair: { mode: 'x', snapX: true, showXValue: false },*/
        xaxis: {
          mode: "time",
          timezone: 'browser',
          min: chartTimespan > timespans.oneDay ? summaryData.StartDT : startDate,
          max: chartTimespan > timespans.oneDay ? summaryData.EndDT : endDate,
          transform: function (v) { return mapChartXValue(v, gapMap); },
          inverseTransform: function (v) { return unmapChartXValue(v, gapMap); }
        },
        yaxes: [{ position: 'left' }, { position: 'right', tickFormatter: volumeAxisFormatter }]
      });
    }
    catch (ex) {
      logError(ex.message);
    }

    //Bind the chart tooltip
    if (plot) {
      chartContainer.bind("plothover", function (event, pos, item) {
        if (!pos.x || !pos.y) { return; }
        if (item) {
          let x = new Date(item.datapoint[0]).yyyymmddhhmmss();
          let y = item.datapoint[1].toFixed(2);
          let content = x + " = " + y;

          let o = plot.getPlotOffset();
          let t = o.top;
          let l = o.left;

          //let tt = $("#" + chartContainerID + " .chart-tooltip");
          let tt = chartContainer.children(".chart-tooltip");
          if (tt.length > 0) {
            tt.html(content).css({ top: t, left: l }).fadeIn(200);
          } else {
            tt = "<div class='chart-tooltip' style='top: " + t + "; left: " + l + ";'>" + content + "</div>";
            chartContainer.append(tt).fadeIn(200);
          }
        } else {
          //$("#" + chartContainerID + " .chart-tooltip").fadeOut(1);
          chartContainer.children(".chart-tooltip").fadeOut(1);
        }
      });
    }

    //Create the summary table
    let summaryTable = '<table class="summary">';
    if ((endDate - startDate) <= timespans.oneDay) {
      //Add market times and previous close info for Single Dat chart
      let dt = new Date().getTime();
      let marketHours = (new Date(Instrument.SingleDayStartDate)).hhmm() + ' - ' + (new Date(Instrument.SingleDayEndDate)).hhmm();
      summaryTable += '<tr><td>' + ((dt >= Instrument.SingleDayStartDate && dt <= Instrument.SingleDayEndDate) ? '<span style="color:limegreen">Market Open' : '<span style="color:firebrick">Market Closed') + '</span> (' + marketHours + ')</td>';
      summaryTable += (!previousClose) ? '' : ('<td colspan="2">Prev close: ' + previousClose.toString() + '</td>');
      summaryTable += '</tr>';
    }

    summaryTable += '<tr><td>O: ' + summaryData.Open.autoDP() + ' ' + ((previousClose) ? getProfitOrLoss(previousClose, summaryData.Open, 1) : '') + '</td>';
    summaryTable += '<td title="' + new Date(summaryData.HighDT).yyyymmddhhmmss() + '">H: ' + summaryData.High.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.High, 1) + '</td><tr>';
    summaryTable += '<tr><td title="' + new Date(summaryData.LowDT).yyyymmddhhmmss() + '">L: ' + summaryData.Low.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.Low, 1) + '</td>';
    summaryTable += '<td>C: ' + summaryData.Close.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.Close, 1) + '</tr></td>';

    //Create CurrentPrice for this instrument if it doesn't exist yet
    Instrument.CurrentPrice = Instrument.CurrentPrice || summaryData.Close;

    summaryTable += '</table>';
    $('#' + summaryContainerID).html(summaryTable);
  } else {
    //console.warn('Not enough data to plot ' + Instrument.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss() + ' to ' + chartContainerID);
  }
}
export function createChart(Instrument, startDate, endDate, chartContainerID, summaryContainerID, previousClose) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
      //return Instrument.SingleDayData;
    } else {
      //Anything over one month should only use daily data
      if (dateTo - dateFrom > (timespans.oneMonth + (timespans.oneDay * 4))) {
        for (let i = 0; i < Instrument.DailyData.length; i++) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            result.push(quote);
          }
        }
      } else {
        //Get all in-date-range intraday data
        let foundDates = { min: new Date().getTime(), max: 0 }
        for (let i = 0; i < Instrument.IntradayData.length; i++) {
          let quote = Instrument.IntradayData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            if (quote.DT > foundDates.max) foundDates.max = quote.DT;
            if (quote.DT < foundDates.min) foundDates.min = quote.DT;
            result.push(quote);
          }
        }
        //Back-fill with daily data if the intraday data doesn't cover the whole date range
        let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);
        if (minFoundDate > dateFrom) {
          for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
            let quote = Instrument.DailyData[i];
            if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
              result.unshift(quote);
            }
          }
        }
      }
    }
    return result;
  }
  function getBreakevenLine(Instrument, dateFrom, dateTo) {
    let result = [];
    let currentPrice = 0;
    for (let i = 0; i < Instrument.HoldingsHistory.length; i++) {
      let h = Instrument.HoldingsHistory[i];
      if (currentPrice > 0) { result.push([h.DT - 1, currentPrice]) };
      if (h.TotalUnits > 0) {
        currentPrice = h.TotalCost / h.TotalUnits;
        result.push([h.DT, (h.TotalCost / h.TotalUnits)]);
      } else {
        currentPrice = 0;
        result.push([h.DT, null]);
      }
    }
    if (currentPrice > 0) {
      result.push([dateTo, currentPrice]);
    }

    //Remove all points before the start of the date range
    let i = result.length - 1;
    while (i >= 0 && result[i][0] >= startDate) i--;
    while (i > 0) { result.shift(); i--; }

    return result;
  }
  function transformAndSummariseChartData(cd) {
    let seriesData = [];
    //Find the first entry within the date range
    let x = 0;
    let summaryData = {
      StartDT: cd[x].DT,
      EndDT: cd[cd.length - 1].DT,
      Open: cd[x].open,
      High: cd[x].high,
      HighDT: cd[x].DT,
      Low: cd[x].low,
      LowDT: cd[x].DT,
      Close: cd[cd.length - 1].close
    };
    while (x < cd.length && cd[x].DT < startDate) {
      summaryData.StartDT = cd[x].DT;
      summaryData.EndDT = cd[cd.length - 1].DT;
      summaryData.Open = cd[x].open;
      summaryData.High = cd[x].high;
      summaryData.HighDT = cd[x].DT;
      summaryData.Low = cd[x].low;
      summaryData.LowDT = cd[x].DT;
      summaryData.Close = cd[cd.length - 1].close;
      x++;
    };
    let basePrice = (!previousClose) ? summaryData.Open : previousClose;
    for (let i = 0; i < cd.length; i++) {
      let entry = cd[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = cd[x].DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = cd[x].DT;
      }
      if (i >= x) {
        if (priceType == 'percent') {
          seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
        } else {
          seriesData.push([entry.DT, entry.close]);
        }
      }
    }
    return { summaryData: summaryData, seriesData: seriesData, basePrice: basePrice };
  }

  let chartContainer = $('#' + chartContainerID);
  if (chartContainer.length > 0) {
    let chart = {};
    let priceType = $('#displayAsPercent').prop('checked') ? 'percent' : '';

    let chartData = [];
    if (endDate - endDate <= timespans.oneDay) {
      chartData = getChartDataSubset(Instrument, startDate, endDate);
    } else {
      chartData = getChartDataSubset(Instrument, startDate - (timespans.oneDay * 3), endDate);
    }

    let seriesData = [];
    let volumeData = [];
    if (chartData && chartData.length > 1) {

      //Summarise the data and transform values to percentage if required
      let summaryData = {
        StartDT: chartData[0].DT,
        EndDT: chartData[chartData.length - 1].DT,
        Open: chartData[0].open,
        High: chartData[0].high,
        HighDT: chartData[0].DT,
        Low: chartData[0].low,
        LowDT: chartData[0].DT,
        Close: chartData[chartData.length - 1].close
      };
      let basePrice = (!previousClose) ? summaryData.Open : previousClose;
      for (let i = 0; i < chartData.length; i++) {
        let entry = chartData[i];
        if (summaryData.High < entry.high) {
          summaryData.High = entry.high;
          summaryData.HighDT = entry.DT;
        }
        if (summaryData.Low > entry.low) {
          summaryData.Low = entry.low;
          summaryData.LowDT = entry.DT;
        }
        if (priceType == 'percent') {
          seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
        } else {
          seriesData.push([entry.DT, entry.close]);
        }
        volumeData.push([entry.DT, entry.volume]);
      }

      let series = [{ name: 'Volume', type: 'column', yAxis: 1, color: "#304e57", borderColor: "#304e57", data: volumeData }];

      //Add the breakeven price line
      if ((Instrument.ShowBreakevenLine | 0) == 1) {
        if (priceType == 'percent') {
          series.unshift({ name: 'Breakeven', type: 'line', color: '#24BCC7', lineWidth: 1, marker: { enabled: false }, data: [[startDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100], [endDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100]] /* shadowSize: 0 */ });
        } else {
          let b = getBreakevenLine(Instrument, startDate, endDate);
          if (b.length > 0 && b[0][0] < endDate && b[b.length - 1][0] > startDate) { //Only add the series if it falls within the chart date range
            series.push({ name: 'Breakeven', type: 'line', yAxis: 0, color: 'green', lineWidth: 1, marker: { enabled: false }, data: b /* shadowSize: 0 */ });
          }
        }
      }

      //Add the previousClose line to the series
      if (previousClose) {
        if (priceType == 'percent') {
          series.unshift({ name: 'Previous Close', type: 'line', yAxis: 0, color: '#3b88ff', lineWidth: 1, marker: { enabled: false }, data: [[startDate, ((previousClose / basePrice) - 1) * 100], [endDate, ((previousClose / basePrice) - 1) * 100]] /* shadowSize: 0 */ });
        } else {
          series.unshift({ name: 'Previous Close', type: 'line', yAxis: 0, color: '#3b88ff', lineWidth: 1, marker: { enabled: false }, data: [[startDate, previousClose], [endDate, previousClose]] /* shadowSize: 0 */ });
        }
      }

      //Add the price data to the end of the series, so it is plotted above all other lines
      let color = (summaryData.Close > basePrice) ? "#00cc00" : (summaryData.Close < basePrice) ? "red" : "blue";
      series.push({ name: 'Price', type: 'line', color: color, lineWidth: 1, data: seriesData /* shadowSize: 0 */ });

      //Draw the chart
      let chartTimespan = endDate - startDate;
      let xAxisBreaks = [];
      if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
        xAxisBreaks = generateAxisBreaks(seriesData, timespans.oneHour, timespans.oneHour);
      }

      try {
        chart = Highcharts.chart(chartContainerID, {
          series: series,
          chart: {
            backgroundColor: 'rgba(0,0,0,0)',
            spacingTop: 5,
            spacingBottom: 0,
            spacingLeft: 2,
            spacingRight: 0
          },
          title: { text: undefined },
          legend: { enabled: false },
          xAxis: [{
            type: 'datetime',
            lineColor: '#505050',
            tickColor: '#505050',
            tickPixelInterval: 60,
            min: chartTimespan > timespans.oneDay ? summaryData.StartDT : startDate,
            max: chartTimespan > timespans.oneDay ? summaryData.EndDT : endDate,
            breaks: xAxisBreaks,
            labels: { style: { color: '#b0b0b0', fontSize: 'x-small' } }
          }],
          yAxis: [{
            title: { text: undefined },
            gridLineColor: '#505050',
            tickPixelInterval: 30,
            endOnTick: false,
            tickLength: 20,
            labels: { style: { color: '#b0b0b0', fontSize: 'x-small' } }
          },
          {
            title: { text: undefined },
            /*gridLineColor: '#505050',*/
            gridLineWidth: 0,
            tickPixelInterval: 30,
            endOnTick: false,
            opposite: true,
            labels: { style: { color: '#b0b0b0', fontSize: 'x-small' } }
          }],
          credits: { enabled: false }
        });
      }
      catch (ex) {
        logError(ex.message);
      }

      //Create the summary table
      let summaryTable = '<table class="summary">';
      if ((endDate - startDate) <= timespans.oneDay) {
        //Add market times and previous close info for Single Day chart
        let dt = new Date().getTime();
        let marketHours = (new Date(Instrument.SingleDayStartDate)).hhmm() + ' - ' + (new Date(Instrument.SingleDayEndDate)).hhmm();
        summaryTable += '<tr><td>' + ((dt >= Instrument.SingleDayStartDate && dt <= Instrument.SingleDayEndDate) ? '<span style="color:limegreen">Market Open' : '<span style="color:firebrick">Market Closed') + '</span> (' + marketHours + ')</td>';
        summaryTable += (!previousClose) ? '' : ('<td colspan="2">Prev close: ' + previousClose.toString() + '</td>');
        summaryTable += '</tr>';
      }

      summaryTable += '<tr><td>O: ' + summaryData.Open.autoDP() + ' ' + ((previousClose) ? getProfitOrLoss(previousClose, summaryData.Open, 1) : '') + '</td>';
      summaryTable += '<td title="' + new Date(summaryData.HighDT).yyyymmddhhmmss() + '">H: ' + summaryData.High.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.High, 1) + '</td><tr>';
      summaryTable += '<tr><td title="' + new Date(summaryData.LowDT).yyyymmddhhmmss() + '">L: ' + summaryData.Low.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.Low, 1) + '</td>';
      summaryTable += '<td>C: ' + summaryData.Close.autoDP() + ' ' + getProfitOrLoss(basePrice, summaryData.Close, 1) + '</tr></td>';

      //Create CurrentPrice for this instrument if it doesn't exist yet
      Instrument.CurrentPrice = Instrument.CurrentPrice || summaryData.Close;

      summaryTable += '</table>';
      $('#' + summaryContainerID).html(summaryTable);
    } else {
      //console.warn('Not enough data to plot ' + Instrument.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss() + ' to ' + chartContainerID);
    }

    return chart;
  }
}
export function createFullScreenChart(Instrument, startDate, endDate) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
    } else {
      //Get all in-date-range intraday data
      let foundDates = { min: new Date().getTime(), max: 0 }
      for (let i = 0; i < Instrument.IntradayData.length; i++) {
        let quote = Instrument.IntradayData[i];
        if (quote.DT >= dateFrom && quote.DT <= dateTo) {
          if (quote.DT > foundDates.max) foundDates.max = quote.DT;
          if (quote.DT < foundDates.min) foundDates.min = quote.DT;
          result.push(quote);
        }
      }
      //Back-fill with daily data if the intraday data doesn't cover the whole date range
      let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);

      if (minFoundDate > dateFrom) {
        for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
            result.unshift(quote);
          }
        }
      }
    }

    return result;
  }
  function getBreakevenLine(Instrument, dateFrom, dateTo) {
    let result = [];
    let currentPrice = 0;
    for (let i = 0; i < Instrument.HoldingsHistory.length; i++) {
      let h = Instrument.HoldingsHistory[i];
      if (currentPrice > 0) { result.push([h.DT - 1, currentPrice]) };
      if (h.TotalUnits > 0) {
        currentPrice = h.TotalCost / h.TotalUnits;
        result.push([h.DT, (h.TotalCost / h.TotalUnits)]);
      } else {
        currentPrice = 0;
        result.push([h.DT, null]);
      }
    }
    if (currentPrice > 0) {
      result.push([dateTo, currentPrice]);
    }

    return result;
  }

  let chartContainerID = 'divFullScreenChart';

  let priceType = $('#displayAsPercent').prop('checked') ? 'percent' : '';
  let chartData = getChartDataSubset(Instrument, startDate, endDate);
  let seriesData = [];
  let volumeData = [];

  if (chartData.length > 1) {
    //Summarise the data and transform values to percentage if required
    let summaryData = {
      StartDT: chartData[0].DT,
      EndDT: chartData[chartData.length - 1].DT,
      Open: chartData[0].open,
      High: chartData[0].high,
      HighDT: chartData[0].DT,
      Low: chartData[0].low,
      LowDT: chartData[0].DT,
      Close: chartData[chartData.length - 1].close
    };
    let basePrice = summaryData.Open;
    for (let i = 0; i < chartData.length; i++) {
      let entry = chartData[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = entry.DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = entry.DT;
      }
      if (priceType == 'percent') {
        seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
      } else {
        seriesData.push([entry.DT, entry.close]);
      }
      volumeData.push([entry.DT, entry.volume]);
    }

    let color = (summaryData.Close > basePrice) ? "#00cc00" : (summaryData.Close < basePrice) ? "red" : "blue";
    let series = [{ name: 'Volume', type: 'column', yAxis: 1, color: "#304e57", borderColor: "#304e57", data: volumeData }];

    //Add the breakeven price line
    if (priceType == 'percent') {
      series.unshift({ name: 'Breakeven', type: 'line', yAxis: 0, color: '#24BCC7', lineWidth: 1, data: [[startDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100], [endDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100]] });
    } else {
      let b = getBreakevenLine(Instrument, startDate, endDate);
      if (b.length > 0 && b[0][0] < endDate && b[b.length - 1][0] > startDate) { //Only add the series if it falls within the chart date range
        series.push({ name: 'Breakeven', type: 'line', yAxis: 0, color: 'green', lineWidth: 1, data: b });
      }
    }

    //Add the price data to the end of the series, so it is plotted over all other lines
    series.push({ name: 'Price', type: 'line', yAxis: 0, color: color, lineWidth: 1, data: seriesData });

    //Draw the chart
    let chartTimespan = endDate - startDate;
    let xAxisBreaks = [];
    if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
      xAxisBreaks = generateAxisBreaks(series[0].data, timespans.oneHour, timespans.oneHour);
    }

    let chart = Highcharts.chart(chartContainerID, {
      series: series,
      chart: {
        backgroundColor: 'rgba(0,0,0,0)',
        zoomType: 'x'
      },
      title: { text: undefined },
      legend: {
        itemStyle: { color: '#b0b0b0' }
      },
      xAxis: [{
        type: 'datetime',
        lineColor: '#505050',
        tickColor: '#505050',
        //tickPixelInterval: 60,
        min: chartTimespan > timespans.oneDay ? summaryData.StartDT : startDate,
        max: chartTimespan > timespans.oneDay ? summaryData.EndDT : endDate,
        breaks: xAxisBreaks,
        labels: { style: { color: '#b0b0b0' /*, fontSize: 'x-small' */ } }
      }],
      yAxis: [{
        title: {
          text: 'Price (' + Instrument.Currency + ')',
          style: { color: '#b0b0b0' }
        },
        gridLineColor: '#505050',
        //endOnTick: false,
        labels: { style: { color: '#b0b0b0' /*, fontSize: 'x-small'*/ } }
      },
      {
        title: {
          text: 'Volume',
          style: { color: '#b0b0b0' }
        },
        /*gridLineColor: '#505050',*/
        gridLineWidth: 0,
        endOnTick: false,
        opposite: true,
        labels: { style: { color: '#b0b0b0' /*, fontSize: 'x-small'*/ } }
      }],
      credits: { enabled: false }
    });
  } else {
    //console.warn('Not enough data to plot ' + Instrument.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss() + ' to ' + chartContainerID);
  }
}
export function createFullScreenChart_Flot(Instrument, startDate, endDate) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
    } else {
      //Anything over 3 months should only use daily data
      if (dateTo - dateFrom > ((3 * timespans.oneMonth) + (timespans.oneDay * 4))) {
        for (let i = 0; i < Instrument.DailyData.length; i++) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            result.push(quote);
          }
        }
      } else {
        //Get all in-date-range intraday data
        let foundDates = { min: new Date().getTime(), max: 0 }
        for (let i = 0; i < Instrument.IntradayData.length; i++) {
          let quote = Instrument.IntradayData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            if (quote.DT > foundDates.max) foundDates.max = quote.DT;
            if (quote.DT < foundDates.min) foundDates.min = quote.DT;
            result.push(quote);
          }
        }
        //Back-fill with daily data if the intraday data doesn't cover the whole date range
        let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);
        if (minFoundDate > dateFrom) {
          for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
            let quote = Instrument.DailyData[i];
            if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
              result.unshift(quote);
            }
          }
        }
      }
    }

    return result;
  }
  function getBreakevenLine(Instrument, dateFrom, dateTo) {
    let result = [];
    let currentPrice = 0;
    for (let i = 0; i < Instrument.HoldingsHistory.length; i++) {
      let h = Instrument.HoldingsHistory[i];
      if (currentPrice > 0) { result.push([h.DT - 1, currentPrice]) };
      if (h.TotalUnits > 0) {
        currentPrice = h.TotalCost / h.TotalUnits;
        result.push([h.DT, (h.TotalCost / h.TotalUnits)]);
      } else {
        currentPrice = 0;
        result.push([h.DT, null]);
      }
    }
    if (currentPrice > 0) {
      result.push([dateTo, currentPrice]);
    }

    return result;
  }

  let chartContainerID = 'divFullScreenChart';
  let chartContainer = $("#" + chartContainerID);

  let priceType = $('#displayAsPercent').prop('checked') ? 'percent' : '';
  let chartData = getChartDataSubset(Instrument, startDate, endDate);
  let seriesData = [];
  let volumeData = [];

  if (chartData.length > 1) {
    //Summarise the data and transform values to percentage if required
    let summaryData = {
      StartDT: chartData[0].DT,
      EndDT: chartData[chartData.length - 1].DT,
      Open: chartData[0].open,
      High: chartData[0].high,
      HighDT: chartData[0].DT,
      Low: chartData[0].low,
      LowDT: chartData[0].DT,
      Close: chartData[chartData.length - 1].close
    };
    let basePrice = summaryData.Open;
    for (let i = 0; i < chartData.length; i++) {
      let entry = chartData[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = entry.DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = entry.DT;
      }
      if (priceType == 'percent') {
        seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
      } else {
        seriesData.push([entry.DT, entry.close]);
      }
      volumeData.push([entry.DT, entry.volume]);
    }

    //let weekendMarkings = getWeekendMarkings(startDate, endDate);
    let color = (summaryData.Close > basePrice) ? "#00cc00" : (summaryData.Close < basePrice) ? "red" : "blue";
    let series = [{ data: volumeData, yaxis: 2, color: "#304e57", bars: { show: true } }];

    //Add the breakeven price line
    //if ($('#showBreakevenLine').prop('checked')) {
    if (priceType == 'percent') {
      series.unshift({ data: [[startDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100], [endDate, ((Instrument.BreakevenPrice / basePrice) - 1) * 100]], color: '#24BCC7', shadowSize: 0, lines: { lineWidth: 1 } });
    } else {
      let b = getBreakevenLine(Instrument, startDate, endDate);
      if (b.length > 0 && b[0][0] < endDate && b[b.length - 1][0] > startDate) { //Only add the series if it falls within the chart date range
        series.push({ data: b, color: 'green', shadowSize: 0, lines: { lineWidth: 1.5 } });
      }
    }
    //}

    //Add the price data to the end of the series, so it is plotted over all other lines
    series.push({ data: seriesData, color: color, shadowSize: 0, lines: { lineWidth: 1 } });

    //Draw the chart
    let chartTimespan = endDate - startDate;
    let gapMap = [];
    if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
      gapMap = generateChartGapMap(series[0].data, timespans.oneHour);
    }
    function volumeAxisFormatter(val, axis) {
      if (val >= 1000000000)
        return (val / 1000000000).toFixed(axis.tickDecimals) + "b";
      else if (val >= 1000000)
        return (val / 1000000).toFixed(axis.tickDecimals) + "m";
      else if (val >= 1000)
        return (val / 1000).toFixed(axis.tickDecimals) + "k";
      else
        return val.toFixed(axis.tickDecimals);
    }
    let plot = $.plot(chartContainer, series, {
      grid: { show: true, hoverable: true, clickable: true, color: 'white', borderColor: '#505050' },
      selection: { mode: "x" },
      xaxis: {
        mode: "time",
        timezone: 'browser',
        min: chartTimespan > timespans.oneDay ? summaryData.StartDT : startDate,
        max: chartTimespan > timespans.oneDay ? summaryData.EndDT : endDate,
        transform: function (v) { return mapChartXValue(v, gapMap); },
        inverseTransform: function (v) { return unmapChartXValue(v, gapMap); }
      },
      yaxes: [{ axisLabel: 'Price (' + Instrument.Currency + ')', position: 'left' }, { axisLabel: 'Volume', position: 'right', tickFormatter: volumeAxisFormatter }]
    });

    //Unbind the container from any previously bound events
    chartContainer.unbind();

    //Bind the plotselected event to allow zooming
    chartContainer.bind("plotselected", function (event, ranges) { createFullScreenChart(Instrument, Math.floor(ranges.xaxis.from), Math.ceil(ranges.xaxis.to)); });
    //Reset zoom level on right-click
    chartContainer.bind("contextmenu", function () { createFullScreenComparison(Instrument1, 0, new Date().getTime()); return false; });

    //Bind the chart tooltip
    chartContainer.bind("plothover", function (event, pos, item) {
      if (!pos.x || !pos.y) { return; }
      if (item) {
        let x = new Date(item.datapoint[0]).yyyymmddhhmmss();
        let y = item.datapoint[1].toFixed(2);
        let content = x + " = " + y;

        let o = plot.getPlotOffset();
        let t = o.top;
        let l = o.left;

        //let tt = $("#" + chartContainerID + " .chart-tooltip");
        let tt = chartContainer.children(".chart-tooltip");
        if (tt.length > 0) {
          tt.html(content).css({ top: t, left: l }).fadeIn(200);
        } else {
          tt = "<div class='chart-tooltip' style='top: " + t + "; left: " + l + ";'>" + content + "</div>";
          chartContainer.append(tt).fadeIn(200);
        }
      } else {
        //$("#" + chartContainerID + " .chart-tooltip").fadeOut(1);
        chartContainer.children(".chart-tooltip").fadeOut(1);
      }
    });
    chartContainer.bind("mouseout", function (event, pos, item) {
      //$("#chartTooltip").fadeOut(1);
      chartContainer.children(".chart-tooltip").fadeOut(1);
    });
  } else {
    //console.warn('Not enough data to plot ' + Instrument.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss() + ' to ' + chartContainerID);
  }
}
export function createFullScreenComparisonChart(Instrument1, Instrument2, startDate, endDate) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
    } else {
      //Anything over 3 months should only use daily data
      if (dateTo - dateFrom > ((3 * timespans.oneMonth) + (timespans.oneDay * 4))) {
        for (let i = 0; i < Instrument.DailyData.length; i++) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            result.push(quote);
          }
        }
      } else {
        //Get all in-date-range intraday data
        let foundDates = { min: new Date().getTime(), max: 0 }
        for (let i = 0; i < Instrument.IntradayData.length; i++) {
          let quote = Instrument.IntradayData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            if (quote.DT > foundDates.max) foundDates.max = quote.DT;
            if (quote.DT < foundDates.min) foundDates.min = quote.DT;
            result.push(quote);
          }
        }
        //Back-fill with daily data if the intraday data doesn't cover the whole date range
        let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);
        if (minFoundDate > dateFrom) {
          for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
            let quote = Instrument.DailyData[i];
            if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
              result.unshift(quote);
            }
          }
        }
      }
    }

    return result;
  }
  function transformAndSummariseData(chartData, priceType) {
    let seriesData = [];

    //Summarise the data and transform values to percentage if required
    let summaryData = {
      StartDT: chartData[0].DT,
      EndDT: chartData[chartData.length - 1].DT,
      Open: chartData[0].open,
      High: chartData[0].high,
      HighDT: chartData[0].DT,
      Low: chartData[0].low,
      LowDT: chartData[0].DT,
      Close: chartData[chartData.length - 1].close
    };
    let basePrice = summaryData.Open;
    for (let i = 0; i < chartData.length; i++) {
      let entry = chartData[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = entry.DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = entry.DT;
      }
      if (priceType == 'percent') {
        seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
      } else {
        seriesData.push([entry.DT, entry.close]);
      }
    }
    return { summaryData: summaryData, seriesData: seriesData };
  }

  let chartContainerID = 'divFullScreenChart';

  let instrument1Data = getChartDataSubset(Instrument1, startDate, endDate);
  let instrument2Data = getChartDataSubset(Instrument2, startDate, endDate);

  if (instrument1Data.length > 1 && instrument2Data.length > 1) {
    let sd = transformAndSummariseData(instrument1Data, 'percent');
    let price1Data = sd.seriesData;
    let price1Summary = sd.summaryData;
    sd = transformAndSummariseData(instrument2Data, 'percent');
    let price2Data = sd.seriesData;
    let price2Summary = sd.summaryData;

    let series = [{ name: Instrument1.InstrumentName + '(' + Instrument1.Symbol + ')', type: 'line', color: 'blue', lineWidth: 1, data: price1Data },
                  { name: Instrument2.InstrumentName + '(' + Instrument2.Symbol + ')', type: 'line', color: 'yellow', lineWidth: 1, data: price2Data }];

    //Draw the chart
    let chartTimespan = endDate - startDate;
    let xAxisBreaks = [];
    if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
      xAxisBreaks = generateAxisBreaks(series[0].data, timespans.oneHour, timespans.oneHour);
    }

    let chart = Highcharts.chart(chartContainerID, {
      series: series,
      chart: {
        backgroundColor: 'rgba(0,0,0,0)',
        zoomType: 'x'
      },
      title: { text: undefined },
      legend: {
        itemStyle: { color: '#b0b0b0' }
      },
      xAxis: [{
        type: 'datetime',
        lineColor: '#505050',
        tickColor: '#505050',
        min: chartTimespan > timespans.oneDay ? price1Summary.StartDT : startDate,
        max: chartTimespan > timespans.oneDay ? price1Summary.EndDT : endDate,
        breaks: xAxisBreaks,
        labels: { style: { color: '#b0b0b0' /*, fontSize: 'x-small' */ } }
      }],
      yAxis: [{
        title: {
          text: 'Price Delta (%)',
          style: { color: '#b0b0b0' }
        },
        gridLineColor: '#505050',
        labels: { style: { color: '#b0b0b0' /*, fontSize: 'x-small'*/ } }
      }],
      credits: { enabled: false }
    });

  } else {
    chartContainer.html('Not enough data to plot ' + Instrument1.Symbol + ' vs ' + Instrument2.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss());
  }
}
export function createFullScreenComparisonChart_Flot(Instrument1, Instrument2, startDate, endDate) {
  function getChartDataSubset(Instrument, dateFrom, dateTo) {
    let result = [];
    //Single day data can be passed direct
    if (dateTo - dateFrom <= timespans.oneDay) {
      result = Instrument.SingleDayData;
    } else {
      //Anything over 3 months should only use daily data
      if (dateTo - dateFrom > ((3 * timespans.oneMonth) + (timespans.oneDay * 4))) {
        for (let i = 0; i < Instrument.DailyData.length; i++) {
          let quote = Instrument.DailyData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            result.push(quote);
          }
        }
      } else {
        //Get all in-date-range intraday data
        let foundDates = { min: new Date().getTime(), max: 0 }
        for (let i = 0; i < Instrument.IntradayData.length; i++) {
          let quote = Instrument.IntradayData[i];
          if (quote.DT >= dateFrom && quote.DT <= dateTo) {
            if (quote.DT > foundDates.max) foundDates.max = quote.DT;
            if (quote.DT < foundDates.min) foundDates.min = quote.DT;
            result.push(quote);
          }
        }
        //Back-fill with daily data if the intraday data doesn't cover the whole date range
        let minFoundDate = new Date(foundDates.min).setHours(0, 0, 0);
        if (minFoundDate > dateFrom) {
          for (let i = Instrument.DailyData.length - 1; i > 0; i--) {
            let quote = Instrument.DailyData[i];
            if (quote.DT >= dateFrom && quote.DT < minFoundDate) {
              result.unshift(quote);
            }
          }
        }
      }
    }

    return result;
  }
  function transformAndSummariseData(chartData, priceType) {
    let seriesData = [];

    //Summarise the data and transform values to percentage if required
    let summaryData = {
      StartDT: chartData[0].DT,
      EndDT: chartData[chartData.length - 1].DT,
      Open: chartData[0].open,
      High: chartData[0].high,
      HighDT: chartData[0].DT,
      Low: chartData[0].low,
      LowDT: chartData[0].DT,
      Close: chartData[chartData.length - 1].close
    };
    let basePrice = summaryData.Open;
    for (let i = 0; i < chartData.length; i++) {
      let entry = chartData[i];
      if (summaryData.High < entry.high) {
        summaryData.High = entry.high;
        summaryData.HighDT = entry.DT;
      }
      if (summaryData.Low > entry.low) {
        summaryData.Low = entry.low;
        summaryData.LowDT = entry.DT;
      }
      if (priceType == 'percent') {
        seriesData.push([entry.DT, ((entry.close / basePrice) - 1) * 100]);
      } else {
        seriesData.push([entry.DT, entry.close]);
      }
    }
    return { summaryData: summaryData, seriesData: seriesData };
  }

  let chartContainerID = 'divFullScreenChart';
  let chartContainer = $("#" + chartContainerID);

  let priceType = 'percent';
  let instrument1Data = getChartDataSubset(Instrument1, startDate, endDate);
  let instrument2Data = getChartDataSubset(Instrument2, startDate, endDate);

  if (instrument1Data.length > 1 && instrument2Data.length > 1) {
    let sd = transformAndSummariseData(instrument1Data, priceType);
    let price1Data = sd.seriesData;
    let price1Summary = sd.summaryData;
    sd = transformAndSummariseData(instrument2Data, priceType);
    let price2Data = sd.seriesData;
    let price2Summary = sd.summaryData;

    let series = [{ label: Instrument1.InstrumentName + '(' + Instrument1.Symbol + ')', data: price1Data, color: 'blue', /*yaxis: 1,*/ shadowSize: 0, lines: { lineWidth: 1 } },
                  { label: Instrument2.InstrumentName + '(' + Instrument2.Symbol + ')', data: price2Data, color: 'yellow', /*yaxis: 2,*/ shadowSize: 0, lines: { lineWidth: 1 } }];

    //Draw the chart
    let chartTimespan = endDate - startDate;
    let gapMap = [];
    if (chartTimespan > timespans.oneDay && chartTimespan < timespans.oneMonth * 3) {
      gapMap = generateChartGapMap(series[0].data, timespans.oneHour);
    }
    let plot = $.plot(chartContainer, series, {
      grid: { show: true, hoverable: true, clickable: true, color: 'white', borderColor: '#505050' },
      selection: { mode: "x" },
      legend: { show: true, position: "nw", backgroundColor: 'transparent', backgroundOpacity: 0.3 },
      xaxis: {
        mode: "time",
        timezone: 'browser',
        min: chartTimespan > timespans.oneDay ? price1Summary.StartDT : startDate,
        max: chartTimespan > timespans.oneDay ? price1Summary.EndDT : endDate,
        transform: function (v) { return mapChartXValue(v, gapMap); },
        inverseTransform: function (v) { return unmapChartXValue(v, gapMap); }
      },
      yaxes: [{ axisLabel: '% Change', position: 'left' }/*,
              { axisLabel: Instrument2.InstrumentName + ' (' + Instrument2.Currency + ')', position: 'right' }*/]
    });

    //Unbind the container from any previously bound events
    chartContainer.unbind();

    //Bind the plotselected event to allow zooming
    chartContainer.bind("plotselected", function (event, ranges) { createFullScreenComparisonChart(Instrument1, Instrument2, Math.floor(ranges.xaxis.from), Math.ceil(ranges.xaxis.to)); });
    //Reset zoom level on right-click
    chartContainer.bind("contextmenu", function () { createFullScreenComparisonChart(Instrument1, Instrument2, 0, new Date().getTime()); return false; });

    //Bind the chart tooltip
    chartContainer.bind("plothover", function (event, pos, item) {
      if (!pos.x || !pos.y) { return; }
      if (item) {
        let x = new Date(item.datapoint[0]).yyyymmddhhmmss();
        let y = item.datapoint[1].toFixed(2);
        let content = x + " = " + y;

        let o = plot.getPlotOffset();
        let t = o.top;
        let l = o.left;

        //let tt = $("#" + chartContainerID + " .chart-tooltip");
        let tt = chartContainer.children(".chart-tooltip");
        if (tt.length > 0) {
          tt.html(content).css({ top: t, left: l }).fadeIn(200);
        } else {
          tt = "<div class='chart-tooltip' style='top: " + t + "; left: " + l + ";'>" + content + "</div>";
          chartContainer.append(tt).fadeIn(200);
        }
      } else {
        //$("#" + chartContainerID + " .chart-tooltip").fadeOut(1);
        chartContainer.children(".chart-tooltip").fadeOut(1);
      }
    });
    chartContainer.bind("mouseout", function (event, pos, item) {
      //$("#chartTooltip").fadeOut(1);
      chartContainer.children(".chart-tooltip").fadeOut(1);
    });

  } else {
    chartContainer.html('Not enough data to plot ' + Instrument1.Symbol + ' vs ' + Instrument2.Symbol + ' ' + new Date(startDate).yyyymmddhhmmss() + ' -> ' + new Date(endDate).yyyymmddhhmmss());
  }
}
