BathHacked/energy-sparks

View on GitHub
app/assets/javascripts/common_chart_options.js.erb

Summary

Maintainability
Test Coverage
Highcharts.setOptions({
  lang: {
    numericSymbols: null,  //otherwise by default ['k', 'M', 'G', 'T', 'P', 'E']
    thousandsSep: ','
  },
  chart: {
    style: {
      fontFamily: 'Inter'
    }
  }
});

function commonChartOptions(clickListener){
  return {
    colors: <%= Colours::DEFAULT_CHART_COLOURS.to_s %>,
    title: { text: null },
    exporting: {
      sourceWidth: 1278,
      sourceHeight: 665,
      scale: 1 ,
      chartOptions: {
        chart: {
          style: {
            fontFamily: 'Arial'
          }
        }
      }
    },
    xAxis: { showEmpty: false },
    yAxis: { showEmpty: false, title: { rotation: 0, margin: 30, useHTML: true, style: {fontSize: '18px'} } },
    tooltip: {
      backgroundColor: null,
      borderWidth: 0,
      shadow: false,
      useHTML: true,
      style: {
          padding: 0
      }
    },
    legend: {
      align: 'left',
      margin: 20,
      verticalAlign: 'top',
      floating: false,
      backgroundColor: 'white',
      shadow: false,
      itemStyle: { fontWeight: 'normal', fontSize: '18px' },
      itemHoverStyle: { fontWeight: 'bold', fontSize: '18px' }

    },
    plotOptions: {
      series: {
        states: {
          inactive: {
            opacity: 1
          },
          hover: {
            brightness: -0.2
          }
        },
        events: {
          legendItemClick: function(e) {
            logEvent("legend", e.target.name);
            return true;
          }
        }
      },
      bar: {
        stacking: 'normal',
      },
      column: {
        dataLabels: {
          color: '<%= Colours.chart_dark_blue %>'
        },
        point: {
          events: {
            click: clickListener
          }
        }
      },
      pie: {
        allowPointSelect: true,
        cursor: 'pointer',
        dataLabels: { enabled: false },
        showInLegend: true,
        point: {
          events: {
            legendItemClick: function () {
              return false;
            }
          }
        }
      },
      line: {
        tooltip: {
          headerFormat: '<b>{point.key}</b><br>',
          pointFormat: orderedPointFormat('kW')
        },
        point: {
          events: {
            click: clickListener
          }
        }
      },
      scatter: {
        marker: {
          radius: 5,
          states: {
            hover: {
              enabled: true,
              lineColor: 'rgb(100,100,100)'
            }
          }
        },
        states: {
          hover: {
            marker: {
              enabled: false
            }
          }
        },
        tooltip: {
          headerFormat: '<b>{series.name}</b><br>',
          pointFormat: '{point.x:.2f} °C, {point.y:.2f} kWh'
        },
        point: {
          events: {
            click: clickListener
          }
        }
      }
    }
  };
}

function barColumnLine(chartData, highchartsChart, seriesData, chartConfig, $chartDiv) {
  var subChartType = chartData.chart1_subtype;
  var chartType = chartData.chart1_type;

  var xAxisCategories = chartData.x_axis_categories;
  var yAxisLabel = chartData.y_axis_label;
  var y2AxisLabel = chartData.y2_axis_label;

  var noAdvice = chartConfig.no_advice;
  var noZoom = chartConfig.no_zoom;

  highchartsChart.xAxis[0].setCategories(xAxisCategories);


  // BAR Charts
  if (chartType == 'bar') {
    $chartDiv.addClass('bar-chart');

    if (chartData.uses_time_of_day) {
      //console.log('time of day set');
      highchartsChart.update({yAxis: { type: 'datetime', dateTimeLabelFormats: { day: '%H:%M'} }})
    }

    highchartsChart.update({ chart: { inverted: true, marginLeft: 200, marginRight: 100 }, yAxis: [{ reversedStacks: false, stackLabels: { style: { fontWeight: 'bold',  color: '<%= Colours.chart_dark_blue %>' } } }], plotOptions: { bar: { tooltip: { headerFormat: '<b>{series.name}</b><br>', pointFormat: orderedPointFormat(yAxisLabel)}}}});

    if (chartData.x_max_value) {
      highchartsChart.update({yAxis: { max: chartData.x_max_value }})
    }

    if (chartData.x_min_value) {
      highchartsChart.update({yAxis: { min: chartData.x_min_value }})
    }
  }

  // LINE charts
  if (chartType == 'line') {
    $chartDiv.addClass('line-chart');

    if (! y2AxisLabel) {
      highchartsChart.update({ plotOptions: { line: { tooltip: { headerFormat: '<b>{point.key}</b><br>',  pointFormat: orderedPointFormat(yAxisLabel) }}}});
    }
  }

  // Column charts
  if (chartType == 'column') {
    $chartDiv.addClass('column-chart');

    if (! noZoom) {
      highchartsChart.update({ chart: { zoomType: 'x'}, subtitle: { text: document.ontouchstart === undefined ?  chartData.click_and_drag_message : chartData.pinch_and_zoom_message }});
    }

    if (subChartType == 'stacked') {
      highchartsChart.update({ plotOptions: { column: { tooltip: { headerFormat: '<b>{series.name}</b><br>', pointFormat: orderedPointFormat(yAxisLabel) }, stacking: 'normal'}}, yAxis: [{title: { text: yAxisLabel }, stackLabels: { style: { fontWeight: 'bold',  color: '<%= Colours.chart_dark_blue %>' } } }]});
    } else {

      if(seriesData[0]['day_format'] && seriesData[0]['day_format']) {
        highchartsChart.update({ plotOptions: { column: { tooltip: { headerFormat: '', pointFormat: dayAndPointFormat(yAxisLabel)}}}});
      }
      else {
        highchartsChart.update({ plotOptions: { column: { tooltip: { headerFormat: '<b>{series.name}</b><br>', pointFormat: orderedPointFormat(yAxisLabel)}}}});
      }

    }
  }

  // Handle Y2 axis
  if (y2AxisLabel) {
    var colour = '<%= Colours.chart_dark_blue %>';

    //console.log('Y2 axis label' + y2AxisLabel);

    axisFontSize = '18px'

    highchartsChart.addAxis({ title: { text: chartData.y2_axis_label, rotation: 0, useHTML: true, margin: 10, style: {fontSize: axisFontSize} }, stackLabels: { style: { fontWeight: 'bold',  color: colour }}, opposite: true, max: chartData.y2_max });
    highchartsChart.update({ plotOptions: { line: { tooltip: { headerFormat: '<b>{point.key}</b><br>',  pointFormat: chartData.y2_point_format }}}});
  }

  Object.keys(seriesData).forEach(function (key) {
    //console.log('Series data name: ' + seriesData[key].name);

    if (seriesData[key].name == 'CUSUM') {
      highchartsChart.update({ plotOptions: { line: { tooltip: { pointFormat: '{point.y:.2f}', valueSuffix: yAxisLabel }}}});
    }

    if (isAStringAndStartsWith(seriesData[key].name, 'Energy') && seriesData[key].type == 'line') {
      //console.log(seriesData[key]);
      seriesData[key].tooltip = { pointFormat: orderedPointFormat(yAxisLabel) }
    }
    // The false parameter stops it being redrawed after every addition of series data
    highchartsChart.addSeries(seriesData[key], false);
  });

  updateChartLabels(chartData, highchartsChart);
  normaliseYAxis(highchartsChart);

  highchartsChart.redraw();
}

function updateExport(highchartsChart, chartConfig) {
  // Set a title & subtitle, if present, for all chart exports
  if ('export_title' in chartConfig) {
    highchartsChart.update({
      exporting: {
        chartOptions: {
          allowHTML: true,
          title: {
            useHTML: true,
            align: 'left',
            text: "<span style='font-size:21px'><b>" + chartConfig['export_title'] + '</b></span>'
          }
        }
      }
    });
  }
  if ('export_subtitle' in chartConfig) {
    highchartsChart.update({
      exporting: {
        chartOptions: {
          allowHTML: true,
          subtitle: {
            useHTML: true,
            align: 'left',
            text: "<span style='font-size:18px'>" + chartConfig['export_subtitle'] + '</span>'
          }
        }
      }
    });
  }
}

function updateChartLabels(data, chart){

  var yAxisLabel = data.y_axis_label;
  var xAxisLabel = data.x_axis_label;

  if (yAxisLabel) {
    //console.log('we have a yAxisLabel ' + yAxisLabel);
    chart.update({ yAxis: [{ title: { text: yAxisLabel, useHTML: true }}]});
  }

  if (xAxisLabel) {
    //console.log('we have a xAxisLabel ' + xAxisLabel);
    chart.update({ xAxis: [{ title: { text: xAxisLabel }}]});
  }
}

function isAStringAndStartsWith(thing, startingWith) {
  // IE Polyfill for startsWith
  if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
      value: function(search, pos) {
        pos = !pos || pos < 0 ? 0 : +pos;
        return this.substring(pos, pos + search.length) === search;
      }
    });
  }

  return (typeof thing === 'string' || thing instanceof String) && thing.startsWith(startingWith);
}

function scatter(chartData, highchartsChart, seriesData) {
  //console.log('scatter');

  updateChartLabels(chartData, highchartsChart);
  highchartsChart.update({chart: { type: 'scatter', zoomType: 'xy'}, subtitle: { text: document.ontouchstart === undefined ? chartData.click_and_drag_message : chartData.pinch_and_zoom_message }});

  Object.keys(seriesData).forEach(function (key) {
    if (seriesData[key].name.toLowerCase().startsWith("trendline")) {
      highchartsChart.addSeries(
        {
          type: 'line',
          name: seriesData[key].name,
          data: seriesData[key].data,
          connectNulls: true
        }
      )
    } else {
      highchartsChart.addSeries(seriesData[key], false)
    }
  });
  normaliseYAxis(highchartsChart);
  highchartsChart.redraw();
}

function pie(chartData, highchartsChart, seriesData, $chartDiv) {
  $chartDiv.addClass('pie-chart');
  var chartHeight = $chartDiv.height();
  var yAxisLabel = chartData.y_axis_label;

  highchartsChart.addSeries(seriesData, false);
  highchartsChart.update({chart: {
    height: chartHeight,
    plotBackgroundColor: null,
    plotBorderWidth: null,
    plotShadow: false,
    type: 'pie'
  },
  plotOptions: {
   pie: {
    tooltip: {
        headerFormat: '<b>{point.key}</b><br>',
        pointFormat: orderedPointFormat(yAxisLabel)
      }
    }
  }
  });

  highchartsChart.redraw();
}

function dayAndPointFormat(label) {
  var format = '<b>{point.day}</b><br>{point.y:.2f}';
  if(label == '£'){
    return label + format;
  } else {
    return format + ' ' + label;
  }
}

function orderedPointFormat(label){
  var format = '{point.y:.2f}';
  if(label == '£'){
    return label + format;
  } else {
    return format + ' ' + label;
  }
}

// If there are 2 axis and one or both of them dips under 0
// then make sure the scales are in the same ratio

function normaliseYAxis(chart){
  if(chart.yAxis.length === 2){
    var firstAxis = chart.yAxis[0];
    var firstExtremes = firstAxis.getExtremes();
    var secondAxis = chart.yAxis[1];
    var secondExtremes = secondAxis.getExtremes();
    if((firstExtremes.min < 0 && secondExtremes.min >= 0)){
      normaliseAxis(secondAxis, firstExtremes, secondExtremes);
    }
    if((firstExtremes.min >= 0 && secondExtremes.min <  0)){
      normaliseAxis(firstAxis, secondExtremes, firstExtremes);
    }
    if((firstExtremes.min < 0 && secondExtremes.min <  0)){
      var firstMinMaxRatio = firstExtremes.max / firstExtremes.min;
      var secondMinMaxRatio = secondExtremes.max / secondExtremes.min;
      if(firstMinMaxRatio > secondMinMaxRatio){
        normaliseAxis(secondAxis, firstExtremes, secondExtremes);
      } else {
        normaliseAxis(firstAxis, secondExtremes, firstExtremes);
      }
    }
  }
}

function normaliseAxis(axisToChange, axisAExtremes, axisBExtremes){
  var ratio = axisAExtremes.max / axisAExtremes.min;
  axisToChange.setExtremes((axisBExtremes.max / ratio), axisBExtremes.max)
}

function logEvent(action, label){
  //console.log("Logging:" + action + " - " + label);
  if (typeof gtag !== 'undefined') {
    gtag('event', action, {
      'event_category': 'Charts',
      'event_label': label
    });
  }
}