Barcharts now inherit from a Bootstrap Theme

Barcharts were using a complex D3 mechanism to draw very very simple
markup that can be achieved using a simple Bootstrap Stacked Progress
Bar.  A templatetag was added to aid in markup reuse.

Also, a 'minifyspace' template helper was also added that enables us
to make templates that have excessive attributes to be more readable,
while not affecting the rendered HTML on the page.

By moving the logic to simple markup, the rendering of the barcharts
are now immediate on the page, instead of requiring Horizon Init
functions to run and draw.  This immediate render removes the first
animation on the Quota bars, but it retains animations driven by user
interaction with the forms.

It was hoped that horizon.d3barchart.js could be completely removed,
but it is currently being used by horizon.d3linechart.js.  This will
be addressed soon, but was outside of the scope of this patch.

Much of the markup on the Quota pages was cleaned up and made simpler
to use existing style as well as replacing improper usage of <strong>

Summary of Improvements:
 * Immediate Render time for Bar Charts
 * Bar Charts now Inherit from theme, and use progress bar styles
 * 'minifyspace' was added to increase readability to templates
 * bs_progress_bar templatetag was added to facilitate code reuse
 * Markup for Quota Bar pages refactored

Partially-Implements: blueprint horizon-theme-css-reorg
Partially-Implements: blueprint bootstrap-html-standards

Change-Id: I16a1e3955d3a4fded0f2ea6a87f5e7c2f630185d
This commit is contained in:
Diana Whitten 2015-11-13 13:42:45 -07:00
parent 98cb37d7ce
commit f5f371655e
14 changed files with 384 additions and 350 deletions

View File

@ -128,6 +128,7 @@ full use of the Bootstrap theme architecture.
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
* Tables_ * Tables_
* `Bar Charts`_
* Login_ * Login_
* Tabs_ * Tabs_
@ -171,16 +172,35 @@ The side navigation component has been refactored to use the native Stacked
Pills element from Bootstrap. See **Pills** section of your variables file Pills element from Bootstrap. See **Pills** section of your variables file
for specific variables to customize. for specific variables to customize.
Pie Charts Charts
---------- ------
Pie Charts, in Horizon, are SVG elements. SVG elements allow CSS Pie Charts
customizations for only a basic element's look and feel (i.e. colors, size). ~~~~~~~~~~
Pie Charts are SVG elements. SVG elements allow CSS customizations for
only a basic element's look and feel (i.e. colors, size).
Since there is no native element in Bootstrap specifically for pie charts, Since there is no native element in Bootstrap specifically for pie charts,
the look and feel of the charts are inheriting from other elements of the the look and feel of the charts are inheriting from other elements of the
theme. Please see ``_pie_charts.scss`` for specifics. theme. Please see ``_pie_charts.scss`` for specifics.
.. _Bar Charts:
Bar Charts
~~~~~~~~~~
Bar Charts can be either a Bootstrap Progress Bar or an SVG element. Either
implementation will use the Bootstrap Progress Bar styles.
The SVG implementation will not make use of the customized Progress Bar
height though, so it is recommended that Bootstrap Progress Bars are used
whenever possible.
Please see ``_bar_charts.scss`` for specifics on what can be customized for
SVGs. See the **Progress bars** section of your variables file for specific
variables to customize.
Tables Tables
------ ------

View File

@ -13,7 +13,7 @@
data-tooltip-used='Used' data-tooltip-used='Used'
data-tooltip-free='Free' data-tooltip-free='Free'
data-tooltip-average='Average' data-tooltip-average='Average'
data-settings='{"orientation": "horizontal", "color_scale_range": ["#000060", "#99FFFF"]}' data-settings='{"orientation": "horizontal"}'
data-used="20" data-used="20"
data-average="30"> data-average="30">
</div> </div>
@ -41,9 +41,9 @@
data-settings="JSON" data-settings="JSON"
Json with variety of settings described below. Json with variety of settings described below.
used-label-placement='string' OPTIONAL used-label-placement='string' OPTIONAL
String determining where the floating label stating number of percent String determining where the floating label stating number of percent
will be placed. So far only left is supported. will be placed. So far only left is supported.
width="integer" OPTIONAL width="integer" OPTIONAL
Integer in pixels. Determines the total width of the bar. Handy when Integer in pixels. Determines the total width of the bar. Handy when
@ -55,20 +55,7 @@
auto-scale-selector OPTIONAL auto-scale-selector OPTIONAL
Jquery selector of bar elements that have Integer Jquery selector of bar elements that have Integer
used attribute. It then takes maximum of these used attribute.
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 orientation OPTIONAL
String representing orientation of the bar.Can be "horizontal" String representing orientation of the bar.Can be "horizontal"
@ -148,8 +135,7 @@ horizon.d3_bar_chart = {
} }
} }
} } else {
else {
if (!isNaN(self.max_value) && !isNaN(self.data.used)) { if (!isNaN(self.max_value) && !isNaN(self.data.used)) {
self.data.percentage_used = Math.round((self.data.used / self.max_value) * 100); self.data.percentage_used = Math.round((self.data.used / self.max_value) * 100);
} else { // If NaN self.data.percentage_used is 0 } else { // If NaN self.data.percentage_used is 0
@ -180,15 +166,9 @@ horizon.d3_bar_chart = {
self.data.settings = {}; self.data.settings = {};
// Placement of the used label
self.data.settings.used_label_placement = undefined;
// Orientation of the Bar chart // Orientation of the Bar chart
self.data.settings.orientation = 'horizontal'; 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 // Width and height of bar
self.data.settings.width = self.jquery_element.data('width'); self.data.settings.width = self.jquery_element.data('width');
self.data.settings.height = self.jquery_element.data('height'); self.data.settings.height = self.jquery_element.data('height');
@ -216,7 +196,6 @@ horizon.d3_bar_chart = {
self.apply_settings = function(settings){ self.apply_settings = function(settings){
var self = this; var self = this;
var allowed_settings = ['orientation', 'used_label_placement', var allowed_settings = ['orientation', 'used_label_placement',
'color_scale_domain', 'color_scale_range',
'width', 'height']; 'width', 'height'];
$.each(allowed_settings, function(index, setting_name) { $.each(allowed_settings, function(index, setting_name) {
@ -254,13 +233,8 @@ horizon.d3_bar_chart = {
// Initialize wrapper // Initialize wrapper
var wrapper = new self.chart_module.Wrapper(self.chart_module, self.html_element, self.data); 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 // Append Unused resources Bar
(new self.chart_module.UnusedComponent(wrapper)).render(tooltip_free); (new self.chart_module.UnusedComponent(wrapper)).render(self.data.tooltip_free);
if (wrapper.used_multi()){ if (wrapper.used_multi()){
// If UsedComponent is shown as multiple values in one chart // If UsedComponent is shown as multiple values in one chart
@ -268,13 +242,8 @@ horizon.d3_bar_chart = {
// FIXME write proper iterator // FIXME write proper iterator
wrapper.used_multi_iterator = i; 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 // Append used so it will be shown as multiple values in one chart
(new self.chart_module.UsedComponent(wrapper)).render(tooltip_used); (new self.chart_module.UsedComponent(wrapper)).render(self.data.tooltip_used);
// Compute total value as a start point for next Used bar // Compute total value as a start point for next Used bar
wrapper.total_used_perc += wrapper.percentage_used_value(); wrapper.total_used_perc += wrapper.percentage_used_value();
@ -283,9 +252,9 @@ horizon.d3_bar_chart = {
} else { } else {
// Used is show as one value it the chart // Used is show as one value it the chart
(new self.chart_module.UsedComponent(wrapper)).render(tooltip_used); (new self.chart_module.UsedComponent(wrapper)).render(self.data.tooltip_used);
// Append average value to Bar // Append average value to Bar
(new self.chart_module.AverageComponent(wrapper)).render(tooltip_average); (new self.chart_module.AverageComponent(wrapper)).render(self.data.tooltip_average);
} }
}; };
}, },
@ -305,8 +274,7 @@ horizon.d3_bar_chart = {
self.bar_html = d3.select(html_element); self.bar_html = d3.select(html_element);
// Bar layout for bar chart // Bar layout for bar chart
self.bar = self.bar_html.append('svg:svg') self.bar = self.bar_html.append('svg:svg')
.attr('class', 'chart') .attr('class', 'legacy-bar-chart');
.style('background-color', 'white');
// Get correct size of chart and the wrapper. // Get correct size of chart and the wrapper.
chart_module.get_size(self.html_element); chart_module.get_size(self.html_element);
@ -342,11 +310,8 @@ horizon.d3_bar_chart = {
} }
self.chart_wrapper_h = self.h; self.chart_wrapper_h = self.h;
// Basic settigns of the chart // Basic settings of the chart
self.lvl_curve = 3; self.lvl_curve = 3;
self.bkgrnd = '#F2F2F2';
self.frgrnd = 'grey';
self.color_scale_max = 25;
// Percentage used // Percentage used
self.percentage_used = data.percentage_used; self.percentage_used = data.percentage_used;
@ -360,11 +325,6 @@ horizon.d3_bar_chart = {
self.percentage_average = data.percentage_average; self.percentage_average = data.percentage_average;
self.tooltip_used_contents = data.tooltip_used_contents; 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 // Border of the chart
self.border_width = 1; self.border_width = 1;
@ -431,37 +391,23 @@ horizon.d3_bar_chart = {
} }
self.render = function(tooltip){ self.render = function(tooltip){
self.wrapper.bar.append('rect') var elem = self.wrapper.bar.append('rect')
.attr('class', 'used_component') .attr('class', 'used_component legacy-bar-chart-section')
.attr('y', self.y) .attr('y', self.y)
.attr('x', self.x) .attr('x', self.x)
.attr('width', self.width) .attr('width', self.width)
.attr('height', self.height) .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('d', self.wrapper.percentage_used_value())
.attr('tooltip-used', self.wrapper.tooltip_used_value())
.on('mouseover', function(){
if ($(this).attr('tooltip-used')){
tooltip.html($(this).attr('tooltip-used'));
}
tooltip.style('visibility', 'visible');
})
.on('mousemove', function(){
var eventX = event.offsetX || event.layerX;
var eventY = event.offsetY || event.layerY;
tooltip
.style('top', (eventY - 10) + 'px')
.style('left',(eventX + 10) + 'px');
})
.on('mouseout', function(){
tooltip.style('visibility', 'hidden');
})
.transition() .transition()
.duration(500) .duration(500)
.attr(self.trasition_attr, self.trasition_value); .attr(self.trasition_attr, self.trasition_value);
$(elem).tooltip({
placement: self.wrapper.data.settings.orientation === 'horizontal' ? 'bottom' : 'left',
container: 'body',
title: $.isArray(self.wrapper.data.percentage_used) ? self.wrapper.tooltip_used_value() : tooltip
});
if (self.wrapper.used_label_placement === 'left') { if (self.wrapper.used_label_placement === 'left') {
// Now it works only for vertical bar chart placed left form the chart // 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; var label_placement_y = self.wrapper.h - self.wrapper.used_value_in_pixels;
@ -480,7 +426,6 @@ horizon.d3_bar_chart = {
.attr('y', label_placement_y) .attr('y', label_placement_y)
.attr('x', 0) .attr('x', 0)
.attr('dominant-baseline', 'middle') .attr('dominant-baseline', 'middle')
.attr('font-size', 12)
.transition() .transition()
.duration(500) .duration(500)
.attr('x', function() { .attr('x', function() {
@ -508,12 +453,12 @@ horizon.d3_bar_chart = {
.data([poly]) .data([poly])
.enter() .enter()
.append('polygon') .append('polygon')
.attr('class', 'used_component_label_arrow')
.attr('points',function(d) { .attr('points',function(d) {
return d.map(function(d) { return d.map(function(d) {
return [d.x,d.y].join(','); return [d.x,d.y].join(',');
}).join(' '); }).join(' ');
}) })
.attr('stroke','black')
.attr('stroke-width', 2); .attr('stroke-width', 2);
} }
}; };
@ -549,47 +494,20 @@ horizon.d3_bar_chart = {
self.render = function(tooltip){ self.render = function(tooltip){
if (self.wrapper.percentage_average > 0) { if (self.wrapper.percentage_average > 0) {
// Only show average when it is bigger than 0 // Only show average when it is bigger than 0
// A dashed line, so it's pretty // A dashed line
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(){
var eventX = event.offsetX || event.layerX;
var eventY = event.offsetY || event.layerY;
tooltip
.style('top',(eventY - 10) + 'px')
.style('left',(eventX + 10) + 'px');
})
.on('mouseout', function(){tooltip.style('visibility', 'hidden');});
// A normal line, so it shows popup even in spaces, it's also bigger so var elem = self.wrapper.bar.append('line')
// it's easier to show popup
self.wrapper.bar.append('line')
.attr('class', 'average_component') .attr('class', 'average_component')
.attr('y1', self.y) .attr('y1', self.y)
.attr('x1', self.x) .attr('x1', self.x)
.attr('class', 'average')
.attr('y2', self.y + self.height) .attr('y2', self.y + self.height)
.attr('x2', self.x + self.width) .attr('x2', self.x + self.width);
.style('stroke', 'transparent')
.style('stroke-width', 5) $(elem).tooltip({
.on('mouseover', function(){tooltip.style('visibility', 'visible');}) placement: self.wrapper.data.settings.orientation === 'horizontal' ? 'top' : 'right',
.on('mousemove', function(){ container: 'body',
var eventX = event.offsetX || event.layerX; title: tooltip
var eventY = event.offsetY || event.layerY; });
tooltip
.style('top',(eventY - 10) + 'px')
.style('left',(eventX + 10) + 'px');
})
.on('mouseout', function(){tooltip.style('visibility', 'hidden');});
} }
}; };
}, },
@ -603,65 +521,21 @@ horizon.d3_bar_chart = {
var self = this; var self = this;
self.wrapper = wrapper; self.wrapper = wrapper;
self.render = function(tooltip_free){ self.render = function(tooltip){
self.wrapper.bar.append('rect') var elem = self.wrapper.bar.append('rect')
.attr('class', 'unused_component') .attr('class', 'unused_component legacy-bar-chart-section')
.attr('y', 0) .attr('y', 0)
.attr('x', self.wrapper.chart_start_x) .attr('x', self.wrapper.chart_start_x)
.attr('width', self.wrapper.w) .attr('width', self.wrapper.w)
.attr('height', self.wrapper.h) .attr('height', self.wrapper.h)
.attr('rx', self.wrapper.lvl_curve) .attr('rx', self.wrapper.lvl_curve)
.attr('ry', self.wrapper.lvl_curve) .attr('ry', self.wrapper.lvl_curve);
.style('fill', self.wrapper.bkgrnd)
.on('mouseover', function(){ $(elem).tooltip({
tooltip_free.style('visibility', 'visible'); placement: self.wrapper.data.settings.orientation === 'horizontal' ? 'bottom' : 'left',
}) container: 'body',
.on('mousemove', function(){ title: tooltip
var eventX = event.offsetX || event.layerX; });
var eventY = event.offsetY || event.layerY;
tooltip_free
.style('top',(eventY - 10) + 'px')
.style('left',(eventX + 10) + 'px');
})
.on('mouseout', function(){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);
}; };
}, },
/** /**

View File

@ -66,12 +66,6 @@ horizon.Quota = {
return ('#' + $(elm).attr('data-progress-indicator-for')); return ('#' + $(elm).attr('data-progress-indicator-for'));
})); }));
// Draw the initial progress bars
this._initialCreation(this.user_value_progress_bars);
this._initialCreation(this.auto_value_progress_bars);
this._initialCreation(this.flavor_progress_bars);
this._initialAnimations();
this._attachInputHandlers(); this._attachInputHandlers();
}, },
@ -358,99 +352,57 @@ horizon.Quota = {
// Does the math to calculate what percentage to update a progress bar by. // Does the math to calculate what percentage to update a progress bar by.
updateUsageFor: function(progress_element, increment_by) { updateUsageFor: function(progress_element, increment_by) {
progress_element = $(progress_element); var $progress_element = $(progress_element);
//var update_indicator = progress_element.find('.progress_bar_selected'); //var update_indicator = progress_element.find('.progress_bar_selected');
var quota_limit = parseInt(progress_element.attr('data-quota-limit'), 10); var quota_limit = parseInt($progress_element.attr('data-quota-limit'), 10);
var percentage_to_update = ((increment_by / quota_limit) * 100); var percentage_to_update = ((increment_by / quota_limit) * 100);
this.update($(progress_element).attr('id'), percentage_to_update); this.update($progress_element.attr('id'), percentage_to_update);
},
// Create a new d3 bar and populate it with the current amount used
drawUsed: function(element, used) {
var w = "100%";
var h = 20;
var lvl_curve = 4;
var bkgrnd = "#F2F2F2";
var frgrnd = "#006CCF";
var full = "#D0342B";
var addition = "#00D300";
var nearlyfull = "orange";
// Horizontal Bars
var bar = d3.select("#"+element).append("svg:svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", h)
.style("background-color", "white")
.append("g");
// background - unused resources
bar.append("rect")
.attr("y", 0)
.attr("width", w)
.attr("height", h)
.attr("rx", lvl_curve)
.attr("ry", lvl_curve)
.style("fill", bkgrnd)
.style("stroke", "#CCCCCC")
.style("stroke-width", 1);
// new resources
bar.append("rect")
.attr("y",0)
.attr("class", "newbar")
.attr("width", 0)
.attr("height", h)
.attr("rx", lvl_curve)
.attr("ry", lvl_curve)
.style("fill", function () { return addition; });
// used resources
bar.insert("rect")
.attr("class", "usedbar")
.attr("y", 0)
.attr("id", "test")
.attr("width", 0)
.attr("height", h)
.attr("rx", lvl_curve)
.attr("ry", lvl_curve)
.style("fill", function () { return frgrnd; })
.attr("d", used)
.transition()
.duration(500)
.attr("width", used + "%")
.style("fill", function () {
if (used >= 100) { return full; }
else if (used >= 80) { return nearlyfull; }
else { return frgrnd; }
});
}, },
// Update the progress Bar // Update the progress Bar
update: function(element, value) { update: function(element, value) {
var full = "#D0342B";
var addition = "#00D300";
var already_used = parseInt(d3.select("#"+element).select(".usedbar").attr("d"), 10);
d3.select("#"+element).select(".newbar")
.transition()
.duration(500)
.attr("width", function () {
if ((value + already_used) >= 100) {
return "100%";
} else {
return (value + already_used)+ "%";
}
})
.style("fill", function() {
if (value > (100 - already_used)) {
return full;
} else {
return addition;
}
});
// Find Progress Bars, we'll need both of them
var bars = $('#' + element).find('.progress-bar');
// Determine how much is already used -> this is the first bar
// Also, convert it to an int ;)
var used_val = +$(bars[0]).attr('aria-valuenow');
// Calculate new total
var total = used_val + value;
// Make sure to normalize the value to 100 or less
if (total > 100) {
value = 100 - used_val;
}
// Turn percentage into a proper percentage string for style
var percent_str = value + '%';
// jQuery construct it and then cache it, we need it more than once
var $bar = $(bars[1]);
// Update the second progress bar
$bar.css('width', percent_str)
.attr('aria-valuenow', value)
.find('.sr-only')
.html(percent_str);
// If the value is going to set total to 100+, set danger class
if (total > 99) {
$bar.removeClass('progress-bar-warning').addClass('progress-bar-danger');
} else {
$bar.removeClass('progress-bar-danger');
/*eslint-disable */
total > 89 ?
$bar.addClass('progress-bar-warning') :
$bar.removeClass('progress-bar-warning');
/*eslint-enable */
}
}, },
/* /*
@ -476,9 +428,10 @@ horizon.Quota = {
} }
$(this.user_value_form_inputs).each(function(index, element) { $(this.user_value_form_inputs).each(function(index, element) {
$(element).on('input', function(evt) { $(element).on('input', function() {
var progress_element = $('div[data-progress-indicator-for=' + $(evt.target).attr('id') + ']'); var $this = $(this);
var integers_in_input = $(evt.target).val().match(/\d+/g); var $progress_element = $('div[data-progress-indicator-for=' + $this.attr('id') + ']');
var integers_in_input = $this.val().match(/\d+/g);
var user_integer; var user_integer;
if(integers_in_input === null) { if(integers_in_input === null) {
@ -496,44 +449,8 @@ horizon.Quota = {
var progress_amount = parseInt(user_integer, 10); var progress_amount = parseInt(user_integer, 10);
scope.updateUsageFor(progress_element, progress_amount); scope.updateUsageFor($progress_element, progress_amount);
}); });
}); });
},
/*
Animate the progress bars of elements which indicate they should
automatically be incremented, as opposed to elements which trigger
progress updates based on form element input or changes.
*/
_initialAnimations: function() {
var scope = this;
$(this.auto_value_progress_bars).each(function(index, element) {
var auto_progress = $(element);
var update_amount = parseInt(auto_progress.attr('data-progress-indicator-step-by'), 10);
scope.updateUsageFor(auto_progress, update_amount);
});
},
// Draw the initial d3 bars
_initialCreation: function(bars) {
// Draw the initial progress bars
var scope = this;
$(bars).each(function(index, element) {
var progress_element = $(element);
var quota_limit = parseInt(progress_element.attr('data-quota-limit'), 10);
var quota_used = parseInt(progress_element.attr('data-quota-used'), 10);
var percentage_used = 0;
if (!isNaN(quota_limit) && !isNaN(quota_used)) {
// If NaN percentage_used is 0
percentage_used = (quota_used / quota_limit) * 100;
}
scope.drawUsed($(element).attr('id'), percentage_used);
});
} }
}; };

View File

@ -0,0 +1,29 @@
{% load horizon %}
{% minifyspace %}
<div class="progress">
{% for this_bar in bars %}
<div class="progress-bar
{% if this_bar.context %}
progress-bar-{{ this_bar.context }}
{% endif %}
{% if striped %}
progress-bar-striped
{% if animated %}
active
{% endif %}
{% endif %}"
role="progressbar"
aria-valuenow="{{ this_bar.percent }}"
aria-valuemin="{{ min_val }}"
aria-valuemax="{{ max_val }}"
style="width: {{ this_bar.percent }}%;{% if text %} min-width: 2em;{% endif %}">
{% if not text %}
<span class="sr-only">
{{ this_bar.percent }}%
</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endminifyspace %}

View File

@ -0,0 +1,58 @@
# Copyright 2015 Hewlett Packard Enterprise Software, LLC
# All Rights Reserved.
#
# 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.
from __future__ import absolute_import
from django import template
register = template.Library()
@register.inclusion_tag('bootstrap/progress_bar.html')
def bs_progress_bar(*args, **kwargs):
"""A Standard Bootstrap Progress Bar.
http://getbootstrap.com/components/#progress
param args (Array of Numbers: 0-100): Percent of Progress Bars
param context (String): Adds 'progress-bar-{context} to the class attribute
param contexts (Array of Strings): Cycles through contexts for stacked bars
param text (Boolean): True: shows value within the bar, False: uses sr span
param striped (Boolean): Adds 'progress-bar-striped' to the class attribute
param animated (Boolean): Adds 'active' to the class attribute if striped
param min_val (0): Used for the aria-min value
param max_val (0): Used for the aria-max value
"""
bars = []
contexts = kwargs.get(
'contexts',
['', 'success', 'info', 'warning', 'danger']
)
for ndx, arg in enumerate(args):
bars.append(
dict(percent=arg,
context=kwargs.get('context', contexts[ndx % len(contexts)]))
)
return {
'bars': bars,
'text': kwargs.pop('text', False),
'striped': kwargs.pop('striped', False),
'animated': kwargs.pop('animated', False),
'min_val': kwargs.pop('min_val', 0),
'max_val': kwargs.pop('max_val', 100),
}

View File

@ -27,6 +27,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon.base import Horizon # noqa from horizon.base import Horizon # noqa
from horizon import conf from horizon import conf
register = template.Library() register = template.Library()
@ -214,9 +215,7 @@ def datepicker_locale():
def minifyspace(parser, token): def minifyspace(parser, token):
"""Removes whitespace including tab and newline characters. Do not use this """Removes whitespace including tab and newline characters. Do not use this
if you are using a <pre> tag if you are using a <pre> tag
Example usage:: Example usage::
{% minifyspace %} {% minifyspace %}
<p> <p>
<a title="foo" <a title="foo"
@ -225,9 +224,7 @@ def minifyspace(parser, token):
</a> </a>
</p> </p>
{% endminifyspace %} {% endminifyspace %}
This example would return this HTML:: This example would return this HTML::
<p><a title="foo" href="foo/">Foo</a></p> <p><a title="foo" href="foo/">Foo</a></p>
""" """
nodelist = parser.parse(('endminifyspace',)) nodelist = parser.parse(('endminifyspace',))

View File

@ -1,5 +1,5 @@
{% extends "horizon/common/_modal_form.html" %} {% extends "horizon/common/_modal_form.html" %}
{% load horizon i18n %} {% load horizon i18n bootstrap %}
{% block modal-body-right %} {% block modal-body-right %}
<div class="quota-dynamic"> <div class="quota-dynamic">
@ -8,11 +8,20 @@
<h3>{% trans "Project Quotas" %}</h3> <h3>{% trans "Project Quotas" %}</h3>
<div class="quota_title"> <div class="quota_title">
<strong>{% trans "Floating IP" %} <span>({{ usages.floating_ips.used }})</span></strong> <div class="pull-left">
<p>{{ usages.floating_ips.available|quota }}</p> <strong>{% trans "Floating IP" %}</strong>
<span>({{ usages.floating_ips.used }})</span>
</div>
<span class="pull-right">{{ usages.floating_ips.available|quota }}</span>
</div>
<div id="floating_ip_progress"
class="quota_bar"
data-quota-used="{{ usages.floating_ips.used }}"
data-quota-limit="{{ usages.floating_ips.quota }}">
{% widthratio usages.floating_ips.used usages.floating_ips.quota 100 as ip_percent %}
{% widthratio 100 usages.floating_ips.quota 1 as single_step %}
{% bs_progress_bar ip_percent single_step %}
</div> </div>
<div class="clearfix"></div>
<div id="floating_ip_progress" class="quota_bar" data-quota-used="{{ usages.floating_ips.used }}" data-quota-limit="{{ usages.floating_ips.quota }}" data-progress-indicator-step-by="1"></div>
</div> </div>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">

View File

@ -1,4 +1,4 @@
{% load i18n horizon humanize %} {% load i18n horizon humanize bootstrap %}
{% block help_message %} {% block help_message %}
{% endblock %} {% endblock %}
@ -8,7 +8,7 @@
<tbody> <tbody>
<tr><td class="flavor_name">{% trans "Name" %}</td><td><span id="flavor_name"></span></td></tr> <tr><td class="flavor_name">{% trans "Name" %}</td><td><span id="flavor_name"></span></td></tr>
<tr><td class="flavor_name">{% trans "VCPUs" %}</td><td><span id="flavor_vcpus"></span></td></tr> <tr><td class="flavor_name">{% trans "VCPUs" %}</td><td><span id="flavor_vcpus"></span></td></tr>
<tr><td class="flavor_name">{% trans "Root Disk" %}</td><td><span id="flavor_disk"> </span> {% trans "GB" %}</td></tr> <tr><td class="flavor_name">{% trans "Root Disk" %}</td><td><span id="flavor_disk"></span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "Ephemeral Disk" %}</td><td><span id="flavor_ephemeral"></span> {% trans "GB" %}</td></tr> <tr><td class="flavor_name">{% trans "Ephemeral Disk" %}</td><td><span id="flavor_ephemeral"></span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "Total Disk" %}</td><td><span id="flavor_disk_total"></span> {% trans "GB" %}</td></tr> <tr><td class="flavor_name">{% trans "Total Disk" %}</td><td><span id="flavor_disk_total"></span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "RAM" %}</td><td><span id="flavor_ram"></span> {% trans "MB" %}</td></tr> <tr><td class="flavor_name">{% trans "RAM" %}</td><td><span id="flavor_ram"></span> {% trans "MB" %}</td></tr>
@ -17,28 +17,63 @@
<div class="quota-dynamic"> <div class="quota-dynamic">
<h4>{% trans "Project Limits" %}</h4> <h4>{% trans "Project Limits" %}</h4>
<div class="quota_title clearfix"> <div class="quota_title">
<strong>{% trans "Number of Instances" %}</strong> <strong class="pull-left">{% trans "Number of Instances" %}</strong>
{% blocktrans with used=usages.totalInstancesUsed|intcomma quota=usages.maxTotalInstances|intcomma|quotainf %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %} {% blocktrans with used=usages.totalInstancesUsed|intcomma quota=usages.maxTotalInstances|intcomma|quotainf %}
</div> <span class="pull-right">{{ used }} of {{ quota }} Used</span>
<div id="{{ resize_instance|yesno:"quota_resize_instance,quota_instances" }}" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalInstances }}" data-quota-used="{{ usages.totalInstancesUsed }}"> {% endblocktrans %}
</div> </div>
<div class="quota_title clearfix"> {{ minifyspace }}
<strong>{% trans "Number of VCPUs" %}</strong> <div id="{{ resize_instance|yesno:"quota_resize_instance,quota_instances" }}"
{% blocktrans with used=usages.totalCoresUsed|intcomma quota=usages.maxTotalCores|intcomma|quotainf %}<p>{{ used }} of {{ quota }} Used</p>{% endblocktrans %} class="quota_bar"
data-progress-indicator-flavor
data-quota-limit="{{ usages.maxTotalInstances }}"
data-quota-used="{{ usages.totalInstancesUsed }}">
{% widthratio usages.totalInstancesUsed usages.maxTotalInstances 100 as instance_percent %}
{% bs_progress_bar instance_percent 0 %}
</div> </div>
<div id="quota_vcpus" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalCores }}" data-quota-used="{{ usages.totalCoresUsed }}"> {{ endminifyspace }}
<div class="quota_title">
<strong class="pull-left">{% trans "Number of VCPUs" %}</strong>
{% blocktrans with used=usages.totalCoresUsed|intcomma quota=usages.maxTotalCores|intcomma|quotainf %}
<span class="pull-right">{{ used }} of {{ quota }} Used</span>
{% endblocktrans %}
</div> </div>
<div class="quota_title clearfix"> {{ minifyspace }}
<strong>{% trans "Total RAM" %}</strong> <div id="quota_vcpus"
{% blocktrans with used=usages.totalRAMUsed|intcomma quota=usages.maxTotalRAMSize|intcomma|quotainf %}<p>{{ used }} of {{ quota }} MB Used</p>{% endblocktrans %} class="quota_bar"
data-progress-indicator-flavor
data-quota-limit="{{ usages.maxTotalCores }}"
data-quota-used="{{ usages.totalCoresUsed }}">
{% widthratio usages.totalCoresUsed usages.maxTotalCores 100 as vcpu_percent %}
{% bs_progress_bar vcpu_percent 0 %}
</div> </div>
<div id="quota_ram" data-progress-indicator-flavor data-quota-limit="{{ usages.maxTotalRAMSize }}" data-quota-used="{{ usages.totalRAMUsed }}" class="quota_bar"> {{ endminifyspace }}
<div class="quota_title">
<strong class="pull-left">{% trans "Total RAM" %}</strong>
{% blocktrans with used=usages.totalRAMUsed|intcomma quota=usages.maxTotalRAMSize|intcomma|quotainf %}
<span class="pull-right">{{ used }} of {{ quota }} MB Used</span>
{% endblocktrans %}
</div> </div>
{{ minifyspace }}
<div id="quota_ram"
class="quota_bar"
data-progress-indicator-flavor
data-quota-limit="{{ usages.maxTotalRAMSize }}"
data-quota-used="{{ usages.totalRAMUsed }}">
{% widthratio usages.totalRAMUsed usages.maxTotalRAMSize 100 as vcpu_percent %}
{% bs_progress_bar vcpu_percent 0 %}
</div>
{{ endminifyspace }}
</div> </div>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
some_disabled_msg = '{{_("Some flavors not meeting minimum image requirements have been disabled.")|escapejs }}'; some_disabled_msg = '{{_("Some flavors not meeting minimum image requirements have been disabled.")|escapejs }}';
all_disabled_msg = '{{_("No flavors meet minimum criteria for selected image.")|escapejs }}'; all_disabled_msg = '{{_("No flavors meet minimum criteria for selected image.")|escapejs }}';

View File

@ -1,4 +1,4 @@
{% load i18n horizon humanize %} {% load i18n horizon humanize bootstrap %}
<h3>{% trans "Description:" %}</h3> <h3>{% trans "Description:" %}</h3>
@ -6,12 +6,21 @@
<h3>{% trans "Volume Limits" %}</h3> <h3>{% trans "Volume Limits" %}</h3>
<div class="quota_title clearfix"> <div class="quota_title">
<strong>{% trans "Total Gibibytes" %} <span>({{ usages.gigabytesUsed|intcomma }} {% trans "GiB" %})</span></strong> <div class="pull-left">
<p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</p> <strong>{% trans "Total Gibibytes" %}</strong>
<span>({{ usages.gigabytesUsed|intcomma }} {% trans "GiB" %})</span>
</div>
<span class="pull-right">{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</span>
</div> </div>
<div id="quota_size" data-progress-indicator-for="id_new_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used="{{ usages.gigabytesUsed }}" class="quota_bar"> <div id="quota_size"
data-progress-indicator-for="id_new_size"
data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}"
data-quota-used="{{ usages.gigabytesUsed }}"
class="quota_bar">
{% widthratio usages.gigabytesUsed usages.maxTotalVolumeGigabytes 100 as gigabytes_percent %}
{% bs_progress_bar gigabytes_percent 0 %}
</div> </div>
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">

View File

@ -1,35 +1,56 @@
{% load i18n horizon humanize %} {% load i18n horizon humanize bootstrap %}
<h3>{% trans "Description:" %}</h3> <h3>{% trans "Description:" %}</h3>
<p>{% blocktrans %} <p>{% blocktrans %}Volumes are block devices that can be attached to instances.{% endblocktrans %}</p>
Volumes are block devices that can be attached to instances.
{% endblocktrans %}
</p>
<div id="id_show_volume_type_desc_div"> <div id="id_show_volume_type_desc_div">
<h3>{% trans "Volume Type Description:" %}</h3> <h3>{% trans "Volume Type Description:" %}</h3>
<h4><b><span id="id_show_volume_type_name"></span></b></h4> <h4>
<p id="id_show_volume_type_desc"></p> <strong id="id_show_volume_type_name"></strong>
</h4>
<p id="id_show_volume_type_desc"></p>
</div> </div>
<h3>{% block head %}{% trans "Volume Limits" %}{% endblock %}</h3> <h3>{% block head %}{% trans "Volume Limits" %}{% endblock %}</h3>
<div class="quota_title clearfix"> <div class="quota_title">
<strong>{% trans "Total Gibibytes" %} <span>({% block gigabytes_used %}{{ usages.gigabytesUsed|intcomma }}{% endblock %} {% trans "GiB" %})</span></strong> <div class="pull-left">
<p>{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</p> <strong">{% trans "Total Gibibytes" %}</strong>
<span>({% block gigabytes_used %}{{ usages.gigabytesUsed|intcomma }}{% endblock %} {% trans "GiB" %})</span>
</div>
<span class="pull-right">{{ usages.maxTotalVolumeGigabytes|intcomma|quota:_("GiB") }}</span>
</div> </div>
<div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" data-quota-used={% block gigabytes_used_progress %}"{{ usages.gigabytesUsed }}"{% endblock %} class="quota_bar"> {{ minifyspace }}
<div id="quota_size"
data-progress-indicator-for="id_size"
data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}"
data-quota-used={% block gigabytes_used_progress %}"{{ usages.gigabytesUsed }}"{% endblock %}
class="quota_bar">
{% widthratio usages.gigabytesUsed usages.maxTotalVolumeGigabytes 100 as gigabytes_percent %}
{% bs_progress_bar gigabytes_percent 0 %}
</div>
{{ endminifyspace }}
<div class="quota_title">
<div class="pull-left">
<strong>{% block type_title %}{% trans "Number of Volumes" %}{% endblock %}</strong>
<span>({% block used %}{{ usages.volumesUsed|intcomma }}{% endblock %})</span>
</div>
<span class="pull-right">{% block total %}{{ usages.maxTotalVolumes|intcomma|quota }}{% endblock %}</span>
</div> </div>
<div class="quota_title clearfix"> {{ minifyspace }}
<strong>{% block type_title %}{% trans "Number of Volumes" %}{% endblock %} <span>({% block used %}{{ usages.volumesUsed|intcomma }}{% endblock %})</span></strong> <div id={% block type_id %}"quota_volumes"{% endblock %}
<p>{% block total %}{{ usages.maxTotalVolumes|intcomma|quota }}{% endblock %}</p> data-quota-limit={% block total_progress %}"{{ usages.maxTotalVolumes }}"{% endblock %}
</div> data-quota-used={% block used_progress %}"{{ usages.volumesUsed }}"{% endblock %}
class="quota_bar">
<div id={% block type_id %}"quota_volumes"{% endblock %} data-progress-indicator-step-by="1" data-quota-limit={% block total_progress %}"{{ usages.maxTotalVolumes }}"{% endblock %} data-quota-used={% block used_progress %}"{{ usages.volumesUsed }}"{% endblock %} class="quota_bar"> {% widthratio usages.volumesUsed usages.maxTotalVolumes 100 as volumes_percent %}
{% widthratio 100 usages.maxTotalVolumes 1 as single_step %}
{% bs_progress_bar volumes_percent single_step %}
</div> </div>
{{ endminifyspace }}
<script type="text/javascript" charset="utf-8"> <script type="text/javascript" charset="utf-8">
if(typeof horizon.Quota !== 'undefined') { if(typeof horizon.Quota !== 'undefined') {
@ -40,8 +61,8 @@
}); });
} }
if(typeof horizon.Volumes !== 'undefined'){ if(typeof horizon.Volumes !== 'undefined') {
horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }}); horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }});
} else { } else {
addHorizonLoadEvent(function() { addHorizonLoadEvent(function() {
horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }}); horizon.Volumes.initWithTypes({{ volume_types|safe|default:"{}" }});

View File

@ -0,0 +1,61 @@
// The idea behind this mixin is to allow a variety of
// colors to be configured, from 1 - $num, that will
// toggle between an incrementing percentage ($increment)
// from the theme's primary brand color. This should
// adapt nicely to most themes.
@mixin make_bar_chart_distribution($num, $increment) {
@for $ii from 1 through $num {
$color_increment: $increment * ($ii/2);
// Set the arc color
.legacy-bar-chart-section:nth-child(#{$ii}n) {
@if $ii % 2 == 0 {
fill: lighten($progress-bar-bg, $color_increment * 1%);
} @else {
fill: darken($progress-bar-bg, $color_increment * 1%);
}
}
}
}
.legacy-bar-chart {
@extend .progress;
fill: $progress-bg;
// Set the colors!
@include make_bar_chart_distribution(8, 8);
.legacy-bar-chart-section {
@extend .progress-bar;
}
.unused_component.legacy-bar-chart-section {
fill: transparent;
}
.average_component {
stroke: $gray-dark;
stroke-dasharray: 6, 2;
stroke-width: 3px;
}
.average_component_hover {
stroke-width: 5px;
}
.used_component_label {
font-size: $font-size-base;
fill: $text-color;
}
.used_component_label_arrow {
stroke: $text-color;
}
}
// (hurgleburgler) Remove when we've upgraded to Bootstrap 3.3.5, because we need
// https://github.com/twbs/bootstrap/commit/2c2564faefd99b044273f132275bb620b5eccb93
.progress-bar {
min-width: 0 !important;
}

View File

@ -13,14 +13,16 @@
} }
.quota_title { .quota_title {
margin-bottom: $padding-large-vertical; font-size: $font-size-h6;
@extend .h5; margin-bottom: 0;
@include clearfix();
& > span { & > span {
color: $gray-light; color: $gray-light;
} }
} }
// This extend is important as it inherits the padding as well as font size
.quota_subtitle { .quota_subtitle {
@extend .h6; @extend .h6;
} }

View File

@ -29,6 +29,7 @@
@import "components/network_topology"; @import "components/network_topology";
@import "components/context_selection"; @import "components/context_selection";
@import "components/pie_charts"; @import "components/pie_charts";
@import "components/bar_charts";
@import "components/quota"; @import "components/quota";
@import "components/table_actions"; @import "components/table_actions";
@import "/framework/framework"; @import "/framework/framework";

View File

@ -1,3 +1,4 @@
.quota_title { .quota_title {
font-size: $font-size-h5;
font-weight: bold; font-weight: bold;
} }