UNPKG

@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

358 lines (313 loc) 10.7 kB
define(function (require) { return function LineChartFactory(Private) { var d3 = require('d3'); var _ = require('lodash'); var $ = require('jquery'); var errors = require('ui/errors'); var PointSeriesChart = Private(require('ui/vislib/visualizations/_point_series_chart')); var TimeMarker = Private(require('ui/vislib/visualizations/time_marker')); /** * Line Chart Visualization * * @class LineChart * @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(LineChart).inherits(PointSeriesChart); function LineChart(handler, chartEl, chartData) { if (!(this instanceof LineChart)) { return new LineChart(handler, chartEl, chartData); } LineChart.Super.apply(this, arguments); // Line chart specific attributes this._attr = _.defaults(handler._attr || {}, { interpolate: 'linear', xValue: function (d) { return d.x; }, yValue: function (d) { return d.y; } }); } /** * Adds Events to SVG circle * * @method addCircleEvents * @param element{D3.UpdateSelection} Reference to SVG circle * @returns {D3.Selection} SVG circles with event listeners attached */ LineChart.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 circles to SVG * * @method addCircles * @param svg {HTMLElement} SVG to which rect are appended * @param data {Array} Array of object data points * @returns {D3.UpdateSelection} SVG with circles added */ LineChart.prototype.addCircles = function (svg, data) { var self = this; var showCircles = this._attr.showCircles; 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 tooltip = this.tooltip; var isTooltip = this._attr.addTooltip; var radii = _(data) .map(function (series) { return _.pluck(series, '_input.z'); }) .flattenDeep() .reduce(function (result, val) { if (result.min > val) result.min = val; if (result.max < val) result.max = val; return result; }, { min: Infinity, max: -Infinity }); var radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this._attr.radiusRatio, 2); var layer = svg.selectAll('.points') .data(data) .enter() .append('g') .attr('class', 'points line'); var circles = layer .selectAll('circle') .data(function appendData(data) { return data.filter(function (d) { return !_.isNull(d.y); }); }); circles .exit() .remove(); function cx(d) { if (ordered && ordered.date) { return xScale(d.x); } return xScale(d.x) + xScale.rangeBand() / 2; } function cy(d) { return yScale(d.y); } function cColor(d) { return color(d.label); } function colorCircle(d) { var parent = d3.select(this).node().parentNode; var lengthOfParent = d3.select(parent).data()[0].length; var isVisible = (lengthOfParent === 1); // If only 1 point exists, show circle if (!showCircles && !isVisible) return 'none'; return cColor(d); } function getCircleRadiusFn(modifier) { return function getCircleRadius(d) { var margin = self._attr.margin; var width = self._attr.width - margin.left - margin.right; var height = self._attr.height - margin.top - margin.bottom; var circleRadius = (d._input.z - radii.min) / radiusStep; return _.min([Math.sqrt((circleRadius || 2) + 2), width, height]) + (modifier || 0); }; } circles .enter() .append('circle') .attr('r', getCircleRadiusFn()) .attr('fill-opacity', (this._attr.drawLinesBetweenPoints ? 1 : 0.7)) .attr('cx', cx) .attr('cy', cy) .attr('class', 'circle-decoration') .call(this._addIdentifier) .attr('fill', colorCircle); circles .enter() .append('circle') .attr('r', getCircleRadiusFn(10)) .attr('cx', cx) .attr('cy', cy) .attr('fill', 'transparent') .attr('class', 'circle') .call(this._addIdentifier) .attr('stroke', cColor) .attr('stroke-width', 0); if (isTooltip) { circles.call(tooltip.render()); } return circles; }; /** * Adds path to SVG * * @method addLines * @param svg {HTMLElement} SVG to which path are appended * @param data {Array} Array of object data points * @returns {D3.UpdateSelection} SVG with paths added */ LineChart.prototype.addLines = function (svg, data) { var self = this; var xScale = this.handler.xAxis.xScale; var yScale = this.handler.yAxis.yScale; var xAxisFormatter = this.handler.data.get('xAxisFormatter'); var color = this.handler.data.getColorFunc(); var ordered = this.handler.data.get('ordered'); var interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; var line = d3.svg.line() .defined(function (d) { return !_.isNull(d.y); }) .interpolate(interpolate) .x(function x(d) { if (ordered && ordered.date) { return xScale(d.x); } return xScale(d.x) + xScale.rangeBand() / 2; }) .y(function y(d) { return yScale(d.y); }); var lines; lines = svg .selectAll('.lines') .data(data) .enter() .append('g') .attr('class', 'pathgroup lines'); lines.append('path') .call(this._addIdentifier) .attr('d', function lineD(d) { return line(d.values); }) .attr('fill', 'none') .attr('stroke', function lineStroke(d) { return color(d.label); }) .attr('stroke-width', 2); return lines; }; /** * 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 */ LineChart.prototype.addClipPath = function (svg, width, height) { var clipPathBuffer = 5; var startX = 0; var startY = 0 - clipPathBuffer; var id = 'chart-area' + _.uniqueId(); return svg .attr('clip-path', 'url(#' + id + ')') .append('clipPath') .attr('id', id) .append('rect') .attr('x', startX) .attr('y', startY) .attr('width', width) // Adding clipPathBuffer to height so it doesn't // cutoff the lower part of the chart .attr('height', height + clipPathBuffer); }; /** * Renders d3 visualization * * @method draw * @returns {Function} Creates the line chart */ LineChart.prototype.draw = function () { var self = this; var $elem = $(this.chartEl); var margin = this._attr.margin; var elWidth = this._attr.width = $elem.width(); var elHeight = this._attr.height = $elem.height(); var scaleType = this.handler.yAxis.getScaleType(); var yMin = this.handler.yAxis.yMin; var yScale = this.handler.yAxis.yScale; var xScale = this.handler.xAxis.xScale; var minWidth = 20; var minHeight = 20; var startLineX = 0; var lineStrokeWidth = 1; var addTimeMarker = this._attr.addTimeMarker; var times = this._attr.times || []; var timeMarker; var div; var svg; var width; var height; var lines; var circles; return function (selection) { selection.each(function (data) { var el = this; var layers = data.series.map(function mapSeries(d) { var label = d.label; return d.values.map(function mapValues(e, i) { return { _input: e, label: label, x: self._attr.xValue.call(d.values, e, i), y: self._attr.yValue.call(d.values, e, i) }; }); }); width = elWidth - margin.left - margin.right; height = elHeight - margin.top - margin.bottom; if (addTimeMarker) { timeMarker = new TimeMarker(times, xScale, height); } if (self._attr.scale === 'log' && self._invalidLogScaleValues(data)) { throw new errors.InvalidLogScaleValues(); } if (width < minWidth || height < minHeight) { throw new errors.ContainerTooSmall(); } div = d3.select(el); svg = div.append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom) .append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); self.addClipPath(svg, width, height); if (self._attr.drawLinesBetweenPoints) { lines = self.addLines(svg, data.series); } circles = self.addCircles(svg, layers); self.addCircleEvents(circles, svg); self.createEndZones(svg); var scale = (scaleType === 'log') ? yScale(1) : yScale(0); if (scale) { svg.append('line') .attr('class', 'base-line') .attr('x1', startLineX) .attr('y1', scale) .attr('x2', width) .attr('y2', scale) .style('stroke', '#ddd') .style('stroke-width', lineStrokeWidth); } if (addTimeMarker) { timeMarker.render(svg); } return svg; }); }; }; return LineChart; }; });