Adds d3.js library and reworked quota infographics

The d3.js library is now added and the quota infographics
have been redone to use d3.

Added a reusable animated d3 pie chart that is now used to
display the quota infographics.

Reworked the progress bars (horizon.quota.js) so that project
quotas when creating a new Instance / Volume display a progress bar
in D3. Also changed progress bar for adding a floating ip so that
it shows that 1 will be used up on create just like adding a new volume.

Change-Id: I2930c3c80736e50d3f38c001aac1918e1932be5e
Implements: blueprint d3
This commit is contained in:
Bradley Jones 2013-05-06 16:53:11 -07:00
parent 09fc3f1fbb
commit ccb11b7099
11 changed files with 240 additions and 62 deletions

View File

@ -0,0 +1,91 @@
/*
Draw pie chart in d3.
To use, a div is required with the class .d3_pie_chart
and a data-used attribute in the div
that stores the percentage to fill the chart
Example:
<div class="d3_pie_chart"
data-used="{% widthratio current_val max_val 100 %}">
</div>
*/
horizon.d3_pie_chart = {
w: 100,
h: 100,
r: 45,
bkgrnd: "#F2F2F2",
frgrnd: "#4790B2",
init: function() {
var self = this;
// Pie Charts
var pie_chart_data = $(".d3_pie_chart");
self.chart = d3.selectAll(".d3_pie_chart");
for (var i = 0; i < pie_chart_data.length; i++) {
used = parseInt(pie_chart_data[i].dataset.used);
self.data = [{"percentage":used}, {"percentage":100 - used}];
self.pieChart(i);
}
},
// Draw a pie chart
pieChart: function(i) {
var self = this;
var vis = d3.select(self.chart[0][i]).append("svg:svg")
.attr("class", "chart")
.attr("width", self.w)
.attr("height", self.h)
.style("background-color", "white")
.append("g")
.attr("transform", "translate(" + (self.r + 2) + "," + (self.r + 2) + ")")
var arc = d3.svg.arc()
.outerRadius(self.r)
.innerRadius(0)
var pie = d3.layout.pie()
.sort(null)
.value(function(d){ return d.percentage; })
// Draw an empty pie chart
var piechart = vis.selectAll(".arc")
.data(pie([{"percentage":10}]))
.enter()
.append("path")
.attr("class","arc")
.attr("d", arc)
.style("fill", self.frgrnd)
.style("stroke", "#CCCCCC")
.style("stroke-width", 1)
.each(function(d) {return self.current = d;})
// Animate filling the pie chart
animate = function(data) {
var piechart = vis.selectAll(".arc")
.data(pie(data))
.enter()
.append("path")
.attr("class","arc")
.attr("d", arc)
.style("fill", self.bkgrnd)
.style("stroke", "#CCCCCC")
.style("stroke-width", 1)
.each(function(d) {return self.current = d;})
.transition()
.duration(500)
.attrTween("d", function(a) {
var tween = d3.interpolate(self.current, a);
self.current = tween(0);
return function(t) { return arc(tween(t)); }
})
}
animate(self.data)
}
}
horizon.addInitFunction(function () {
horizon.d3_pie_chart.init();
});

View File

@ -1,6 +1,6 @@
/*
Used for animating and displaying quota information on forms which use the
Bootstrap progress bars. Also used for displaying flavor details on modal-
Used for animating and displaying quota information on forms using
D3js progress bars. Also used for displaying flavor details on modal-
dialogs.
Usage:
@ -8,7 +8,6 @@
DOM structure like this in your Django template:
<div id="your_progress_bar_id" class="quota_bar">
{% horizon_progress_bar total_number_used max_number_allowed %}
</div>
With this progress bar, you then need to add some data- HTML attributes
@ -67,6 +66,11 @@ horizon.Quota = {
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();
},
@ -126,23 +130,6 @@ horizon.Quota = {
}
},
// Updates a progress bar, taking care of exceeding quota display as well.
update: function(element, percentage_used, percentage_to_update) {
var update_width = percentage_to_update;
if(percentage_to_update + percentage_used > 100) {
update_width = 100 - percentage_used;
if(!element.hasClass('progress_bar_over')) {
element.addClass('progress_bar_over');
}
} else {
element.removeClass('progress_bar_over');
}
element.animate({width: parseInt(update_width, 10) + "%"}, 300);
},
/*
When a new flavor is selected, this takes care of updating the relevant
progress bars associated with the flavor quota usage.
@ -176,13 +163,80 @@ horizon.Quota = {
updateUsageFor: function(progress_element, increment_by) {
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_used = parseInt(progress_element.attr('data-quota-used'), 10);
var percentage_to_update = ((increment_by / quota_limit) * 100);
var percentage_used = ((quota_used / quota_limit) * 100);
this.update(update_indicator, percentage_used, 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= "grey";
// 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", "lightgreen")
// used resources
var used_bar = 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", frgrnd)
.attr("d", used)
.transition()
.duration(500)
.attr("width", used + "%")
},
// Update the progress Bar
update: function(element, value) {
var already_used = parseInt(d3.select("#"+element).select(".usedbar").attr("d"))
d3.select("#"+element).select(".newbar")
.transition()
.duration(500)
.attr("width", (value + already_used) + "%")
.style("fill", function() {
if (value > (100 - already_used)) { return "red" }
else {return "lightgreen" }
});
},
/*
@ -242,5 +296,25 @@ horizon.Quota = {
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);
if (!isNaN(quota_limit) && !isNaN(quota_used)) {
var percentage_used = ((quota_used / quota_limit) * 100);
} else { // If NaN percentage_used is 0
var percentage_used = 0;
}
scope.drawUsed($(element).attr('id'), percentage_used);
});
}
};

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,8 @@
<script src="{{ STATIC_URL }}horizon/lib/underscore/underscore-min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/jquery/jquery-ui-1.9.2.custom.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/d3.v3.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}bootstrap/js/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/hogan-2.0.0.js" type="text/javascript" charset='utf-8'></script>
@ -34,6 +36,7 @@
<script src='{{ STATIC_URL }}horizon/js/horizon.utils.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.projects.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopology.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.d3piechart.js' type='text/javascript' charset='utf-8'></script>
{% endcompress %}
{% comment %} Client-side Templates (These should *not* be inside the "compress" tag.) {% endcomment %}

View File

@ -1,4 +0,0 @@
<div class="progress_bar progress">
<div class="progress_bar_fill bar progress-warning" data-width="{% widthratio current_val max_val 100 %}" style="width: {% widthratio current_val max_val 100 %}%"></div>
<div class="progress_bar_selected bar progress-success"></div>
</div>

View File

@ -2,20 +2,29 @@
<div class="quota-dynamic">
<h3>{% trans "Quota Summary" %}</h3>
<strong>{% trans "Used" %}<span> {{ usage.quotas.instances.used|intcomma }} </span> {% trans "of" %} <span> {{ usage.quotas.instances.quota|intcomma }} </span>{% trans "Available Instances" %} </strong>
{% horizon_progress_bar usage.quotas.instances.used usage.quotas.instances.quota %}
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.instances.used usage.quotas.instances.quota 100 %}"></div>
<strong>{% trans "Available Instances" %} <br /> {% trans "Used" %}<span> {{ usage.quotas.instances.used|intcomma }} </span> {% trans "of" %} <span> {{ usage.quotas.instances.quota|intcomma }} </span></strong>
</div>
<strong>{% trans "Used" %} <span> {{ usage.quotas.cores.used|intcomma }} </span>{% trans "of" %}<span> {{ usage.quotas.cores.quota|intcomma }} </span>{% trans "Available vCPUs" %} </strong>
{% horizon_progress_bar usage.quotas.cores.used usage.quotas.cores.quota %}
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.cores.used usage.quotas.cores.quota 100 %}"></div>
<strong>{% trans "Available VCPUs" %} <br /> {% trans "Used" %} <span> {{ usage.quotas.cores.used|intcomma }} </span>{% trans "of" %}<span> {{ usage.quotas.cores.quota|intcomma }} </span></strong>
</div>
<strong>{% trans "Used" %} <span> {{ usage.quotas.ram.used|intcomma }} MB </span>{% trans "of" %}<span> {{ usage.quotas.ram.quota|intcomma }} MB </span>{% trans "Available RAM" %} </strong>
{% horizon_progress_bar usage.quotas.ram.used usage.quotas.ram.quota %}
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.ram.used usage.quotas.ram.quota 100 %}"></div>
<strong>{% trans "Available RAM" %} <br /> {% trans "Used" %} <span> {{ usage.quotas.ram.used|intcomma }} MB </span>{% trans "of" %}<span> {{ usage.quotas.ram.quota|intcomma }} MB </span></strong>
</div>
{% if usage.quotas.volumes %}
<strong>{% trans "Used" %} <span> {{ usage.quotas.volumes.used|intcomma }} </span>{% trans "of" %}<span> {{ usage.quotas.volumes.quota|intcomma }} </span>{% trans "Available volumes" %} </strong>
{% horizon_progress_bar usage.quotas.volumes.used usage.quotas.volumes.quota %}
<strong>{% trans "Used" %} <span> {{ usage.quotas.gigabytes.used|intcomma }} GB </span>{% trans "of" %}<span> {{ usage.quotas.gigabytes.quota|intcomma }} GB </span>{% trans "Available volume storage" %} </strong>
{% horizon_progress_bar usage.quotas.gigabytes.used usage.quotas.gigabytes.quota %}
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.volumes.used usage.quotas.volumes.quota 100 %}"></div>
<strong>{% trans "Available Volumes" %} <br /> {% trans "Used" %} <span> {{ usage.quotas.volumes.used|intcomma }} </span>{% trans "of" %}<span> {{ usage.quotas.volumes.quota|intcomma }} </span></strong>
</div>
<div class="d3_quota_bar">
<div class="d3_pie_chart" data-used="{% widthratio usage.quotas.gigabytes.used usage.quotas.gigabytes.quota 100 %}"></div>
<strong>{% trans "Available Volume Storage" %} <br /> {% trans "Used" %} <span> {{ usage.quotas.gigabytes.used|intcomma }} GB </span>{% trans "of" %}<span> {{ usage.quotas.gigabytes.quota|intcomma }} GB </span></strong>
</div>
{% endif %}
</div>

View File

@ -85,24 +85,6 @@ def horizon_dashboard_nav(context):
'request': context['request']}
@register.inclusion_tag('horizon/common/_progress_bar.html')
def horizon_progress_bar(current_val, max_val):
""" Renders a progress bar based on parameters passed to the tag. The first
parameter is the current value and the second is the max value.
Example: ``{% progress_bar 25 50 %}``
This will generate a half-full progress bar.
The rendered progress bar will fill the area of its container. To constrain
the rendered size of the bar provide a container with appropriate width and
height styles.
"""
return {'current_val': current_val,
'max_val': max_val}
@register.filter
def quota(val, units=None):
if val == float("inf"):

View File

@ -14,7 +14,7 @@
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<div class="right quota-dynamic">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Allocate a floating IP from a given floating ip pool." %}</p>
@ -24,8 +24,18 @@
<p>{{ usages.floating_ips.available|quota }}</p>
</div>
<div class="clearfix"></div>
<div class="quota_bar">{% horizon_progress_bar usages.floating_ips.used usages.floating_ips.quota %}</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>
<script type="text/javascript" charset="utf-8">
if(typeof horizon.Quota !== 'undefined') {
horizon.Quota.init();
} else {
addHorizonLoadEvent(function() {
horizon.Quota.init();
});
}
</script>
{% endblock %}
{% block modal-footer %}

View File

@ -22,7 +22,6 @@
<p>{{ usages.instances.available|quota|intcomma }}</p>
</div>
<div id="quota_instances" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.instances.quota }}" data-quota-used="{{ usages.instances.used }}">
{% horizon_progress_bar usages.instances.used usages.instances.quota %}
</div>
<div class="quota_title clearfix">
@ -30,7 +29,6 @@
<p>{{ usages.cores.available|quota|intcomma }}</p>
</div>
<div id="quota_vcpus" class="quota_bar" data-progress-indicator-flavor data-quota-limit="{{ usages.cores.quota }}" data-quota-used="{{ usages.cores.used }}">
{% horizon_progress_bar usages.cores.used usages.cores.quota %}
</div>
<div class="quota_title clearfix">
@ -38,7 +36,6 @@
<p>{{ usages.ram.available|quota:"MB"|intcomma }}</p>
</div>
<div id="quota_ram" data-progress-indicator-flavor data-quota-limit="{{ usages.ram.quota }}" data-quota-used="{{ usages.ram.used }}" class="quota_bar">
{% horizon_progress_bar usages.ram.used usages.ram.quota %}
</div>
</div>

View File

@ -28,7 +28,6 @@
</div>
<div id="quota_size" data-progress-indicator-for="id_size" data-quota-limit="{{ usages.gigabytes.quota }}" data-quota-used="{{ usages.gigabytes.used }}" class="quota_bar">
{% horizon_progress_bar usages.gigabytes.used usages.gigabytes.quota %}
</div>
<div class="quota_title clearfix">
@ -37,7 +36,6 @@
</div>
<div id="quota_volumes" data-progress-indicator-step-by="1" data-quota-limit="{{ usages.volumes.quota }}" data-quota-used="{{ usages.volumes.used }}" class="quota_bar">
{% horizon_progress_bar usages.volumes.used usages.volumes.quota %}
</div>
</div>

View File

@ -1196,6 +1196,19 @@ iframe {
background-color: red;
}
.d3_quota_bar {
width: 20%;
margin-bottom: 8px;
margin-top: 10px;
float: left;
text-align: center;
}
.quota-dynamic {
overflow: hidden;
margin-bottom: 8px;
}
.quota_title {
color: #999;
padding-bottom: 0;