@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
365 lines (314 loc) • 10.4 kB
JavaScript
define(function (require) {
return function AreaChartFactory(Private) {
var d3 = require('d3');
var _ = require('lodash');
var $ = require('jquery');
var PointSeriesChart = Private(require('ui/vislib/visualizations/_point_series_chart'));
var TimeMarker = Private(require('ui/vislib/visualizations/time_marker'));
var errors = require('ui/errors');
/**
* Area chart visualization
*
* @class AreaChart
* @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(AreaChart).inherits(PointSeriesChart);
function AreaChart(handler, chartEl, chartData) {
if (!(this instanceof AreaChart)) {
return new AreaChart(handler, chartEl, chartData);
}
AreaChart.Super.apply(this, arguments);
this.isOverlapping = (handler._attr.mode === 'overlap');
if (this.isOverlapping) {
// Default opacity should return to 0.6 on mouseout
handler._attr.defaultOpacity = 0.6;
}
this.checkIfEnoughData();
this._attr = _.defaults(handler._attr || {}, {
xValue: function (d) { return d.x; },
yValue: function (d) { return d.y; }
});
}
/**
* Adds SVG path to area chart
*
* @method addPath
* @param svg {HTMLElement} SVG to which rect are appended
* @param layers {Array} Chart data array
* @returns {D3.UpdateSelection} SVG with path added
*/
AreaChart.prototype.addPath = function (svg, layers) {
var self = this;
var ordered = this.handler.data.get('ordered');
var isTimeSeries = (ordered && ordered.date);
var isOverlapping = this.isOverlapping;
var color = this.handler.data.getColorFunc();
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate;
var area = d3.svg.area()
.x(function (d) {
if (isTimeSeries) {
return xScale(d.x);
}
return xScale(d.x) + xScale.rangeBand() / 2;
})
.y0(function (d) {
if (isOverlapping) {
return yScale(0);
}
return yScale(d.y0);
})
.y1(function (d) {
if (isOverlapping) {
return yScale(d.y);
}
return yScale(d.y0 + d.y);
})
.defined(function (d) { return !_.isNull(d.y); })
.interpolate(interpolate);
// Data layers
var layer = svg.selectAll('.layer')
.data(layers)
.enter()
.append('g')
.attr('class', function (d, i) {
return 'pathgroup ' + i;
});
// Append path
var path = layer.append('path')
.call(this._addIdentifier)
.style('fill', function (d) {
return color(d[0].label);
})
.classed('overlap_area', function () {
return isOverlapping;
});
// update
path.attr('d', function (d) {
return area(d);
});
return path;
};
/**
* Adds Events to SVG circles
*
* @method addCircleEvents
* @param element {D3.UpdateSelection} SVG circles
* @returns {D3.Selection} circles with event listeners attached
*/
AreaChart.prototype.addCircleEvents = function (element, svg) {
var events = this.events;
var isBrushable = events.isBrushable();
var brush = isBrushable ? events.addBrushEvent(svg) : undefined;
var hover = events.addHoverEvent();
var mouseout = events.addMouseoutEvent();
var click = events.addClickEvent();
var attachedEvents = element.call(hover).call(mouseout).call(click);
if (isBrushable) {
attachedEvents.call(brush);
}
return attachedEvents;
};
/**
* Adds SVG circles to area chart
*
* @method addCircles
* @param svg {HTMLElement} SVG to which circles are appended
* @param data {Array} Chart data array
* @returns {D3.UpdateSelection} SVG with circles added
*/
AreaChart.prototype.addCircles = function (svg, data) {
var self = this;
var color = this.handler.data.getColorFunc();
var xScale = this.handler.xAxis.xScale;
var yScale = this.handler.yAxis.yScale;
var ordered = this.handler.data.get('ordered');
var circleRadius = 12;
var circleStrokeWidth = 0;
var tooltip = this.tooltip;
var isTooltip = this._attr.addTooltip;
var isOverlapping = this.isOverlapping;
var layer;
var circles;
layer = svg.selectAll('.points')
.data(data)
.enter()
.append('g')
.attr('class', 'points area');
// append the circles
circles = layer
.selectAll('circles')
.data(function appendData(data) {
return data.filter(function isZeroOrNull(d) {
return d.y !== 0 && !_.isNull(d.y);
});
});
// exit
circles.exit().remove();
// enter
circles
.enter()
.append('circle')
.call(this._addIdentifier)
.attr('stroke', function strokeColor(d) {
return color(d.label);
})
.attr('fill', 'transparent')
.attr('stroke-width', circleStrokeWidth);
// update
circles
.attr('cx', function cx(d) {
if (ordered && ordered.date) {
return xScale(d.x);
}
return xScale(d.x) + xScale.rangeBand() / 2;
})
.attr('cy', function cy(d) {
if (isOverlapping) {
return yScale(d.y);
}
return yScale(d.y0 + d.y);
})
.attr('r', circleRadius);
// Add tooltip
if (isTooltip) {
circles.call(tooltip.render());
}
return circles;
};
/**
* Adds SVG clipPath
*
* @method addClipPath
* @param svg {HTMLElement} SVG to which clipPath is appended
* @param width {Number} SVG width
* @param height {Number} SVG height
* @returns {D3.UpdateSelection} SVG with clipPath added
*/
AreaChart.prototype.addClipPath = function (svg, width, height) {
// Prevents circles from being clipped at the top of the chart
var startX = 0;
var startY = 0;
var id = 'chart-area' + _.uniqueId();
// Creating clipPath
return svg
.attr('clip-path', 'url(#' + id + ')')
.append('clipPath')
.attr('id', id)
.append('rect')
.attr('x', startX)
.attr('y', startY)
.attr('width', width)
.attr('height', height);
};
AreaChart.prototype.checkIfEnoughData = function () {
var series = this.chartData.series;
var message = 'Area charts require more than one data point. Try adding ' +
'an X-Axis Aggregation';
var notEnoughData = series.some(function (obj) {
return obj.values.length < 2;
});
if (notEnoughData) {
throw new errors.NotEnoughData(message);
}
};
AreaChart.prototype.validateWiggleSelection = function () {
var isWiggle = this._attr.mode === 'wiggle';
var ordered = this.handler.data.get('ordered');
if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection();
};
/**
* Renders d3 visualization
*
* @method draw
* @returns {Function} Creates the area chart
*/
AreaChart.prototype.draw = function () {
// Attributes
var self = this;
var xScale = this.handler.xAxis.xScale;
var $elem = $(this.chartEl);
var margin = this._attr.margin;
var elWidth = this._attr.width = $elem.width();
var elHeight = this._attr.height = $elem.height();
var yMin = this.handler.yAxis.yMin;
var yScale = this.handler.yAxis.yScale;
var minWidth = 20;
var minHeight = 20;
var addTimeMarker = this._attr.addTimeMarker;
var times = this._attr.times || [];
var timeMarker;
var div;
var svg;
var width;
var height;
var layers;
var circles;
var path;
return function (selection) {
selection.each(function (data) {
// Stack data
layers = self.stackData(data);
// Get the width and height
width = elWidth;
height = elHeight - margin.top - margin.bottom;
if (addTimeMarker) {
timeMarker = new TimeMarker(times, xScale, height);
}
if (width < minWidth || height < minHeight) {
throw new errors.ContainerTooSmall();
}
self.validateWiggleSelection();
// Select the current DOM element
div = d3.select(this);
// Create the canvas for the visualization
svg = div.append('svg')
.attr('width', width)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(0,' + margin.top + ')');
// add clipPath to hide circles when they go out of bounds
self.addClipPath(svg, width, height);
self.createEndZones(svg);
// add path
path = self.addPath(svg, layers);
if (yMin < 0 && self._attr.mode !== 'wiggle' && self._attr.mode !== 'silhouette') {
// Draw line at yScale 0 value
svg.append('line')
.attr('class', 'zero-line')
.attr('x1', 0)
.attr('y1', yScale(0))
.attr('x2', width)
.attr('y2', yScale(0))
.style('stroke', '#ddd')
.style('stroke-width', 1);
}
// add circles
circles = self.addCircles(svg, layers);
// add click and hover events to circles
self.addCircleEvents(circles, svg);
// chart base line
var line = svg.append('line')
.attr('class', 'base-line')
.attr('x1', 0)
.attr('y1', yScale(0))
.attr('x2', width)
.attr('y2', yScale(0))
.style('stroke', '#ddd')
.style('stroke-width', 1);
if (addTimeMarker) {
timeMarker.render(svg);
}
return svg;
});
};
};
return AreaChart;
};
});