Adding bar chart
Adding charts due to the latest overview wireframes - Adding reusable bar-chart library - Adding support of bar-chart to linechart, so there can be combination line-bart chart. Implements blueprint horizon-reusable-charts Change-Id: I1c0eb891e396ad2c2a309dadee9bf6896db9430e
This commit is contained in:
parent
310c37e019
commit
a19bc02dc7
696
horizon/static/horizon/js/horizon.d3barchart.js
Normal file
696
horizon/static/horizon/js/horizon.d3barchart.js
Normal file
@ -0,0 +1,696 @@
|
||||
/*
|
||||
Used for animating and displaying bar information using
|
||||
D3js rect.
|
||||
|
||||
Usage:
|
||||
In order to have single bars that work with this, you need to have a
|
||||
DOM structure like this in your Django template:
|
||||
|
||||
Example:
|
||||
<div style="width: 100px; min-width: 100px; height: 20px; min-height: 20px">
|
||||
<div class="chart"
|
||||
data-chart-type="bar_chart"
|
||||
data-tooltip-used='Used'
|
||||
data-tooltip-free='Free'
|
||||
data-tooltip-average='Average'
|
||||
data-settings='{"orientation": "horizontal", "color_scale_range": ["#000060", "#99FFFF"]}'
|
||||
data-used="20"
|
||||
data-average="30">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The available data- attributes are:
|
||||
data-tooltip-free, data-tooltip-used, data-tooltip-average OPTIONAL
|
||||
Html content of tooltips that will be displayed over this areas.
|
||||
|
||||
|
||||
data-used="integer" REQUIRED
|
||||
1. Integer
|
||||
Integer representing the percent used.
|
||||
2. Array
|
||||
Array of following structure:
|
||||
[{"tooltip_used": "Popup html 1", "used_instances": "5"},
|
||||
{"tooltip_used": "Popup html 2", "used_instances": "15"},....]
|
||||
|
||||
used_instances: Integer representing the percent used.
|
||||
tooltip_used: Html that will be displayed in tooltip window over
|
||||
this area.
|
||||
|
||||
data-settings="JSON"
|
||||
Json with variety of settings described below.
|
||||
|
||||
used-label-placement='string' OPTIONAL
|
||||
String determinign where the floating label stating number of percent
|
||||
will be placed. So far only left is supported.
|
||||
|
||||
width="integer" OPTIONAL
|
||||
Integer in pixels. Determines the total width of the bar. Handy when
|
||||
we use a used_label, so the bar is not a 100% of the container.
|
||||
|
||||
average="integer" OPTIONAL
|
||||
Integer representing the average usage in percent of given
|
||||
single-bar.
|
||||
|
||||
auto-scale-selector OPTIONAL
|
||||
Jquery selector of bar elements that have Integer
|
||||
used attribute. It then takes maximum of these
|
||||
values as 100% of the linear scale of the colors.
|
||||
So the array representing linear scale interval is set
|
||||
automatically.This then maps to color-scale-range.
|
||||
(arrays must have the same structure)
|
||||
|
||||
color-scale-range OPTIONAL
|
||||
Array representing linear scale interval that is set manually.
|
||||
E.g "[0,10]". This then maps to color-scale-range.
|
||||
(arrays must have the same structure)
|
||||
|
||||
color-scale-range OPTIONAL
|
||||
Array representing linear scale of colors.
|
||||
E.g '["#000060", "#99FFFF"]'
|
||||
|
||||
orientation OPTIONAL
|
||||
String representing orientation of the bar.Can be "horizontal"
|
||||
or "vertical". Default is horizontal.
|
||||
|
||||
*/
|
||||
|
||||
horizon.d3_bar_chart = {
|
||||
/**
|
||||
* A class representing the bar 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.
|
||||
*/
|
||||
BarChart: function(chart_module, html_element, settings, data){
|
||||
var self = this;
|
||||
self.chart_module = chart_module;
|
||||
self.html_element = html_element;
|
||||
self.jquery_element = $(self.html_element);
|
||||
|
||||
/************************************************************************/
|
||||
/*********************** Initialization methods *************************/
|
||||
/************************************************************************/
|
||||
/**
|
||||
* Initialize the object.
|
||||
*/
|
||||
self.init = function(settings, data) {
|
||||
var self = this;
|
||||
self.data = {};
|
||||
|
||||
self.data.max_value = self.jquery_element.data('max-value');
|
||||
if (!self.max_value){
|
||||
// Default mas value is 100, representing 100 percent
|
||||
self.max_value = 100;
|
||||
}
|
||||
|
||||
// Chart data
|
||||
self.data.used = self.jquery_element.data('used');
|
||||
self.data.average = self.jquery_element.data('average');
|
||||
|
||||
// Tooltips data
|
||||
self.data.tooltip_average = self.jquery_element.data('tooltip-average');
|
||||
self.data.tooltip_free = self.jquery_element.data('tooltip-free');
|
||||
self.data.tooltip_used = self.jquery_element.data('tooltip-used');
|
||||
|
||||
if (data !== undefined){
|
||||
if (data.used !== undefined){
|
||||
self.data.used = data.used;
|
||||
}
|
||||
if (data.average !== undefined){
|
||||
self.data.average = data.average;
|
||||
}
|
||||
if (data.tooltip_average !== undefined){
|
||||
self.data.tooltip_average = data.tooltip_average;
|
||||
}
|
||||
if (data.tooltip_free !== undefined){
|
||||
self.data.tooltip_free = data.tooltip_free;
|
||||
}
|
||||
if (data.tooltip_used !== undefined){
|
||||
self.data.tooltip_used = data.tooltip_used;
|
||||
}
|
||||
}
|
||||
|
||||
// Percentage count
|
||||
if ($.isArray(self.data.used)){
|
||||
self.data.percentage_average = 0; // No average for Multi-bar chart
|
||||
self.data.percentage_used = Array();
|
||||
self.data.tooltip_used_contents = Array();
|
||||
for (var i = 0; i < self.data.used.length; ++i) {
|
||||
if (!isNaN(self.max_value) && !isNaN(self.data.used[i].used_instances)) {
|
||||
var used = Math.round((self.data.used[i].used_instances / self.max_value) * 100);
|
||||
|
||||
self.data.percentage_used.push(used);
|
||||
// for multi-bar chart, tooltip is in the data
|
||||
self.data.tooltip_used_contents.push(self.data.used[i].tooltip_used);
|
||||
} else { // If NaN self.data.percentage_used is 0
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!isNaN(self.max_value) && !isNaN(self.data.used)) {
|
||||
self.data.percentage_used = Math.round((self.data.used / self.max_value) * 100);
|
||||
} else { // If NaN self.data.percentage_used is 0
|
||||
self.data.percentage_used = 0;
|
||||
}
|
||||
|
||||
if (!isNaN(self.max_value) && !isNaN(self.data.average)) {
|
||||
self.data.percentage_average = ((self.data.average / self.max_value) * 100);
|
||||
} else {
|
||||
self.data.percentage_average = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Load initial settings.
|
||||
self.init_settings(settings);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.data.settings = {};
|
||||
|
||||
// Placement of the used label
|
||||
self.data.settings.used_label_placement = undefined;
|
||||
// Orientation of the Bar chart
|
||||
self.data.settings.orientation = 'horizontal';
|
||||
|
||||
// Color scales
|
||||
self.data.settings.color_scale_domain = [0,100];
|
||||
self.data.settings.color_scale_range = ['#000000', '#0000FF'];
|
||||
|
||||
// Width and height of bar
|
||||
self.data.settings.width = self.jquery_element.data('width');
|
||||
self.data.settings.height = self.jquery_element.data('height');
|
||||
|
||||
/* 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 = ['orientation', 'used_label_placement',
|
||||
'color_scale_domain', 'color_scale_range',
|
||||
'width', 'height'];
|
||||
|
||||
$.each(allowed_settings, function(index, setting_name) {
|
||||
if (settings[setting_name] !== undefined){
|
||||
self.data.settings[setting_name] = settings[setting_name];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
/****************************** Initialization **************************/
|
||||
/************************************************************************/
|
||||
// Init the object
|
||||
self.init(settings, data);
|
||||
|
||||
/************************************************************************/
|
||||
/****************************** Methods *********************************/
|
||||
/************************************************************************/
|
||||
/**
|
||||
* Obtains the actual chart data and renders the chart again.
|
||||
*/
|
||||
self.refresh = function(){
|
||||
var self = this;
|
||||
self.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the chart into html element given in initializer.
|
||||
*/
|
||||
self.render = function() {
|
||||
var self = this;
|
||||
|
||||
// Initialize wrapper
|
||||
var wrapper = new self.chart_module.Wrapper(self.chart_module, self.html_element, self.data);
|
||||
|
||||
// Initialize Tool-tips
|
||||
var tooltip_average = (new self.chart_module.TooltipComponent(wrapper)).render(self.data.tooltip_average);
|
||||
var tooltip_free = (new self.chart_module.TooltipComponent(wrapper)).render(self.data.tooltip_free);
|
||||
var tooltip_used = (new self.chart_module.TooltipComponent(wrapper)).render(self.data.tooltip_used);
|
||||
|
||||
// Append Unused resources Bar
|
||||
(new self.chart_module.UnusedComponent(wrapper)).render(tooltip_free);
|
||||
|
||||
if (wrapper.used_multi()){
|
||||
// If UsedComponent is shown as multiple values in one chart
|
||||
for (var i = 0; i < wrapper.percentage_used.length; ++i) {
|
||||
// FIXME write proper iterator
|
||||
wrapper.used_multi_iterator = i;
|
||||
|
||||
/* Use general tool-tip, content of tool-tip will be changed by inner
|
||||
used components on their hover. HTML content is taken from JSON sent
|
||||
from the server. */
|
||||
tooltip_used = (new self.chart_module.TooltipComponent(wrapper)).render('');
|
||||
|
||||
// Append used so it will be shown as multiple values in one chart
|
||||
(new self.chart_module.UsedComponent(wrapper)).render(tooltip_used);
|
||||
|
||||
// Compute total value as a start point for next Used bar
|
||||
wrapper.total_used_perc += wrapper.percentage_used_value();
|
||||
wrapper.total_used_value_in_pixels = (wrapper.w / 100) * wrapper.total_used_perc;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Used is show as one value it the chart
|
||||
(new self.chart_module.UsedComponent(wrapper)).render(tooltip_used);
|
||||
// Append average value to Bar
|
||||
(new self.chart_module.AverageComponent(wrapper)).render(tooltip_average);
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Chart wrapper class renders the main svg element and encapsulate
|
||||
* the chart data.
|
||||
* @param chart_module Chart module name
|
||||
* @param html_element HTML element where the chart will be rendered
|
||||
* @param chart_module Data + settings of the chart
|
||||
*/
|
||||
Wrapper: function(chart_module, html_element, data){
|
||||
var self = this;
|
||||
self.html_element = html_element;
|
||||
self.jquery_element = $(html_element);
|
||||
|
||||
// Bar HTML element
|
||||
self.bar_html = d3.select(html_element);
|
||||
// Bar layout for bar chart
|
||||
self.bar = self.bar_html.append('svg:svg')
|
||||
.attr('class', 'chart')
|
||||
.style('background-color', 'white');
|
||||
|
||||
// Get correct size of chart and the wrapper.
|
||||
chart_module.get_size(self.html_element);
|
||||
|
||||
self.data = data;
|
||||
// Floating label of used bar placement
|
||||
self.used_label_placement = data.settings.used_label_placement;
|
||||
|
||||
// Width and height of the chart itself
|
||||
if (data.settings.width !== undefined){
|
||||
self.w = parseFloat(data.settings.width);
|
||||
} else {
|
||||
self.w = parseFloat(self.jquery_element.width());
|
||||
}
|
||||
if (data.settings.height !== undefined) {
|
||||
self.h = parseFloat(data.settings.height);
|
||||
} else {
|
||||
self.h = parseFloat(self.jquery_element.height());
|
||||
}
|
||||
|
||||
/* Start coordinations of the chart and size of the chart wrapper.
|
||||
Chart can have other elements next to it, so it doesn't have to
|
||||
start at 0.
|
||||
*/
|
||||
self.chart_start_x = 0;
|
||||
if (self.data.settings.orientation=='vertical'){
|
||||
if (self.used_label_placement == 'left'){
|
||||
self.chart_start_x = 44;
|
||||
}
|
||||
self.chart_wrapper_w = self.w + self.chart_start_x;
|
||||
} else {
|
||||
self.chart_wrapper_w = self.w;
|
||||
}
|
||||
self.chart_wrapper_h = self.h;
|
||||
|
||||
// Basic settigns of the chart
|
||||
self.lvl_curve = 3;
|
||||
self.bkgrnd = '#F2F2F2';
|
||||
self.frgrnd = 'grey';
|
||||
self.color_scale_max = 25;
|
||||
|
||||
// Percentage used
|
||||
self.percentage_used = data.percentage_used;
|
||||
self.total_used_perc = 0; // incremented in render method
|
||||
self.total_used_value_in_pixels = 0; // incremented in render method
|
||||
self.used_value_in_pixels = 0; // set in UsedComponent
|
||||
self.average_value_in_pixels = 0; // set in AverageComponent
|
||||
|
||||
|
||||
// Percentage average
|
||||
self.percentage_average = data.percentage_average;
|
||||
self.tooltip_used_contents = data.tooltip_used_contents;
|
||||
|
||||
// Set scales for multi bar chart
|
||||
self.usage_color = d3.scale.linear()
|
||||
.domain(data.settings.color_scale_domain)
|
||||
.range(data.settings.color_scale_range);
|
||||
|
||||
// Border of the chart
|
||||
self.border_width = 1;
|
||||
|
||||
// Return true if it renders multiple used percentage in one chart
|
||||
self.used_multi = function (){
|
||||
return ($.isArray(self.percentage_used));
|
||||
};
|
||||
|
||||
// Deals with percentage if there should be multiple in one chart
|
||||
self.used_multi_iterator = 0;
|
||||
self.percentage_used_value = function(){
|
||||
if (self.used_multi()){
|
||||
return self.percentage_used[self.used_multi_iterator];
|
||||
} else {
|
||||
return self.percentage_used;
|
||||
}
|
||||
};
|
||||
|
||||
// Deals with html tooltips if there should be multiple in one chart
|
||||
self.tooltip_used_value = function (){
|
||||
if (self.used_multi()){
|
||||
return self.tooltip_used_contents[self.used_multi_iterator];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// Return true if it chart is oriented horizontally
|
||||
self.horizontal_orientation = function (){
|
||||
return (self.data.settings.orientation == 'horizontal');
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Component rendering part of chart showing 'used', optional labels
|
||||
* and optional tool-tip element.
|
||||
* the chart data.
|
||||
* @param wrapper Wrapper object
|
||||
*/
|
||||
UsedComponent: function(wrapper){
|
||||
var self = this;
|
||||
self.wrapper = wrapper;
|
||||
|
||||
// FIXME(lsmola) would be good to abstract all attributes and resolve orientation inside
|
||||
if (self.wrapper.horizontal_orientation()){
|
||||
// Horizontal Bars
|
||||
self.wrapper.used_value_in_pixels = (self.wrapper.w / 100) * self.wrapper.percentage_used_value();
|
||||
|
||||
self.y = 0;
|
||||
self.x = self.wrapper.total_used_value_in_pixels;
|
||||
self.width = 0;
|
||||
self.height = self.wrapper.h;
|
||||
self.trasition_attr = 'width';
|
||||
self.trasition_value = self.wrapper.used_value_in_pixels;
|
||||
} else {
|
||||
// Vertical Bars
|
||||
self.wrapper.used_value_in_pixels = (self.wrapper.h / 100) * self.wrapper.percentage_used_value();
|
||||
|
||||
self.y = self.wrapper.h;
|
||||
self.x = self.wrapper.chart_start_x;
|
||||
self.width = self.wrapper.w - self.wrapper.border_width;
|
||||
self.height = self.wrapper.used_value_in_pixels;
|
||||
self.trasition_attr = 'y';
|
||||
self.trasition_value = self.wrapper.h - self.wrapper.used_value_in_pixels;
|
||||
}
|
||||
|
||||
self.render = function(tooltip){
|
||||
self.wrapper.bar.append('rect')
|
||||
.attr('class', 'used_component')
|
||||
.attr('y', self.y)
|
||||
.attr('x', self.x)
|
||||
.attr('width', self.width)
|
||||
.attr('height', self.height)
|
||||
.style('fill', self.wrapper.usage_color(self.wrapper.percentage_used_value()))
|
||||
.style('stroke', '#bebebe')
|
||||
.style('stroke-width', 0)
|
||||
.attr('d', self.wrapper.percentage_used_value())
|
||||
.attr('tooltip-used', self.wrapper.tooltip_used_value())
|
||||
.on('mouseover', function(d){
|
||||
if ($(this).attr('tooltip-used')){
|
||||
tooltip.html($(this).attr('tooltip-used'));
|
||||
}
|
||||
tooltip.style('visibility', 'visible');})
|
||||
.on('mousemove', function(d){tooltip.style('top',
|
||||
(event.pageY - 10) + 'px').style('left',(event.pageX + 10) + 'px');})
|
||||
.on('mouseout', function(d){tooltip.style('visibility', 'hidden');})
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr(self.trasition_attr, self.trasition_value);
|
||||
|
||||
if (self.wrapper.used_label_placement == 'left') {
|
||||
// Now it works only for vertical bar chart placed left form the chart
|
||||
var label_placement_y = self.wrapper.h - self.wrapper.used_value_in_pixels;
|
||||
|
||||
// Make sure the placement will be visible with border values
|
||||
if (label_placement_y <= 6){
|
||||
label_placement_y = 6;
|
||||
} else if (label_placement_y >= (self.wrapper.h - 6)){
|
||||
label_placement_y = self.wrapper.h - 6;
|
||||
}
|
||||
|
||||
// Append label text
|
||||
self.wrapper.bar.append('text')
|
||||
.attr('class', 'used_component_label')
|
||||
.text(self.wrapper.percentage_used_value() + '%')
|
||||
.attr('y', label_placement_y)
|
||||
.attr('x', 0)
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('font-size', 12)
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('x', function() {
|
||||
if (self.wrapper.percentage_used_value() > 99){
|
||||
// If there are two digits, label have to be farther to the bar chart
|
||||
return 0;
|
||||
}
|
||||
else if (self.wrapper.percentage_used_value() > 9){
|
||||
// If there are two digits, label have to be farther to the bar chart
|
||||
return 4;
|
||||
}
|
||||
else {
|
||||
// If there is only one digit, label can be closer to the bar chart
|
||||
return 8;
|
||||
}
|
||||
});
|
||||
|
||||
// Append little triangle pointing to text
|
||||
var poly = [{'x':self.wrapper.chart_start_x - 8, 'y':label_placement_y},
|
||||
{'x':self.wrapper.chart_start_x - 3,'y':label_placement_y + 2},
|
||||
{'x':self.wrapper.chart_start_x - 3,'y':label_placement_y - 2},
|
||||
];
|
||||
|
||||
self.wrapper.bar.selectAll('polygon')
|
||||
.data([poly])
|
||||
.enter().append('polygon')
|
||||
.attr('points',function(d) {
|
||||
return d.map(function(d) { return [d.x,d.y].join(','); }).join(' ');})
|
||||
.attr('stroke','black')
|
||||
.attr('stroke-width', 2);
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Component rendering part of chart showing 'average' and optional
|
||||
* tool-tip element.
|
||||
* the chart data.
|
||||
* @param wrapper Wrapper object
|
||||
*/
|
||||
AverageComponent: function(wrapper){
|
||||
var self = this;
|
||||
self.wrapper = wrapper;
|
||||
|
||||
// FIXME woud be good to abstract all atributes and resolve orientation inside
|
||||
if (wrapper.horizontal_orientation()){
|
||||
// Horizontal Bars
|
||||
self.wrapper.average_value_in_pixels = (self.wrapper.w / 100) * self.wrapper.percentage_average;
|
||||
|
||||
self.y = 1;
|
||||
self.x = self.wrapper.average_value_in_pixels;
|
||||
self.width = 0;
|
||||
self.height = self.wrapper.h;
|
||||
} else { // Vertical Bars
|
||||
self.wrapper.average_value_in_pixels = (self.wrapper.h / 100) * (100 - self.wrapper.percentage_average);
|
||||
|
||||
self.y = self.wrapper.average_value_in_pixels;
|
||||
self.x = self.wrapper.chart_start_x;
|
||||
self.width = self.wrapper.w - self.wrapper.border_width;
|
||||
self.height = 0;
|
||||
}
|
||||
|
||||
self.render = function(tooltip){
|
||||
if (self.wrapper.percentage_average > 0) {
|
||||
// Only show average when it is bigger than 0
|
||||
// A dashed line, so it's pretty
|
||||
self.wrapper.bar.append('line')
|
||||
.attr('class', 'average_component')
|
||||
.attr('y1', self.y)
|
||||
.attr('x1', self.x)
|
||||
.attr('class', 'average')
|
||||
.attr('y2', self.y + self.height)
|
||||
.attr('x2', self.x + self.width)
|
||||
.style('stroke', 'black')
|
||||
.style('stroke-width', 3)
|
||||
.style('stroke-dasharray', ('6, 2'))
|
||||
.on('mouseover', function(){tooltip.style('visibility', 'visible');})
|
||||
.on('mousemove', function(){tooltip.style('top',
|
||||
(event.pageY-10)+'px').style('left',(event.pageX+10)+'px');})
|
||||
.on('mouseout', function(){tooltip.style('visibility', 'hidden');});
|
||||
|
||||
// A normal line, so it shows popup even in spaces, it's also bigger so
|
||||
// it's easier to show popup
|
||||
self.wrapper.bar.append('line')
|
||||
.attr('class', 'average_component')
|
||||
.attr('y1', self.y)
|
||||
.attr('x1', self.x)
|
||||
.attr('class', 'average')
|
||||
.attr('y2', self.y + self.height)
|
||||
.attr('x2', self.x + self.width)
|
||||
.style('stroke', 'transparent')
|
||||
.style('stroke-width', 5)
|
||||
.on('mouseover', function(){tooltip.style('visibility', 'visible');})
|
||||
.on('mousemove', function(){tooltip.style('top',
|
||||
(event.pageY-10)+'px').style('left',(event.pageX+10)+'px');})
|
||||
.on('mouseout', function(){tooltip.style('visibility', 'hidden');});
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Component rendering part of chart showing 'unused' and optional
|
||||
* tool-tip element.
|
||||
* the chart data.
|
||||
* @param wrapper Wrapper object
|
||||
*/
|
||||
UnusedComponent: function(wrapper){
|
||||
var self = this;
|
||||
self.wrapper = wrapper;
|
||||
|
||||
self.render = function(tooltip_free){
|
||||
self.wrapper.bar.append('rect')
|
||||
.attr('class', 'unused_component')
|
||||
.attr('y', 0)
|
||||
.attr('x', self.wrapper.chart_start_x)
|
||||
.attr('width', self.wrapper.w)
|
||||
.attr('height', self.wrapper.h)
|
||||
.attr('rx', self.wrapper.lvl_curve)
|
||||
.attr('ry', self.wrapper.lvl_curve)
|
||||
.style('fill', self.wrapper.bkgrnd)
|
||||
|
||||
.on('mouseover', function(d){tooltip_free.style('visibility', 'visible');})
|
||||
.on('mousemove', function(d){tooltip_free.style('top',
|
||||
(event.pageY-10)+'px').style('left',(event.pageX+10)+'px');})
|
||||
.on('mouseout', function(d){tooltip_free.style('visibility', 'hidden');});
|
||||
|
||||
self.wrapper.bar.append('rect')
|
||||
.attr('class', 'unused_component_border')
|
||||
.attr('x', self.wrapper.chart_start_x)
|
||||
.attr('y', 0)
|
||||
.attr('height', self.wrapper.h)
|
||||
.attr('width', self.wrapper.w - self.wrapper.border_width) // a space for right border line
|
||||
.style('stroke', '#bebebe')
|
||||
.style('fill', 'none')
|
||||
.style('stroke-width', 1);
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Component rendering tool-tip HTML code.
|
||||
* the chart data.
|
||||
* @param wrapper Wrapper object
|
||||
* @return HTML code of tool-tip
|
||||
*/
|
||||
TooltipComponent: function(wrapper){
|
||||
var self = this;
|
||||
self.wrapper = wrapper;
|
||||
self.tooltip_html = self.wrapper.bar_html.append('div');
|
||||
|
||||
self.render = function(html_content){
|
||||
var display = 'none';
|
||||
if (html_content){
|
||||
// Display only when there is some HTML content
|
||||
display = 'block';
|
||||
}
|
||||
|
||||
return self.tooltip_html
|
||||
.attr('class', 'tooltip_detail')
|
||||
.style('position', 'absolute')
|
||||
.style('z-index', '10')
|
||||
.style('visibility', 'hidden')
|
||||
.style('display', display)
|
||||
.html(html_content);
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Function for initializing of the charts.
|
||||
* @param selector JQuery selector of charts we want to initialize.
|
||||
* @param settings An object containing settings of the chart.
|
||||
* @param data An object containing data of the chart.
|
||||
*/
|
||||
init: function(selector, settings, data) {
|
||||
var self = this;
|
||||
self.bars = $(selector);
|
||||
|
||||
self.bars.each(function() {
|
||||
self.refresh(this, settings, data);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 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.
|
||||
* @param data An object containing data of the chart.
|
||||
*/
|
||||
refresh: function(html_element, settings, data){
|
||||
var chart = new this.BarChart(this, html_element, settings, data);
|
||||
// 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 computing size of the chart from the surrounding HTML.
|
||||
* @param html_element HTML element where the chart will be rendered.
|
||||
*/
|
||||
get_size: function(html_element){
|
||||
/* 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. */
|
||||
var jquery_element = $(html_element);
|
||||
jquery_element.css('height', '');
|
||||
jquery_element.css('width', '');
|
||||
var svg = jquery_element.find('svg');
|
||||
svg.hide();
|
||||
|
||||
// Width an height of the chart will be taken from chart wrapper,
|
||||
// that can be styled by css.
|
||||
var width = jquery_element.width();
|
||||
|
||||
// Set either the minimal height defined by CSS.
|
||||
var height = jquery_element.height();
|
||||
|
||||
/* Setting new sizes. It is important when resizing a window.*/
|
||||
jquery_element.css('height', height);
|
||||
jquery_element.css('width', width);
|
||||
svg.show();
|
||||
svg.css('height', height);
|
||||
svg.css('width', width);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
horizon.addInitFunction(function () {
|
||||
horizon.d3_bar_chart.init('div[data-chart-type="bar_chart"]', {}, {});
|
||||
});
|
@ -43,6 +43,51 @@ Url has to return JSON in format:
|
||||
"settings": {}
|
||||
}
|
||||
|
||||
Example of line-bar chart sparkline:
|
||||
|
||||
<div class="overview_chart">
|
||||
<div class="chart_container">
|
||||
<div class="chart"
|
||||
data-chart-type="line_chart"
|
||||
data-url="/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": 171, "x": "2013-08-21T11:22:25"}]},
|
||||
{"name": "instance-00000005",
|
||||
"data": [{"y": 171, "x": "2013-08-21T11:22:25"}, {"y": 171, "x": "2013-08-21T11:22:25"}]}
|
||||
],
|
||||
"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
|
||||
@ -166,6 +211,11 @@ horizon.d3_line_chart = {
|
||||
self.data = [];
|
||||
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.
|
||||
@ -187,8 +237,17 @@ horizon.d3_line_chart = {
|
||||
self.settings.auto_size = true;
|
||||
self.settings.axes_x = true;
|
||||
self.settings.axes_y = true;
|
||||
// Static y axes values
|
||||
self.settings.yMin = undefined;
|
||||
self.settings.yMax = 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';
|
||||
@ -217,7 +276,9 @@ horizon.d3_line_chart = {
|
||||
var self = this;
|
||||
|
||||
var allowed_settings = ['renderer', 'auto_size', 'axes_x', 'axes_y',
|
||||
'yMin', 'yMax'];
|
||||
'yMin', 'yMax', 'bar_chart_settings',
|
||||
'bar_chart_selector', 'composed_chart_selector',
|
||||
'higlight_last_point'];
|
||||
|
||||
jQuery.each(allowed_settings, function(index, setting_name) {
|
||||
if (settings[setting_name] !== undefined){
|
||||
@ -287,6 +348,7 @@ horizon.d3_line_chart = {
|
||||
$(self.legend_element).html('');
|
||||
|
||||
self.series = data.series;
|
||||
self.stats = data.stats;
|
||||
// The highest priority settings are sent with the data.
|
||||
self.apply_settings(data.settings);
|
||||
|
||||
@ -320,13 +382,16 @@ horizon.d3_line_chart = {
|
||||
*/
|
||||
self.render = function(){
|
||||
var self = this;
|
||||
var last_point = undefined, last_point_color = undefined;
|
||||
|
||||
$.map(self.series, function (serie) {
|
||||
serie.color = self.color(serie.name);
|
||||
serie.color = last_point_color = self.color(serie.name);
|
||||
$.map(serie.data, function (statistic) {
|
||||
// need to parse each date
|
||||
statistic.x = d3.time.format('%Y-%m-%dT%H:%M:%S').parse(statistic.x);
|
||||
statistic.x = statistic.x.getTime() / 1000;
|
||||
last_point = statistic;
|
||||
last_point.color = serie.color;
|
||||
});
|
||||
});
|
||||
|
||||
@ -404,6 +469,30 @@ horizon.d3_line_chart = {
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -41,6 +41,7 @@
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.heattop.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/lib/rickshaw.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3linechart.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3barchart.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.firewalls.js' type='text/javascript' charset='utf-8'></script>
|
||||
{% block custom_js_files %}{% endblock %}
|
||||
{% endcompress %}
|
||||
|
@ -1,7 +1,102 @@
|
||||
.chart{
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
min-height: inherit;
|
||||
min-width: inherit;
|
||||
|
||||
.tooltip_detail {
|
||||
position: absolute;
|
||||
|
||||
z-index: 2;
|
||||
border-radius: 3px;
|
||||
padding: 0.25em;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
margin-left: 1em;
|
||||
margin-top: -1em;
|
||||
white-space: nowrap;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.tooltip_detail:before {
|
||||
content: "\25c2";
|
||||
position: absolute;
|
||||
left: -0.5em;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
width: 0;
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@overview_chart_height: 81px;
|
||||
.overview_chart{
|
||||
width: 214px;
|
||||
height: @overview_chart_height;
|
||||
border: 1px ridge black;
|
||||
padding: 8px 8px 8px 8px !important;
|
||||
|
||||
.chart_container {
|
||||
width: 140px;
|
||||
min-width: 140px;
|
||||
.chart {
|
||||
svg {
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bar_chart_container {
|
||||
width: 74px;
|
||||
min-width: 74px;
|
||||
}
|
||||
.bar_chart_container, .chart_container {
|
||||
float: left;
|
||||
min-height: @overview_chart_height;
|
||||
height: @overview_chart_height;
|
||||
|
||||
.chart {
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
min-height: inherit;
|
||||
min-width: inherit;
|
||||
.modal-backdrop {
|
||||
min-height: inherit;
|
||||
min-width: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip_detail {
|
||||
position: absolute;
|
||||
|
||||
z-index: 2;
|
||||
border-radius: 3px;
|
||||
padding: 0.25em;
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||||
margin-left: 1em;
|
||||
margin-top: -1em;
|
||||
white-space: nowrap;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.tooltip_detail:before {
|
||||
content: "\25c2";
|
||||
position: absolute;
|
||||
left: -0.5em;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
width: 0;
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@min_height_fullscreen_chart: 300px;
|
||||
@min_width_fullscreen_chart: 400px;
|
||||
@min_width_fullscreen_chart_legend: 90px;
|
||||
|
||||
.chart_container {
|
||||
position: relative;
|
||||
min-height: @min_height_fullscreen_chart;
|
||||
@ -76,4 +171,4 @@
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user