803 lines
26 KiB
JavaScript
803 lines
26 KiB
JavaScript
/**
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
* not use this file except in compliance with the License. You may obtain
|
|
* a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
/* global Rickshaw */
|
|
/*
|
|
Draw line chart in d3.
|
|
|
|
To use, a div is required with the data attributes
|
|
data-chart-type="line_chart", data-data.
|
|
|
|
data-chart-type - REQUIRED(string) must be "line_chart" so chart gets initialized
|
|
data-data - REQUIRED(string or json):
|
|
string: url for the json data for the chart
|
|
json: json stringified object for the chart data
|
|
data-form-selector - Optional(string) jQuery selector of Forms that controls this chart
|
|
data-legend-selector - Optional(string) jQuery selector of div element that will display legend
|
|
data-smoother-selector - Optional(string) jQuery selector of TODO(lsmola)
|
|
data-slider-selector - Optional(string) jQuery selector of TODO(lsmola)
|
|
|
|
|
|
If used in popup, initialization must be made manually e.g.:
|
|
|
|
if (typeof horizon.d3_line_chart !== 'undefined') {
|
|
horizon.d3_line_chart.init("div[data-chart-type='line_chart']");
|
|
}
|
|
|
|
|
|
Example:
|
|
<div id="line_chart"
|
|
data-chart-type="line_chart"
|
|
data-data="{% url 'horizon:admin:metering:samples'%}"
|
|
data-form-selector='#linechart_general_form'>
|
|
</div>
|
|
<div id="linea_chart2"
|
|
data-chart-type="line_chart"
|
|
data-data="{% url 'horizon:admin:metering:samples'%}?query=not_popular_data"
|
|
data-form-selector='#linechart_general_form'>
|
|
</div>
|
|
|
|
|
|
The data format example:
|
|
Url has to return JSON in format:
|
|
{
|
|
"series": [
|
|
{
|
|
"name": "instance-00000005",
|
|
"data": [
|
|
{"y": 171, "x": "2013-08-21T11:22:25"},
|
|
{"y": 172, "x": "2013-08-21T11:22:26"}
|
|
]
|
|
}, {
|
|
"name": "instance-00000006",
|
|
"data": [
|
|
{"y": 161, "x": "2013-08-21T11:22:25"},
|
|
{"y": 162, "x": "2013-08-21T11:22:26"}
|
|
]
|
|
}
|
|
],
|
|
"settings": {}
|
|
}
|
|
|
|
Example of line-bar chart sparkline:
|
|
|
|
<div class="overview_chart">
|
|
<div class="chart_container">
|
|
<div class="chart"
|
|
data-chart-type="line_chart"
|
|
data-data="/admin/samples?meter=test2"
|
|
data-form-selector='#linechart_general_form'>
|
|
</div>
|
|
</div>
|
|
<div class="bar_chart_container">
|
|
<div class="chart" data-chart-type="overview_bar_chart">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{
|
|
"series": [
|
|
{
|
|
"name": "instance-00000005",
|
|
"data": [
|
|
{"y": 171, "x": "2013-08-21T11:22:25"},
|
|
{"y": 172, "x": "2013-08-21T11:22:26"}
|
|
]
|
|
}, {
|
|
"name": "instance-00000006",
|
|
"data": [
|
|
{"y": 161, "x": "2013-08-21T11:22:25"},
|
|
{"y": 162, "x": "2013-08-21T11:22:26"}
|
|
]
|
|
}
|
|
],
|
|
"settings": {
|
|
'renderer': 'StaticAxes',
|
|
'yMin': 0,
|
|
'yMax': 100,
|
|
'higlight_last_point': True,
|
|
"auto_size": False, 'auto_resize': False,
|
|
"axes_x" : False, "axes_y" : False,
|
|
'bar_chart_settings': {
|
|
'orientation': 'vertical',
|
|
'used_label_placement': 'left',
|
|
'width': 30,
|
|
'color_scale_domain': [0, 80, 80, 100],
|
|
'color_scale_range': ['#00FE00', '#00FF00', '#FE0000', '#FF0000'],
|
|
'average_color_scale_domain': [0, 100],
|
|
'average_color_scale_range': ['#0000FF', '#0000FF']
|
|
}
|
|
},
|
|
"stats": {
|
|
'average': 20,
|
|
'used': 30,
|
|
'tooltip_average': tooltip_average
|
|
}
|
|
}
|
|
|
|
|
|
The control Forms:
|
|
There are currently 2 form elements that can be connected to charts and act
|
|
as controls. Elements listen for change event and refresh the chart on change event.
|
|
Chart can be connected to multiple forms via selector, all for data will be sent on
|
|
change.
|
|
Form can be connected to multiple charts, all charts will be refreshed when form
|
|
element changes.
|
|
The firsts rendering of the chart takes data from the connected Forms.
|
|
|
|
1. Selectbox
|
|
The data attribute 'data-line-chart-command="select_box_change' needs to be defined on
|
|
select element.
|
|
|
|
Example:
|
|
<form class="form-horizontal"
|
|
id="linechart_general_form">
|
|
|
|
<div class="form-group">
|
|
<label for="meter" class="control-label col-sm-2">{% trans "Metric" %}: </label>
|
|
<div class="col-sm-10">
|
|
<select data-line-chart-command="select_box_change"
|
|
name="meter" id="meter" class="col-sm-w form-control example">
|
|
{% for meter in meters %}
|
|
<option value="{{ meter }}" data-unit="{{ meter }}">
|
|
{{ meter }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
2. Date picker
|
|
The data attribute 'data-line-chart-command="date_picker_change"' needs to be defined on
|
|
input element.
|
|
|
|
Example:
|
|
<form class="form-horizontal"
|
|
id="linechart_general_form">
|
|
<div class="form-group" id="date_from">
|
|
<label for="date_from" class="control-label col-sm-2">{% trans "From" %}: </label>
|
|
<div class="col-sm-10">
|
|
<input data-line-chart-command="date_picker_change"
|
|
type="text" id="date_from" name="date_from" class="form-control example"/>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
*/
|
|
|
|
Rickshaw.namespace('Rickshaw.Graph.Renderer.StaticAxes');
|
|
Rickshaw.Graph.Renderer.StaticAxes = Rickshaw.Class.create(Rickshaw.Graph.Renderer.Line, {
|
|
name: 'StaticAxes',
|
|
defaults: function($super) {
|
|
return Rickshaw.extend($super(), {
|
|
xMin: undefined,
|
|
xMax: undefined,
|
|
yMin: undefined,
|
|
yMax: undefined
|
|
});
|
|
},
|
|
domain: function($super) {
|
|
var ret = $super();
|
|
var xMin, xMax;
|
|
// If y axis wants to have static range, not based on data
|
|
if (this.yMin !== undefined && this.yMax !== undefined){
|
|
ret.y = [this.yMin, this.yMax];
|
|
}
|
|
// If x axis wants to have static range, not based on data
|
|
if (this.xMin !== undefined && this.xMax !== undefined){
|
|
xMin = d3.time.format.utc('%Y-%m-%dT%H:%M:%S').parse(this.xMin);
|
|
xMin = xMin.getTime() / 1000;
|
|
xMax = d3.time.format.utc('%Y-%m-%dT%H:%M:%S').parse(this.xMax);
|
|
xMax = xMax.getTime() / 1000;
|
|
|
|
ret.x = [xMin, xMax];
|
|
}
|
|
return ret;
|
|
}
|
|
});
|
|
|
|
horizon.d3_line_chart = {
|
|
/**
|
|
* A class representing the line chart
|
|
* @param chart_module A context of horizon.d3_line_chart module.
|
|
* @param html_element A html_element containing the chart.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
LineChart: function(chart_module, html_element, settings){
|
|
var self = this;
|
|
var jquery_element = $(html_element);
|
|
|
|
self.chart_module = chart_module;
|
|
self.html_element = html_element;
|
|
self.jquery_element = jquery_element;
|
|
self.$spinner = horizon.loader.inline(gettext('Loading')).hide().appendTo(jquery_element);
|
|
|
|
/************************************************************************/
|
|
/*********************** Initialization methods *************************/
|
|
/************************************************************************/
|
|
/**
|
|
* Initialize object
|
|
*/
|
|
self.init = function() {
|
|
var self = this;
|
|
/* TODO(lsmola) make more configurable init from more sources */
|
|
self.legend_element = $(jquery_element.data('legend-selector')).get(0);
|
|
self.slider_element = $(jquery_element.data('slider-selector')).get(0);
|
|
|
|
// Set the data, undefined if it doesn't exist
|
|
self.data = jquery_element.data('data');
|
|
self.url_parameters = jquery_element.data('url_parameters');
|
|
|
|
if (typeof self.data === 'string') {
|
|
self.final_url = self.data;
|
|
if (jquery_element.data('form-selector')){
|
|
$(jquery_element.data('form-selector')).each(function(){
|
|
// Add serialized data from all connected forms to url.
|
|
if (self.final_url.indexOf('?') > -1){
|
|
self.final_url += '&' + $(this).serialize();
|
|
} else {
|
|
self.final_url += '?' + $(this).serialize();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
self.color = d3.scale.category10();
|
|
|
|
// Self aggregation and statistic attrs
|
|
self.stats = {};
|
|
self.stats.average = 0;
|
|
self.stats.last_value = 0;
|
|
|
|
// Load initial settings.
|
|
self.init_settings(settings);
|
|
// Get correct size of chart and the wrapper.
|
|
self.get_size();
|
|
};
|
|
/**
|
|
* Initialize settings of the chart with default values, then applies
|
|
* defined settings of the chart. Settings are obtained either from JSON
|
|
* of the html attribute data-settings, or from init of the charts. The
|
|
* highest priority settings are obtained directly from the JSON data
|
|
* obtained from the server.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
self.init_settings = function(settings) {
|
|
var self = this;
|
|
|
|
self.settings = {};
|
|
self.settings.renderer = 'line';
|
|
self.settings.auto_size = true;
|
|
self.settings.axes_x = true;
|
|
self.settings.axes_y = true;
|
|
self.settings.axes_y_label = true;
|
|
self.settings.interpolation = 'linear';
|
|
// Static y axes values
|
|
self.settings.yMin = undefined;
|
|
self.settings.yMax = undefined;
|
|
// Static x axes values
|
|
self.settings.xMin = undefined;
|
|
self.settings.xMax = undefined;
|
|
// Show last point as dot
|
|
self.settings.higlight_last_point = false;
|
|
|
|
// Composed charts wrapper
|
|
self.settings.composed_chart_selector = '.overview_chart';
|
|
// Bar chart component
|
|
self.settings.bar_chart_selector = 'div[data-chart-type="overview_bar_chart"]';
|
|
self.settings.bar_chart_settings = undefined;
|
|
|
|
// allowed: verbose
|
|
self.hover_formatter = 'verbose';
|
|
|
|
/*
|
|
Applying settings. The later application rewrites the previous
|
|
therefore it has bigger priority.
|
|
*/
|
|
|
|
// Settings defined in the init method of the chart
|
|
if (settings){
|
|
self.apply_settings(settings);
|
|
}
|
|
|
|
// Settings defined in the html data-settings attribute
|
|
if (self.jquery_element.data('settings')){
|
|
var inline_settings = self.jquery_element.data('settings');
|
|
self.apply_settings(inline_settings);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Applies passed settings to the chart object. Allowed settings are
|
|
* listed in this method.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
self.apply_settings = function(settings){
|
|
var self = this;
|
|
|
|
var allowed_settings = ['renderer', 'auto_size', 'axes_x', 'axes_y',
|
|
'interpolation', 'yMin', 'yMax', 'xMin', 'xMax', 'bar_chart_settings',
|
|
'bar_chart_selector', 'composed_chart_selector',
|
|
'higlight_last_point', 'axes_y_label'];
|
|
|
|
jQuery.each(allowed_settings, function(index, setting_name) {
|
|
if (settings[setting_name] !== undefined){
|
|
self.settings[setting_name] = settings[setting_name];
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Computes size of the chart from surrounding divs. When
|
|
* settings.auto_size is on, it stretches the chart to bottom of
|
|
* the screen.
|
|
*/
|
|
self.get_size = function(){
|
|
/*
|
|
The height will be determined by css or window size,
|
|
I have to hide everything inside that could mess with
|
|
the size, so it is fully determined by outer CSS.
|
|
*/
|
|
$(self.html_element).css('height', '');
|
|
$(self.html_element).css('width', '');
|
|
var svg = $(self.html_element).find('svg');
|
|
svg.hide();
|
|
|
|
/*
|
|
Width and height of the chart will be taken from chart wrapper,
|
|
that can be styled by css.
|
|
*/
|
|
self.width = jquery_element.width();
|
|
|
|
// Set either the minimal height defined by CSS.
|
|
self.height = jquery_element.height();
|
|
/*
|
|
Or stretch it to the remaining height of the window if there
|
|
is a place. + some space on the bottom, lets say 30px.
|
|
*/
|
|
if (self.settings.auto_size) {
|
|
var auto_height = $(window).height() - jquery_element.offset().top - 30;
|
|
if (auto_height > self.height) {
|
|
self.height = auto_height;
|
|
}
|
|
}
|
|
|
|
/* Setting new sizes. It is important when resizing a window.*/
|
|
$(self.html_element).css('height', self.height);
|
|
$(self.html_element).css('width', self.width);
|
|
svg.show();
|
|
svg.css('height', self.height);
|
|
svg.css('width', self.width);
|
|
};
|
|
|
|
/************************************************************************/
|
|
/****************************** Initialization **************************/
|
|
/************************************************************************/
|
|
// Init of the object
|
|
self.init();
|
|
|
|
/************************************************************************/
|
|
/****************************** Error Message ***************************/
|
|
/************************************************************************/
|
|
// Load the data
|
|
self.error_message = function(error) {
|
|
$(self.html_element).html(error);
|
|
$(self.legend_element).empty();
|
|
// Setting a fix height breaks things when legend is getting
|
|
// bigger.
|
|
$(self.legend_element).css('height', '');
|
|
// FIXME add proper fail message
|
|
horizon.toast.add('error',
|
|
gettext('An error occurred. Please try again later.'));
|
|
};
|
|
|
|
/************************************************************************/
|
|
/******************************** Data Load *****************************/
|
|
/************************************************************************/
|
|
// Load the data
|
|
self.load_data = function(data) {
|
|
|
|
// Clearing the old chart data.
|
|
self.jquery_element.empty();
|
|
$(self.legend_element).empty();
|
|
|
|
self.series = data.series;
|
|
self.stats = data.stats;
|
|
// The highest priority settings are sent with the data.
|
|
self.apply_settings(data.settings);
|
|
|
|
if (self.series.length <= 0) {
|
|
$(self.html_element).html(gettext('No data available.'));
|
|
$(self.legend_element).empty();
|
|
// Setting a fix height breaks things when legend is getting
|
|
// bigger.
|
|
$(self.legend_element).css('height', '');
|
|
} else {
|
|
self.render();
|
|
}
|
|
};
|
|
|
|
/************************************************************************/
|
|
/****************************** Methods *********************************/
|
|
/************************************************************************/
|
|
/**
|
|
* Obtains the actual chart data and renders the chart again.
|
|
*/
|
|
self.refresh = function (){
|
|
var self = this;
|
|
|
|
self.start_loading();
|
|
if (typeof self.data === 'string') {
|
|
horizon.ajax.queue({
|
|
url: self.final_url,
|
|
success: function (data) {
|
|
self.load_data(data);
|
|
},
|
|
error: function () {
|
|
self.error_message(gettext('No data available.'));
|
|
},
|
|
complete: function () {
|
|
self.finish_loading();
|
|
}
|
|
});
|
|
} else if (self.data) {
|
|
self.load_data(self.data);
|
|
self.finish_loading();
|
|
} else {
|
|
self.error_message(gettext('No data available.'));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Renders the chart using Rickshaw library.
|
|
*/
|
|
self.render = function(){
|
|
var self = this;
|
|
var last_point, last_point_color;
|
|
|
|
$.map(self.series, function (serie) {
|
|
serie.color = last_point_color = self.color(serie.name);
|
|
$.map(serie.data, function (statistic) {
|
|
// need to parse each date
|
|
statistic.x = d3.time.format.utc('%Y-%m-%dT%H:%M:%S').parse(statistic.x);
|
|
statistic.x = statistic.x.getTime() / 1000;
|
|
last_point = statistic;
|
|
last_point.color = serie.color;
|
|
});
|
|
});
|
|
|
|
var renderer = self.settings.renderer;
|
|
if (renderer === 'StaticAxes'){
|
|
renderer = Rickshaw.Graph.Renderer.StaticAxes;
|
|
}
|
|
|
|
// clean the old graph the messy way
|
|
// TODO(lsmola) clena when the Rickshaw issue is gone
|
|
// https://github.com/shutterstock/rickshaw/issues/432
|
|
self.jquery_element.empty();
|
|
|
|
var $newGraph = self.jquery_element.clone();
|
|
self.jquery_element.replaceWith($newGraph);
|
|
self.jquery_element = $newGraph;
|
|
self.html_element = self.jquery_element[0];
|
|
|
|
// instantiate our graph!
|
|
var graph = new Rickshaw.Graph({
|
|
element: self.html_element,
|
|
width: self.width,
|
|
height: self.height,
|
|
renderer: renderer,
|
|
series: self.series,
|
|
yMin: self.settings.yMin,
|
|
yMax: self.settings.yMax,
|
|
xMin: self.settings.xMin,
|
|
xMax: self.settings.xMax,
|
|
interpolation: self.settings.interpolation
|
|
});
|
|
|
|
/*
|
|
TODO(lsmola) add jQuery UI slider to make this work
|
|
if (self.slider_element) {
|
|
var slider = new Rickshaw.Graph.RangeSlider({
|
|
graph: graph,
|
|
element: $(self.slider_element)
|
|
});
|
|
}
|
|
*/
|
|
graph.render();
|
|
|
|
if (self.hover_formatter === 'verbose'){
|
|
new Rickshaw.Graph.HoverDetail({
|
|
graph: graph,
|
|
formatter: function(series, x, y) {
|
|
if (y % 1 === 0) {
|
|
y = parseInt(y, 10);
|
|
} else {
|
|
y = parseFloat(y).toFixed(2);
|
|
}
|
|
|
|
var d = new Date(x * 1000);
|
|
// Convert datetime to YYYY-MM-DD HH:MM:SS GMT
|
|
var datetime_string = d.getUTCFullYear() + "-" +
|
|
("00" + (d.getUTCMonth() + 1)).slice(-2) + "-" +
|
|
("00" + d.getUTCDate()).slice(-2) + " " +
|
|
("00" + d.getUTCHours()).slice(-2) + ":" +
|
|
("00" + d.getUTCMinutes()).slice(-2) + ":" +
|
|
("00" + d.getUTCSeconds()).slice(-2) + " GMT";
|
|
|
|
var date = '<span class="date">' + datetime_string + '</span>';
|
|
var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
|
|
return swatch + series.name + ': ' + y + ' ' + series.unit + '<br>' + date;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (self.legend_element) {
|
|
var legend = new Rickshaw.Graph.Legend({
|
|
graph: graph,
|
|
element: self.legend_element
|
|
});
|
|
|
|
new Rickshaw.Graph.Behavior.Series.Toggle({
|
|
graph: graph,
|
|
legend: legend
|
|
});
|
|
|
|
new Rickshaw.Graph.Behavior.Series.Order({
|
|
graph: graph,
|
|
legend: legend
|
|
});
|
|
|
|
new Rickshaw.Graph.Behavior.Series.Highlight({
|
|
graph: graph,
|
|
legend: legend
|
|
});
|
|
}
|
|
if (self.settings.axes_x) {
|
|
var axes_x = new Rickshaw.Graph.Axis.Time({
|
|
graph: graph
|
|
});
|
|
axes_x.render();
|
|
}
|
|
if (self.settings.axes_y) {
|
|
var axes_y_settings = {
|
|
graph: graph
|
|
};
|
|
if (!self.settings.axes_y_label){
|
|
// hiding label of Y axis if setting is set to false
|
|
axes_y_settings.tickFormat = (function () { return ''; });
|
|
}
|
|
var axes_y = new Rickshaw.Graph.Axis.Y(axes_y_settings);
|
|
axes_y.render();
|
|
}
|
|
|
|
/*
|
|
Setting a fix height breaks things when chart is refreshed and
|
|
legend is getting bigger.
|
|
*/
|
|
$(self.legend_element).css('height', '');
|
|
|
|
// Render bar chart
|
|
if (self.stats !== undefined){
|
|
var composed_chart = self.jquery_element.parents(self.settings.composed_chart_selector).first();
|
|
var bar_chart_html = composed_chart.find(self.settings.bar_chart_selector).get(0);
|
|
|
|
horizon.d3_bar_chart.refresh(bar_chart_html,
|
|
self.settings.bar_chart_settings,
|
|
self.stats);
|
|
}
|
|
|
|
// Render ending dot to last point
|
|
if (self.settings.higlight_last_point){
|
|
if (last_point !== undefined && last_point_color !== undefined){
|
|
graph.vis.append('circle')
|
|
.attr('class', 'used_component')
|
|
.attr('cy', graph.y(last_point.y))
|
|
.attr('cx', graph.x(last_point.x))
|
|
.attr('r', 2)
|
|
.style('fill', last_point_color)
|
|
.style('stroke', last_point_color)
|
|
.style('stroke-width', 2);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Shows chart loader with backdrop. Backdrop is computed to hide
|
|
* the canvas with chart. Loader is computed to be placed in the center.
|
|
* Hides also a block with legend.
|
|
*/
|
|
self.start_loading = function () {
|
|
var self = this;
|
|
|
|
$(self.html_element).addClass('has-spinner');
|
|
self.$spinner.show();
|
|
|
|
// Hide the legend.
|
|
$(self.legend_element).empty().addClass('disabled');
|
|
|
|
/*
|
|
TODO(lsmola) a loader for in-line tables spark-lines has to be
|
|
prepared, the parameters of loader could be sent in settings.
|
|
*/
|
|
|
|
};
|
|
|
|
/**
|
|
* Hides the loader and backdrop so the chart will become visible.
|
|
* Shows also the block with legend.
|
|
*/
|
|
self.finish_loading = function () {
|
|
var self = this;
|
|
// Showing the legend.
|
|
$(self.legend_element).removeClass('disabled');
|
|
$(self.html_element).removeClass('has-spinner');
|
|
self.$spinner.hide();
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Function for initializing of the charts.
|
|
* If settings['auto_resize'] is true, the chart will be refreshed when
|
|
* the size of the window is changed. This option made only sense when
|
|
* the size of the chart and its wrapper is not static.
|
|
* @param selector jQuery selector of charts we want to initialize.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
init: function(selector, settings) {
|
|
var self = this;
|
|
$(selector).each(function() {
|
|
self.refresh(this, settings);
|
|
});
|
|
|
|
if (settings !== undefined && settings.auto_resize) {
|
|
/*
|
|
I want to refresh chart on resize of the window, but only
|
|
at the end of the resize. Nice code from mr. Google.
|
|
*/
|
|
var rtime = new Date(1, 1, 2000, 12, 0, 0);
|
|
var timeout = false;
|
|
var delta = 400;
|
|
$(window).resize(function() {
|
|
rtime = new Date();
|
|
if (timeout === false) {
|
|
timeout = true;
|
|
setTimeout(resizeend, delta);
|
|
}
|
|
});
|
|
|
|
var resizeend = function() {
|
|
if (new Date() - rtime < delta) {
|
|
setTimeout(resizeend, delta);
|
|
} else {
|
|
timeout = false;
|
|
$(selector).each(function() {
|
|
self.refresh(this, settings);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
self.bind_commands(selector, settings);
|
|
},
|
|
|
|
/**
|
|
* Function for creating chart objects, saving them for later reuse
|
|
* and calling their refresh method.
|
|
* @param html_element HTML element where the chart will be rendered.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
refresh: function(html_element, settings){
|
|
var chart = new this.LineChart(this, html_element, settings);
|
|
/*
|
|
FIXME save chart objects somewhere so I can use them again when
|
|
e.g. I am switching tabs, or if I want to update them
|
|
via web sockets
|
|
this.charts.add_or_update(chart)
|
|
*/
|
|
chart.refresh();
|
|
},
|
|
|
|
/**
|
|
* Function for binding controlling commands to the chart. Like changing
|
|
* timespan or various parameters we want to show in the chart. The
|
|
* charts will be refreshed immediately after the form element connected
|
|
* to them is changed.
|
|
* @param selector jQuery selector of charts we are initializing.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
bind_commands: function (selector, settings){
|
|
// connecting controls of the charts
|
|
var select_box_selector = 'select[data-line-chart-command="select_box_change"]';
|
|
var datepicker_selector = 'input[data-line-chart-command="date_picker_change"]';
|
|
var self = this;
|
|
|
|
/**
|
|
* Connecting forms to charts it controls. Each chart contains
|
|
* jQuery selector data-form-selector, which defines by which
|
|
* html Forms is a particular chart controlled. This information
|
|
* has to be projected to forms. So when form input is changed,
|
|
* all connected charts are refreshed.
|
|
*/
|
|
var connect_forms_to_charts = function(){
|
|
$(selector).each(function() {
|
|
var chart = $(this);
|
|
$(chart.data('form-selector')).each(function(){
|
|
var form = $(this);
|
|
// each form is building a jquery selector for all charts it affects
|
|
var chart_identifier = 'div[data-form-selector="' + chart.data('form-selector') + '"]';
|
|
if (!form.data('charts_selector')){
|
|
form.data('charts_selector', chart_identifier);
|
|
} else {
|
|
form.data('charts_selector', form.data('charts_selector') + ', ' + chart_identifier);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* A helper function for delegating form events to charts, causing their
|
|
* refreshing.
|
|
* @param selector jQuery selector of charts we are initializing.
|
|
* @param event_name Event name we want to delegate.
|
|
* @param settings An object containing settings of the chart.
|
|
*/
|
|
var delegate_event_and_refresh_charts = function(selector, event_name, settings) {
|
|
$('form').delegate(selector, event_name, function() {
|
|
/*
|
|
Registering 'any event' on form element by delegating. This way it
|
|
can be easily overridden / enhanced when some special functionality
|
|
needs to be added. Like input element showing/hiding another element
|
|
on some condition will be defined directly on element and can block
|
|
this default behavior.
|
|
*/
|
|
var invoker = $(this);
|
|
var form = invoker.parents('form').first();
|
|
|
|
$(form.data('charts_selector')).each(function(){
|
|
// refresh the chart connected to changed form
|
|
self.refresh(this, settings);
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* A helper function for catching change event of form selectboxes
|
|
* connected to charts.
|
|
*/
|
|
var bind_select_box_change = function(settings) {
|
|
delegate_event_and_refresh_charts(select_box_selector, 'change', settings);
|
|
};
|
|
|
|
/**
|
|
* A helper function for catching changeDate event of form datepickers
|
|
* connected to charts.
|
|
*/
|
|
var bind_datepicker_change = function(settings) {
|
|
horizon.datepickers.add(datepicker_selector);
|
|
delegate_event_and_refresh_charts(datepicker_selector, 'changeDate', settings);
|
|
};
|
|
|
|
connect_forms_to_charts();
|
|
bind_select_box_change(settings);
|
|
bind_datepicker_change(settings);
|
|
}
|
|
};
|
|
|
|
/* Init the graphs */
|
|
horizon.addInitFunction(function () {
|
|
horizon.d3_line_chart.init('div[data-chart-type="line_chart"]', {});
|
|
});
|