@spalger/kibana
Version:
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic
211 lines (180 loc) • 6 kB
JavaScript
define(function (require) {
return function PieChartFactory(Private) {
var d3 = require('d3');
var _ = require('lodash');
var $ = require('jquery');
var Chart = Private(require('ui/vislib/visualizations/_chart'));
var errors = require('ui/errors');
/**
* Pie Chart Visualization
*
* @class PieChart
* @constructor
* @extends Chart
* @param handler {Object} Reference to the Handler Class Constructor
* @param el {HTMLElement} HTML element to which the chart will be appended
* @param chartData {Object} Elasticsearch query results for this specific chart
*/
_.class(PieChart).inherits(Chart);
function PieChart(handler, chartEl, chartData) {
if (!(this instanceof PieChart)) {
return new PieChart(handler, chartEl, chartData);
}
PieChart.Super.apply(this, arguments);
var charts = this.handler.data.getVisData();
this._validatePieData(charts);
this._attr = _.defaults(handler._attr || {}, {
isDonut: handler._attr.isDonut || false
});
}
/**
* Checks whether pie slices have all zero values.
* If so, an error is thrown.
*/
PieChart.prototype._validatePieData = function (charts) {
var isAllZeros = charts.every(function (chart) {
return chart.slices.children.length === 0;
});
if (isAllZeros) { throw new errors.PieContainsAllZeros(); }
};
/**
* Adds Events to SVG paths
*
* @method addPathEvents
* @param element {D3.Selection} Reference to SVG path
* @returns {D3.Selection} SVG path with event listeners attached
*/
PieChart.prototype.addPathEvents = function (element) {
var events = this.events;
return element
.call(events.addHoverEvent())
.call(events.addMouseoutEvent())
.call(events.addClickEvent());
};
PieChart.prototype.convertToPercentage = function (slices) {
(function assignPercentages(slices) {
if (slices.sumOfChildren != null) return;
var parent = slices;
var children = parent.children;
var parentPercent = parent.percentOfParent;
var sum = parent.sumOfChildren = Math.abs(children.reduce(function (sum, child) {
return sum + Math.abs(child.size);
}, 0));
children.forEach(function (child) {
child.percentOfGroup = Math.abs(child.size) / sum;
child.percentOfParent = child.percentOfGroup;
if (parentPercent != null) {
child.percentOfParent *= parentPercent;
}
if (child.children) {
assignPercentages(child);
}
});
}(slices));
};
/**
* Adds pie paths to SVG
*
* @method addPath
* @param width {Number} Width of SVG
* @param height {Number} Height of SVG
* @param svg {HTMLElement} Chart SVG
* @param slices {Object} Chart data
* @returns {D3.Selection} SVG with paths attached
*/
PieChart.prototype.addPath = function (width, height, svg, slices) {
var self = this;
var marginFactor = 0.95;
var isDonut = self._attr.isDonut;
var radius = (Math.min(width, height) / 2) * marginFactor;
var color = self.handler.data.getPieColorFunc();
var tooltip = self.tooltip;
var isTooltip = self._attr.addTooltip;
var partition = d3.layout.partition()
.sort(null)
.value(function (d) {
return d.percentOfParent * 100;
});
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var arc = d3.svg.arc()
.startAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function (d) {
// option for a single layer, i.e pie chart
if (d.depth === 1 && !isDonut) {
// return no inner radius
return 0;
}
return Math.max(0, y(d.y));
})
.outerRadius(function (d) {
return Math.max(0, y(d.y + d.dy));
});
var path = svg
.datum(slices)
.selectAll('path')
.data(partition.nodes)
.enter()
.append('path')
.attr('d', arc)
.attr('class', function (d) {
if (d.depth === 0) { return; }
return 'slice';
})
.call(self._addIdentifier, 'name')
.style('stroke', '#fff')
.style('fill', function (d) {
if (d.depth === 0) { return 'none'; }
return color(d.name);
});
if (isTooltip) {
path.call(tooltip.render());
}
return path;
};
PieChart.prototype._validateContainerSize = function (width, height) {
var minWidth = 20;
var minHeight = 20;
if (width <= minWidth || height <= minHeight) {
throw new errors.ContainerTooSmall();
}
};
/**
* Renders d3 visualization
*
* @method draw
* @returns {Function} Creates the pie chart
*/
PieChart.prototype.draw = function () {
var self = this;
return function (selection) {
selection.each(function (data) {
var slices = data.slices;
var div = d3.select(this);
var width = $(this).width();
var height = $(this).height();
var path;
if (!slices.children.length) return;
self.convertToPercentage(slices);
self._validateContainerSize(width, height);
var svg = div.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
path = self.addPath(width, height, svg, slices);
self.addPathEvents(path);
return svg;
});
};
};
return PieChart;
};
});