horizon/horizon/static/horizon/js/horizon.d3piechart.js

268 lines
7.4 KiB
JavaScript

/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/*
Draw pie charts in d3.
To use, a div is required with the class .d3_pie_chart_usage or
.d3_pie_chart_distribution, and a data-used attribute in the div
that stores the data used to fill the chart.
Example (usage):
<div class="pie-chart-usage"
data-used="{% widthratio current_val max_val 100 %}">
</div>
Example (distribution):
<div class="pie-chart-distribution"
data-used="Controller=1|Compute=2|Object Storage=3|Block Storage=4">
</div>
*/
// Pie chart SVG internal dimensions
var WIDTH = 100;
var HEIGHT = 100;
var RADIUS = 45;
function create_vis(chart) {
return d3.select(chart).append("svg:svg")
.attr("class", "chart legacy-pie-chart")
.attr("viewBox", "0 0 " + WIDTH + " " + HEIGHT)
.append("g")
.attr("transform",
"translate(" + (WIDTH / 2) + "," + (HEIGHT / 2) + ")");
}
function create_arc() {
return d3.svg.arc()
.outerRadius(RADIUS)
.innerRadius(0);
}
function create_pie(param) {
return d3.layout.pie()
.sort(null)
.value(function(d){ return d[param]; });
}
horizon.d3_pie_chart_usage = {
init: function() {
var self = this;
// Pie Charts
var pie_chart_data = $(".pie-chart-usage");
self.chart = d3.selectAll(".pie-chart-usage");
for (var i = 0; i < pie_chart_data.length; i++) {
var data = $(pie_chart_data[i]).data("used");
// When true is passed in only show the number, not the actual pie chart
if (data[1] === true) {
self.data = data[0];
self.pieChart(i, false);
} else {
var used = Math.min(parseInt(data, 10), 100);
self.data = [{"percentage":used}, {"percentage":100 - used}];
self.pieChart(i, true);
}
}
},
// Draw a pie chart
pieChart: function(i, fill) {
var self = this;
var vis = create_vis(self.chart[0][i]);
var arc = create_arc();
var pie = create_pie("percentage");
// Draw an empty pie chart
vis.selectAll(".arc")
.data(pie([{"percentage":10}]))
.enter()
.append("path")
.attr("class","arc")
.attr("d", arc);
// Animate filling the pie chart
var animate = function(data) {
vis.selectAll(".arc")
.data(pie(data))
.enter()
.append("path")
.attr("class", function() {
var ret_val = "arc inner";
if (self.data[0].percentage >= 100) {
ret_val += " FULL";
} else if (self.data[0].percentage >= 80) {
ret_val += " NEARLY_FULL";
}
return ret_val;
})
.attr("d", arc)
.transition()
.duration(500)
.attrTween("d", function(start) {
start.endAngle = start.startAngle = 0;
var end = {
startAngle: 0,
endAngle: 2 * Math.PI * (100 - start.value) / 100
};
var tween = d3.interpolate(start, end);
return function(t) { return arc(tween(t)); };
});
};
var show_numbers = function() {
vis.append("text")
.attr("class", "chart-numbers")
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'central')
.text(self.data);
};
if (fill) {
animate(self.data);
} else {
// TODO: this seems to be very broken...
// https://bugs.launchpad.net/horizon/+bug/1490787
// It prints: [object Object] to the screen
show_numbers(self.data);
}
}
};
horizon.d3_pie_chart_distribution = {
init: function() {
var self = this;
var pie_chart_data = $(".pie-chart-distribution");
self.chart = d3.selectAll(".pie-chart-distribution");
for (var i = 0; i < pie_chart_data.length; i++) {
var parts = $(pie_chart_data[i]).data("used").split("|");
self.data = [];
self.keys = [];
for (var j = 0; j < parts.length; j++) {
var key_value = parts[j].split("=");
var d = {
key: key_value[0],
value: key_value[1]
};
self.data.push(d);
self.keys.push(key_value[0]);
}
self.pieChart(i, $(pie_chart_data[i]));
}
},
// Draw a pie chart
pieChart: function(i, $elem) {
var self = this;
var vis = create_vis(self.chart[0][i]);
var arc = create_arc();
var pie = create_pie("value");
var total = 0;
for (var j = 0; j < self.data.length; j++) {
total = total + parseInt(self.data[j].value, 10);
}
var initial_data = [];
if (total === 0) {
initial_data = [{"value": 1}];
}
// Draw an empty pie chart
vis.selectAll(".arc")
.data(pie(initial_data))
.enter()
.append("path")
.attr("class","arc")
.attr("d", arc);
// Animate filling the pie chart
var animate = function(data) {
vis.selectAll(".arc")
.data(pie(data))
.enter()
.append("path")
.attr("class","arc inner")
.attr("d", arc)
.transition()
.duration(500)
.attrTween("d", function(start) {
start.endAngle = start.startAngle;
var end = jQuery.extend({}, start);
end.endAngle = end.startAngle + 2 * Math.PI / total * end.value;
var tween = d3.interpolate(start, end);
return function(t) { return arc(tween(t)); };
});
};
if (total !== 0) {
animate(self.data);
}
// The legend actually doesn't need to be an SVG element at all
// By making it standard markup, we can allow greater customization
var $legend = $(document.createElement('div')).addClass('legend');
// This loop might seem wasteful, but we need to determine the total for the label
var total = 0;
for (var j = 0; j < self.data.length; j++) {
// We need to use it as a float again later, convert it now and store ... its faster
self.data[j].value = parseFloat(self.data[j].value);
total += self.data[j].value;
}
for (var j = 0; j < self.data.length; j++) {
var this_item = self.data[j];
var $this_group = $(document.createElement('div'))
.addClass('legend-group')
.appendTo($legend);
$(document.createElement('span'))
.addClass('legend-symbol')
.appendTo($this_group);
$(document.createElement('span'))
.addClass('legend-key')
.text(this_item.key)
.appendTo($this_group);
var $this_value = $(document.createElement('span'))
.addClass('legend-value');
// If its zero, then we don't need to Math it.
if (this_item.value === 0) {
$this_item.text("0%");
} else {
$this_value.text(Math.round((this_item.value/total) * 100) + "%");
}
// Append it to the container
$this_value.appendTo($this_group);
}
// Append the container last ... cause its faster
$elem.append($legend);
}
};
horizon.addInitFunction(function () {
horizon.d3_pie_chart_usage.init();
horizon.d3_pie_chart_distribution.init();
});