/** * A chart. Mostly just a wrapper for the Highcharts stuff so the defaults * don't have to be set every single time. */ beestat.component.chart = function() { var self = this; this.options = {}; this.options.credits = false; this.options.exporting = { 'enabled': false, 'sourceWidth': 980, 'scale': 1, 'filename': 'beestat', 'chartOptions': { 'credits': { 'text': 'beestat.io' }, 'title': { 'align': 'left', 'text': null, 'margin': beestat.style.size.gutter, 'style': { 'color': '#fff', 'font-weight': beestat.style.font_weight.bold, 'font-size': beestat.style.font_size.large } }, 'subtitle': { 'align': 'left', 'text': null, 'style': { 'color': '#fff', 'font-weight': beestat.style.font_weight.light, 'font-size': beestat.style.font_size.normal } }, 'chart': { 'style': { 'fontFamily': 'Montserrat, Helvetica, Sans-Serif' }, 'spacing': [ beestat.style.size.gutter, beestat.style.size.gutter, beestat.style.size.gutter, beestat.style.size.gutter ] } } }; this.options.chart = { 'style': { 'fontFamily': 'Montserrat' }, 'spacing': [ beestat.style.size.gutter, 0, 0, 0 ], 'zoomType': 'x', 'panning': true, 'panKey': 'ctrl', 'backgroundColor': beestat.style.color.bluegray.base, 'resetZoomButton': { 'theme': { 'display': 'none' } } }; this.options.title = { 'text': null }; this.options.subtitle = { 'text': null }; this.options.legend = { 'itemStyle': { 'color': '#ecf0f1', 'font-weight': '500' }, 'itemHoverStyle': { 'color': '#bdc3c7' }, 'itemHiddenStyle': { 'color': '#7f8c8d' } }; this.options.plotOptions = { 'series': { 'animation': false, 'marker': { 'enabled': false }, 'states': { 'hover': { 'enabled': false }, 'inactive': { 'opacity': 1 } } }, 'column': { 'pointPadding': 0, 'borderWidth': 0, 'stacking': 'normal', 'dataLabels': { 'enabled': false } } }; this.addEventListener('render', function() { self.chart_.reflow(); }); beestat.component.apply(this, arguments); }; beestat.extend(beestat.component.chart, beestat.component); beestat.component.chart.prototype.rerender_on_breakpoint_ = false; beestat.component.chart.prototype.decorate_ = function(parent) { this.options.chart.renderTo = parent[0]; this.chart_ = Highcharts.chart(this.options); // parent.style('position', 'relative'); }; /** * Get the Highcharts chart object * * @return {object} */ beestat.component.chart.prototype.get_chart = function() { return this.chart_; }; /** * Generate a number of colors between two points. * * @param {Object} begin RGB begin color * @param {Object} end RGB end color * @param {number} steps Number of colors to generate * * @see http://forums.codeguru.com/showthread.php?259953-Code-to-create-Color-Gradient-programatically&s=4710043a327ee6059da1f8433ad1e5d2&p=795289#post795289 * * @private * * @return {Array.} RGB color array */ beestat.component.chart.generate_gradient = function(begin, end, steps) { var gradient = []; for (var i = 0; i < steps; i++) { var n = i / (steps - 1); gradient.push({ 'r': Math.round(begin.r * (1 - n) + end.r * n), 'g': Math.round(begin.g * (1 - n) + end.g * n), 'b': Math.round(begin.b * (1 - n) + end.b * n) }); } return gradient; }; beestat.component.chart.tooltip_positioner = function( chart, tooltip_width, tooltip_height, point ) { var plot_width = chart.plotWidth; var fits_on_left = (point.plotX - tooltip_width) > 0; var fits_on_right = (point.plotX + tooltip_width) < plot_width; var x; var y = 60; if (fits_on_left === true) { x = point.plotX - tooltip_width + chart.plotLeft; } else if (fits_on_right === true) { x = point.plotX + chart.plotLeft; } else { x = chart.plotLeft; } return { 'x': x, 'y': y }; }; /** * Get the HTML needed to render a tooltip. * * @param {string} title The tooltip title. * @param {array} sections Data inside the tooltip. * * @return {string} The tooltip HTML. */ beestat.component.chart.tooltip_formatter = function(title, sections) { var tooltip = $.createElement('div') .style({ 'background-color': beestat.style.color.bluegray.dark, 'padding': beestat.style.size.gutter / 2 }); var title_div = $.createElement('div') .style({ 'font-weight': beestat.style.font_weight.bold, 'font-size': beestat.style.font_size.large, 'margin-bottom': beestat.style.size.gutter / 4, 'color': beestat.style.color.gray.light }) .innerText(title); tooltip.appendChild(title_div); var table = $.createElement('table') .setAttribute({ 'cellpadding': '0', 'cellspacing': '0' }); tooltip.appendChild(table); sections.forEach(function(section, i) { if (section.length > 0) { section.forEach(function(item) { var tr = $.createElement('tr').style('color', item.color); table.appendChild(tr); var td_label = $.createElement('td') .style({ 'min-width': '115px', 'font-weight': beestat.style.font_weight.bold }) .innerText(item.label); tr.appendChild(td_label); var td_value = $.createElement('td').innerText(item.value); tr.appendChild(td_value); }); if (i < sections.length) { var spacer_tr = $.createElement('tr'); table.appendChild(spacer_tr); var spacer_td = $.createElement('td') .style('padding-bottom', beestat.style.size.gutter / 4); spacer_tr.appendChild(spacer_td); } } }); return tooltip[0].outerHTML; }; beestat.component.chart.get_outdoor_temperature_zones = function() { /* * This will get me one color for every degree on a nice gradient without * using the multicolor series plugin. Very cool. */ var zone_definitions = [ { 'value': beestat.temperature(-20), 'color': beestat.style.hex_to_rgb(beestat.style.color.lightblue.base) }, { 'value': beestat.temperature(30), 'color': beestat.style.hex_to_rgb(beestat.style.color.lightblue.base) }, { 'value': beestat.temperature(60), 'color': beestat.style.hex_to_rgb(beestat.style.color.green.base) }, { 'value': beestat.temperature(75), 'color': beestat.style.hex_to_rgb(beestat.style.color.yellow.base) }, { 'value': beestat.temperature(90), 'color': beestat.style.hex_to_rgb(beestat.style.color.red.base) }, { 'value': beestat.temperature(120), 'color': beestat.style.hex_to_rgb(beestat.style.color.red.base) } ]; var zones = []; var zone_divisor = 1; // Increase this to like 2 or 3 if there are performance issues with this series. for (var i = 0; i < zone_definitions.length - 1; i++) { var gradient = beestat.component.chart.generate_gradient( zone_definitions[i].color, zone_definitions[i + 1].color, Math.ceil((zone_definitions[i + 1].value - zone_definitions[i].value) / zone_divisor) ); for (var j = 0; j < gradient.length; j++) { zones.push({ 'value': zone_definitions[i].value + j, 'color': 'rgb(' + gradient[j].r + ',' + gradient[j].g + ',' + gradient[j].b + ')' }); } } return zones; }; /** * Wrap the highcharts SVG function with this to embed Montserrat 300 so the * graph downloads don't look like garbage. */ Highcharts.wrap(Highcharts, 'downloadSVGLocal', function( p, svg, options, failCallback, successCallback ) { p( svg.replace(/<\/svg/, '$&'), options, failCallback, successCallback ); });