UNPKG

kibana-123

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

234 lines (201 loc) 6.93 kB
import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; import errors from 'ui/errors'; import Binder from 'ui/binder'; import VislibLibLayoutLayoutProvider from './layout/layout'; import VislibLibChartTitleProvider from './chart_title'; import VislibLibAlertsProvider from './alerts'; import VislibAxisProvider from './axis/axis'; import VislibVisualizationsVisTypesProvider from '../visualizations/vis_types'; export default function HandlerBaseClass(Private) { const chartTypes = Private(VislibVisualizationsVisTypesProvider); const Layout = Private(VislibLibLayoutLayoutProvider); const ChartTitle = Private(VislibLibChartTitleProvider); const Alerts = Private(VislibLibAlertsProvider); const Axis = Private(VislibAxisProvider); /** * Handles building all the components of the visualization * * @class Handler * @constructor * @param vis {Object} Reference to the Vis Class Constructor * @param opts {Object} Reference to Visualization constructors needed to * create the visualization */ class Handler { constructor(vis, visConfig) { this.el = visConfig.get('el'); this.ChartClass = chartTypes[visConfig.get('type')]; this.charts = []; this.vis = vis; this.visConfig = visConfig; this.data = visConfig.data; this.categoryAxes = visConfig.get('categoryAxes').map(axisArgs => new Axis(visConfig, axisArgs)); this.valueAxes = visConfig.get('valueAxes').map(axisArgs => new Axis(visConfig, axisArgs)); this.chartTitle = new ChartTitle(visConfig); this.alerts = new Alerts(this, visConfig.get('alerts')); if (visConfig.get('type') === 'point_series') { this.data.stackData(this); } if (visConfig.get('resize', false)) { this.resize = visConfig.get('resize'); } this.layout = new Layout(visConfig); this.binder = new Binder(); this.renderArray = _.filter([ this.layout, this.chartTitle, this.alerts ], Boolean); this.renderArray = this.renderArray .concat(this.valueAxes) .concat(this.categoryAxes); // memoize so that the same function is returned every time, // allowing us to remove/re-add the same function this.getProxyHandler = _.memoize(function (event) { const self = this; return function (e) { self.vis.emit(event, e); }; }); /** * Enables events, i.e. binds specific events to the chart * object(s) `on` method. For example, `click` or `mousedown` events. * * @method enable * @param event {String} Event type * @param chart {Object} Chart * @returns {*} */ this.enable = this.chartEventProxyToggle('on'); /** * Disables events for all charts * * @method disable * @param event {String} Event type * @param chart {Object} Chart * @returns {*} */ this.disable = this.chartEventProxyToggle('off'); } /** * Validates whether data is actually present in the data object * used to render the Vis. Throws a no results error if data is not * present. * * @private */ _validateData() { const dataType = this.data.type; if (!dataType) { throw new errors.NoResults(); } }; /** * Renders the constructors that create the visualization, * including the chart constructor * * @method render * @returns {HTMLElement} With the visualization child element */ render() { if (this.visConfig.get('error', null)) return this.error(this.visConfig.get('error')); const self = this; const { binder, charts = [] } = this; const selection = d3.select(this.el); selection.selectAll('*').remove(); this._validateData(); this.renderArray.forEach(function (property) { if (typeof property.render === 'function') { property.render(); } }); // render the chart(s) let loadedCount = 0; const chartSelection = selection.selectAll('.chart'); chartSelection.each(function (chartData) { const chart = new self.ChartClass(self, this, chartData); self.vis.activeEvents().forEach(function (event) { self.enable(event, chart); }); binder.on(chart.events, 'rendered', () => { loadedCount++; if (loadedCount === chartSelection.length) { // events from all charts are propagated to vis, we only need to fire renderComplete once they all finish $(self.el).trigger('renderComplete'); } }); charts.push(chart); chart.render(); }); }; chartEventProxyToggle(method) { return function (event, chart) { const proxyHandler = this.getProxyHandler(event); _.each(chart ? [chart] : this.charts, function (chart) { chart.events[method](event, proxyHandler); }); }; } /** * Removes all DOM elements from the HTML element provided * * @method removeAll * @param el {HTMLElement} Reference to the HTML Element that * contains the chart * @returns {D3.Selection|D3.Transition.Transition} With the chart * child element removed */ removeAll(el) { return d3.select(el).selectAll('*').remove(); }; /** * Displays an error message in the DOM * * @method error * @param message {String} Error message to display * @returns {HTMLElement} Displays the input message */ error(message) { this.removeAll(this.el); const div = d3.select(this.el) .append('div') // class name needs `chart` in it for the polling checkSize function // to continuously call render on resize .attr('class', 'visualize-error chart error'); if (message === 'No results found') { div.append('div') .attr('class', 'text-center visualize-error visualize-chart ng-scope') .append('div').attr('class', 'item top') .append('div').attr('class', 'item') .append('h2').html('<i class="fa fa-meh-o"></i>') .append('h4').text(message); div.append('div').attr('class', 'item bottom'); } else { div.append('h4').text(message); } $(this.el).trigger('renderComplete'); return div; }; /** * Destroys all the charts in the visualization * * @method destroy */ destroy() { this.binder.destroy(); this.renderArray.forEach(function (renderable) { if (_.isFunction(renderable.destroy)) { renderable.destroy(); } }); this.charts.splice(0).forEach(function (chart) { if (_.isFunction(chart.destroy)) { chart.destroy(); } }); }; } return Handler; };