kibana-riya
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
216 lines (183 loc) • 6.3 kB
JavaScript
import d3 from 'd3';
import _ from 'lodash';
import VislibVisualizationsPointSeriesProvider from './_point_series';
export default function LineChartFactory(Private) {
const PointSeries = Private(VislibVisualizationsPointSeriesProvider);
const defaults = {
mode: 'normal',
showCircles: true,
radiusRatio: 9,
showLines: true,
interpolate: 'linear',
color: undefined,
fillColor: undefined
};
/**
* 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 extends PointSeries {
constructor(handler, chartEl, chartData, seriesConfigArgs) {
super(handler, chartEl, chartData, seriesConfigArgs);
this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults);
}
addCircles(svg, data) {
const self = this;
const showCircles = this.seriesConfig.showCircles;
const color = this.handler.data.getColorFunc();
const xScale = this.getCategoryAxis().getScale();
const yScale = this.getValueAxis().getScale();
const ordered = this.handler.data.get('ordered');
const tooltip = this.baseChart.tooltip;
const isTooltip = this.handler.visConfig.get('tooltip.show');
const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal();
const radii = this.baseChart.radii;
const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this.seriesConfig.radiusRatio, 2);
const layer = svg.append('g')
.attr('class', 'points line')
.attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')');
const circles = layer
.selectAll('circle')
.data(function appendData() {
return data.values.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.series);
}
function colorCircle(d) {
const parent = d3.select(this).node().parentNode;
const lengthOfParent = d3.select(parent).data()[0].length;
const 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) {
const margin = self.handler.visConfig.get('style.margin');
const width = self.baseChart.chartConfig.width;
const height = self.baseChart.chartConfig.height;
const circleRadius = (d.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.seriesConfig.drawLinesBetweenPoints ? 1 : 0.7))
.attr('cx', isHorizontal ? cx : cy)
.attr('cy', isHorizontal ? cy : cx)
.attr('class', 'circle-decoration')
.attr('data-label', data.label)
.attr('fill', colorCircle);
circles
.enter()
.append('circle')
.attr('r', getCircleRadiusFn(10))
.attr('cx', isHorizontal ? cx : cy)
.attr('cy', isHorizontal ? cy : cx)
.attr('fill', 'transparent')
.attr('class', 'circle')
.attr('data-label', data.label)
.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
*/
addLine(svg, data) {
const xScale = this.getCategoryAxis().getScale();
const yScale = this.getValueAxis().getScale();
const xAxisFormatter = this.handler.data.get('xAxisFormatter');
const color = this.handler.data.getColorFunc();
const ordered = this.handler.data.get('ordered');
const interpolate = this.seriesConfig.interpolate;
const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal();
const line = svg.append('g')
.attr('class', 'pathgroup lines')
.attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')');
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);
}
line.append('path')
.attr('data-label', data.label)
.attr('d', () => {
const d3Line = d3.svg.line()
.defined(function (d) {
return !_.isNull(d.y);
})
.interpolate(interpolate)
.x(isHorizontal ? cx : cy)
.y(isHorizontal ? cy : cx);
return d3Line(data.values);
})
.attr('fill', 'none')
.attr('stroke', () => {
return color(data.label);
})
.attr('stroke-width', 2);
return line;
};
/**
* Renders d3 visualization
*
* @method draw
* @returns {Function} Creates the line chart
*/
draw() {
const self = this;
return function (selection) {
selection.each(function () {
const svg = self.chartEl.append('g');
svg.data([self.chartData]);
if (self.seriesConfig.drawLinesBetweenPoints) {
self.addLine(svg, self.chartData);
}
const circles = self.addCircles(svg, self.chartData);
self.addCircleEvents(circles);
self.events.emit('rendered', {
chart: self.chartData
});
return svg;
});
};
};
}
return LineChart;
};