move tile map visualization out of vislib (#9639)

Backports PR #9441

**Commit 1:**
moving colors and tooltips out of vislib to vis

* Original sha: fd1bc5867d
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-13T12:40:08Z

**Commit 2:**
moving tile map vis out of vislib

* Original sha: 126f1de6c3
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-13T12:40:49Z

**Commit 3:**
rebasing on master and fixing linting

* Original sha: c665d4c0d3
* Authored by ppisljar <peter.pisljar@gmail.com> on 2016-12-14T09:51:18Z
This commit is contained in:
jasper 2016-12-26 12:04:54 -05:00 committed by Thomas Neirynck
parent 16522f665e
commit 8e76bd86fb
45 changed files with 919 additions and 123 deletions

View file

@ -1,17 +1,17 @@
import _ from 'lodash';
import supports from 'ui/utils/supports';
import VislibVisTypeVislibVisTypeProvider from 'ui/vislib_vis_type/vislib_vis_type';
import MapsVisTypeVislibVisTypeProvider from 'ui/vis_maps/maps_vis_type';
import VisSchemasProvider from 'ui/vis/schemas';
import AggResponseGeoJsonGeoJsonProvider from 'ui/agg_response/geo_json/geo_json';
import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';
import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html';
export default function TileMapVisType(Private, getAppState, courier, config) {
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
const MapsVisType = Private(MapsVisTypeVislibVisTypeProvider);
const Schemas = Private(VisSchemasProvider);
const geoJsonConverter = Private(AggResponseGeoJsonGeoJsonProvider);
return new VislibVisType({
return new MapsVisType({
name: 'tile_map',
title: 'Tile map',
icon: 'fa-map-marker',

View file

@ -1,6 +1,6 @@
import _ from 'lodash';
import $ from 'jquery';
import VislibComponentsColorColorPaletteProvider from 'ui/vislib/components/color/color_palette';
import VislibComponentsColorColorPaletteProvider from 'ui/vis/components/color/color_palette';
import uiModules from 'ui/modules';
uiModules
.get('kibana')

View file

@ -1,7 +1,7 @@
import d3 from 'd3';
import d3TagCloud from 'd3-cloud';
import vislibComponentsSeedColorsProvider from 'ui/vislib/components/color/seed_colors';
import {EventEmitter} from 'events';
import vislibComponentsSeedColorsProvider from 'ui/vis/components/color/seed_colors';
import { EventEmitter } from 'events';
const ORIENTATIONS = {
@ -226,7 +226,7 @@ class TagCloud extends EventEmitter {
resolveWhenDone();
});
};
}
_makeTextSizeMapper() {
const mapSizeToFontSize = D3_SCALING_FUNCTIONS[this._textScale]();
@ -314,14 +314,14 @@ class TagCloud extends EventEmitter {
}
TagCloud.STATUS = {COMPLETE: 0, INCOMPLETE: 1};
TagCloud.STATUS = { COMPLETE: 0, INCOMPLETE: 1 };
function seed() {
return 0.5;//constant seed (not random) to ensure constant layouts for identical data
}
function toWordTag(word) {
return {value: word.value, text: word.text};
return { value: word.value, text: word.text };
}
@ -364,7 +364,7 @@ function hashCode(string) {
return hash;
}
for (let i = 0; i < string.length; i++) {
let char = string.charCodeAt(i);
const char = string.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}

View file

@ -3,10 +3,10 @@ import expect from 'expect.js';
import ngMock from 'ng_mock';
import _ from 'lodash';
import d3 from 'd3';
import VislibComponentsColorSeedColorsProvider from 'ui/vislib/components/color/seed_colors';
import VislibComponentsColorColorProvider from 'ui/vislib/components/color/color';
import VislibComponentsColorMappedColorsProvider from 'ui/vislib/components/color/mapped_colors';
import VislibComponentsColorColorPaletteProvider from 'ui/vislib/components/color/color_palette';
import VislibComponentsColorSeedColorsProvider from 'ui/vis/components/color/seed_colors';
import VislibComponentsColorColorProvider from 'ui/vis/components/color/color';
import VislibComponentsColorMappedColorsProvider from 'ui/vis/components/color/mapped_colors';
import VislibComponentsColorColorPaletteProvider from 'ui/vis/components/color/color_palette';
describe('Vislib Color Module Test Suite', function () {
let seedColors;

View file

@ -0,0 +1,9 @@
import TooltipProvider from './tooltip';
import ColorProvider from './color';
export default function ComponentsProvider(Private) {
return {
tooltip: Private(TooltipProvider),
colors: Private(ColorProvider)
};
}

View file

@ -1,4 +1,4 @@
import VisSchemasProvider from 'ui/vis/schemas';
import VisSchemasProvider from './schemas';
export default function VisTypeFactory(Private) {
let VisTypeSchemas = Private(VisSchemasProvider);

View file

@ -7,7 +7,7 @@ import L from 'leaflet';
import sinon from 'auto-release-sinon';
import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
import $ from 'jquery';
import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map';
import VislibVisualizationsMapProvider from 'ui/vis_maps/visualizations/_map';
// // Data
// const dataArray = [

View file

@ -7,10 +7,10 @@ import L from 'leaflet';
import sinon from 'auto-release-sinon';
import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
import $ from 'jquery';
import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker';
import VislibVisualizationsMarkerTypesShadedCirclesProvider from 'ui/vislib/visualizations/marker_types/shaded_circles';
import VislibVisualizationsMarkerTypesScaledCirclesProvider from 'ui/vislib/visualizations/marker_types/scaled_circles';
import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vislib/visualizations/marker_types/heatmap';
import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vis_maps/visualizations/marker_types/base_marker';
import VislibVisualizationsMarkerTypesShadedCirclesProvider from 'ui/vis_maps/visualizations/marker_types/shaded_circles';
import VislibVisualizationsMarkerTypesScaledCirclesProvider from 'ui/vis_maps/visualizations/marker_types/scaled_circles';
import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vis_maps/visualizations/marker_types/heatmap';
// defaults to roughly the lower 48 US states
const defaultSWCoords = [13.496, -143.789];
const defaultNECoords = [55.526, -57.919];

View file

@ -6,7 +6,7 @@ import sinon from 'auto-release-sinon';
import geoJsonData from 'fixtures/vislib/mock_data/geohash/_geo_json';
import MockMap from 'fixtures/tilemap_map';
import $ from 'jquery';
import VislibVisualizationsTileMapProvider from 'ui/vislib/visualizations/tile_map';
import VislibVisualizationsTileMapProvider from 'ui/vis_maps/visualizations/tile_map';
const mockChartEl = $('<div>');
let TileMap;
@ -18,6 +18,11 @@ function createTileMap(handler, chartEl, chartData) {
get: function () {
return '';
}
},
uiState: {
get: function () {
return '';
}
}
};
chartEl = chartEl || mockChartEl;
@ -32,7 +37,7 @@ describe('TileMap Tests', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
Private.stub(require('ui/vislib/visualizations/_map'), MockMap);
Private.stub(require('ui/vis_maps/visualizations/_map'), MockMap);
TileMap = Private(VislibVisualizationsTileMapProvider);
extentsStub = sinon.stub(TileMap.prototype, '_appendGeoExtents', _.noop);
}));
@ -55,12 +60,6 @@ describe('TileMap Tests', function () {
it('should return a function', function () {
expect(tilemap.draw()).to.be.a('function');
});
it('should call destroy for clean state', function () {
const destroySpy = sinon.spy(tilemap, 'destroy');
tilemap.draw();
expect(destroySpy.callCount).to.equal(1);
});
});
describe('appendMap', function () {

View file

@ -0,0 +1,200 @@
import d3 from 'd3';
import _ from 'lodash';
export default function DataFactory(Private) {
/**
* Provides an API for pulling values off the data
* and calculating values using the data
*
* @class Data
* @constructor
* @param data {Object} Elasticsearch query results
* @param attr {Object|*} Visualization options
*/
class Data {
constructor(data, uiState) {
this.uiState = uiState;
this.data = this.copyDataObj(data);
this._normalizeOrdered();
}
copyDataObj(data) {
const copyChart = data => {
const newData = {};
Object.keys(data).forEach(key => {
if (key !== 'series') {
newData[key] = data[key];
} else {
newData[key] = data[key].map(seri => {
return {
label: seri.label,
values: seri.values.map(val => {
const newVal = _.clone(val);
newVal.aggConfig = val.aggConfig;
newVal.aggConfigResult = val.aggConfigResult;
newVal.extraMetrics = val.extraMetrics;
return newVal;
})
};
});
}
});
return newData;
};
if (!data.series) {
const newData = {};
Object.keys(data).forEach(key => {
if (!['rows', 'columns'].includes(key)) {
newData[key] = data[key];
}
else {
newData[key] = data[key].map(chart => {
return copyChart(chart);
});
}
});
return newData;
}
return copyChart(data);
}
/**
* Returns an array of the actual x and y data value objects
* from data with series keys
*
* @method chartData
* @returns {*} Array of data objects
*/
chartData() {
if (!this.data.series) {
const arr = this.data.rows ? this.data.rows : this.data.columns;
return _.toArray(arr);
}
return [this.data];
}
/**
* Returns an array of chart data objects
*
* @method getVisData
* @returns {*} Array of chart data objects
*/
getVisData() {
let visData;
if (this.data.rows) {
visData = this.data.rows;
} else if (this.data.columns) {
visData = this.data.columns;
} else {
visData = [this.data];
}
return visData;
}
/**
* get min and max for all cols, rows of data
*
* @method getMaxMin
* @return {Object}
*/
getGeoExtents() {
const visData = this.getVisData();
return _.reduce(_.pluck(visData, 'geoJson.properties'), function (minMax, props) {
return {
min: Math.min(props.min, minMax.min),
max: Math.max(props.max, minMax.max)
};
}, { min: Infinity, max: -Infinity });
}
/**
* Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter`
* pulls the value off the first item in the array
* these values are typically the same between data objects of the same chart
* TODO: May need to verify this or refactor
*
* @method get
* @param thing {String} Data object key
* @returns {*} Data object value
*/
get(thing, def) {
const source = (this.data.rows || this.data.columns || [this.data])[0];
return _.get(source, thing, def);
}
/**
* Return an array of all value objects
* Pluck the data.series array from each data object
* Create an array of all the value objects from the series array
*
* @method flatten
* @returns {Array} Value objects
*/
flatten() {
return _(this.chartData())
.pluck('series')
.flattenDeep()
.pluck('values')
.flattenDeep()
.value();
}
/**
* ensure that the datas ordered property has a min and max
* if the data represents an ordered date range.
*
* @return {undefined}
*/
_normalizeOrdered() {
const data = this.getVisData();
const self = this;
data.forEach(function (d) {
if (!d.ordered || !d.ordered.date) return;
const missingMin = d.ordered.min == null;
const missingMax = d.ordered.max == null;
if (missingMax || missingMin) {
const extent = d3.extent(self.xValues());
if (missingMin) d.ordered.min = extent[0];
if (missingMax) d.ordered.max = extent[1];
}
});
}
/**
* Calculates min and max values for all map data
* series.rows is an array of arrays
* each row is an array of values
* last value in row array is bucket count
*
* @method mapDataExtents
* @param series {Array} Array of data objects
* @returns {Array} min and max values
*/
mapDataExtents(series) {
const values = _.map(series.rows, function (row) {
return row[row.length - 1];
});
return [_.min(values), _.max(values)];
}
/**
* Get the maximum number of series, considering each chart
* individually.
*
* @return {number} - the largest number of series from all charts
*/
maxNumberOfSeries() {
return this.chartData().reduce(function (max, chart) {
return Math.max(max, chart.series.length);
}, 0);
}
}
return Data;
}

View file

@ -0,0 +1,315 @@
import d3 from 'd3';
import _ from 'lodash';
import $ from 'jquery';
import SimpleEmitter from 'ui/utils/simple_emitter';
export default function DispatchClass(Private, config) {
/**
* Handles event responses
*
* @class Dispatch
* @constructor
* @param handler {Object} Reference to Handler Class Object
*/
class Dispatch extends SimpleEmitter {
constructor(handler) {
super();
this.handler = handler;
this._listeners = {};
}
/**
* Response to click and hover events
*
* @param d {Object} Data point
* @param i {Number} Index number of data point
* @returns {{value: *, point: *, label: *, color: *, pointIndex: *,
* series: *, config: *, data: (Object|*),
* e: (d3.event|*), handler: (Object|*)}} Event response object
*/
eventResponse(d, i) {
const datum = d._input || d;
const data = d3.event.target.nearestViewportElement ?
d3.event.target.nearestViewportElement.__data__ : d3.event.target.__data__;
const label = d.label ? d.label : (d.series || 'Count');
const isSeries = !!(data && data.series);
const isSlices = !!(data && data.slices);
const series = isSeries ? data.series : undefined;
const slices = isSlices ? data.slices : undefined;
const handler = this.handler;
const color = _.get(handler, 'data.color');
const isPercentage = (handler && handler.visConfig.get('mode', 'normal') === 'percentage');
const eventData = {
value: d.y,
point: datum,
datum: datum,
label: label,
color: color ? color(label) : undefined,
pointIndex: i,
series: series,
slices: slices,
config: handler && handler.visConfig,
data: data,
e: d3.event,
handler: handler
};
if (isSeries) {
// Find object with the actual d value and add it to the point object
const object = _.find(series, { 'label': label });
if (object) {
eventData.value = +object.values[i].y;
if (isPercentage) {
// Add the formatted percentage to the point object
eventData.percent = (100 * d.y).toFixed(1) + '%';
}
}
}
return eventData;
}
/**
* Returns a function that adds events and listeners to a D3 selection
*
* @method addEvent
* @param event {String}
* @param callback {Function}
* @returns {Function}
*/
addEvent(event, callback) {
return function (selection) {
selection.each(function () {
const element = d3.select(this);
if (typeof callback === 'function') {
return element.on(event, callback);
}
});
};
}
/**
*
* @method addHoverEvent
* @returns {Function}
*/
addHoverEvent() {
const self = this;
const isClickable = this.listenerCount('click') > 0;
const addEvent = this.addEvent;
const $el = this.handler.el;
if (!this.handler.highlight) {
this.handler.highlight = self.highlight;
}
function hover(d, i) {
// Add pointer if item is clickable
if (isClickable) {
self.addMousePointer.call(this, arguments);
}
self.handler.highlight.call(this, $el);
self.emit('hover', self.eventResponse(d, i));
}
return addEvent('mouseover', hover);
}
/**
*
* @method addMouseoutEvent
* @returns {Function}
*/
addMouseoutEvent() {
const self = this;
const addEvent = this.addEvent;
const $el = this.handler.el;
if (!this.handler.unHighlight) {
this.handler.unHighlight = self.unHighlight;
}
function mouseout() {
self.handler.unHighlight.call(this, $el);
}
return addEvent('mouseout', mouseout);
}
/**
*
* @method addClickEvent
* @returns {Function}
*/
addClickEvent() {
const self = this;
const addEvent = this.addEvent;
function click(d, i) {
self.emit('click', self.eventResponse(d, i));
}
return addEvent('click', click);
}
/**
* Determine if we will allow brushing
*
* @method allowBrushing
* @returns {Boolean}
*/
allowBrushing() {
const xAxis = this.handler.categoryAxes[0];
//Allow brushing for ordered axis - date histogram and histogram
return Boolean(xAxis.ordered);
}
/**
* Determine if brushing is currently enabled
*
* @method isBrushable
* @returns {Boolean}
*/
isBrushable() {
return this.allowBrushing() && this.listenerCount('brush') > 0;
}
/**
* Mouseover Behavior
*
* @method addMousePointer
* @returns {d3.Selection}
*/
addMousePointer() {
return d3.select(this).style('cursor', 'pointer');
}
/**
* Highlight the element that is under the cursor
* by reducing the opacity of all the elements on the graph.
* @param element {d3.Selection}
* @method highlight
*/
highlight(element) {
const label = this.getAttribute('data-label');
if (!label) return;
const dimming = config.get('visualization:dimmingOpacity');
$(element).parent().find('[data-label]')
.css('opacity', 1)//Opacity 1 is needed to avoid the css application
.not((els, el) => String($(el).data('label')) === label)
.css('opacity', justifyOpacity(dimming));
}
/**
* Mouseout Behavior
*
* @param element {d3.Selection}
* @method unHighlight
*/
unHighlight(element) {
$('[data-label]', element.parentNode).css('opacity', 1);
}
/**
* Adds D3 brush to SVG and returns the brush function
*
* @param xScale {Function} D3 xScale function
* @param svg {HTMLElement} Reference to SVG
* @returns {*} Returns a D3 brush function and a SVG with a brush group attached
*/
createBrush(xScale, svg) {
const self = this;
const visConfig = self.handler.visConfig;
const { width, height } = svg.node().getBBox();
const isHorizontal = self.handler.categoryAxes[0].axisConfig.isHorizontal();
// Brush scale
const brush = d3.svg.brush();
if (isHorizontal) {
brush.x(xScale);
} else {
brush.y(xScale);
}
brush.on('brushend', function brushEnd() {
// Assumes data is selected at the chart level
// In this case, the number of data objects should always be 1
const data = d3.select(this).data()[0];
const isTimeSeries = (data.ordered && data.ordered.date);
// Allows for brushing on d3.scale.ordinal()
const selected = xScale.domain().filter(function (d) {
return (brush.extent()[0] <= xScale(d)) && (xScale(d) <= brush.extent()[1]);
});
const range = isTimeSeries ? brush.extent() : selected;
return self.emit('brush', {
range: range,
config: visConfig,
e: d3.event,
data: data
});
});
// if `addBrushing` is true, add brush canvas
if (self.listenerCount('brush')) {
const rect = svg.insert('g', 'g')
.attr('class', 'brush')
.call(brush)
.call(function (brushG) {
// hijack the brush start event to filter out right/middle clicks
const brushHandler = brushG.on('mousedown.brush');
if (!brushHandler) return; // touch events in use
brushG.on('mousedown.brush', function () {
if (validBrushClick(d3.event)) brushHandler.apply(this, arguments);
});
})
.selectAll('rect');
if (isHorizontal) {
rect.attr('height', height);
} else {
rect.attr('width', width);
}
return brush;
}
}
}
/**
* Determine if d3.Scale is quantitative
*
* @param element {d3.Scale}
* @method isQuantitativeScale
* @returns {boolean}
*/
function isQuantitativeScale(scale) {
//Invert is a method that only exists on quantitative scales
if (scale.invert) {
return true;
} else {
return false;
}
}
function validBrushClick(event) {
return event.button === 0;
}
function justifyOpacity(opacity) {
const decimalNumber = parseFloat(opacity, 10);
const fallbackOpacity = 0.5;
return (0 <= decimalNumber && decimalNumber <= 1) ? decimalNumber : fallbackOpacity;
}
return Dispatch;
}

View file

@ -0,0 +1,50 @@
import d3 from 'd3';
import MapSplitProvider from './splits/map_split';
export default function LayoutFactory(Private) {
const mapSplit = Private(MapSplitProvider);
class Layout {
constructor(el, config, data) {
this.el = el;
this.config = config;
this.data = data;
}
render() {
this.removeAll();
this.createLayout();
}
createLayout() {
const wrapper = this.appendElem(this.el, 'div', 'vis-wrapper');
wrapper.datum(this.data.data);
const colWrapper = this.appendElem(wrapper.node(), 'div', 'vis-col-wrapper');
const chartWrapper = this.appendElem(colWrapper.node(), 'div', 'chart-wrapper');
chartWrapper.call(mapSplit, colWrapper.node(), this.config);
}
appendElem(el, type, className) {
if (!el || !type || !className) {
throw new Error('Function requires that an el, type, and class be provided');
}
if (typeof el === 'string') {
// Create a DOM reference with a d3 selection
// Need to make sure that the `el` is bound to this object
// to prevent it from being appended to another Layout
el = d3.select(this.el)
.select(el)[0][0];
}
return d3.select(el)
.append(type)
.attr('class', className);
}
removeAll() {
return d3.select(this.el).selectAll('*').remove();
}
}
return Layout;
}

View file

@ -0,0 +1,38 @@
/**
* Provides vislib configuration, throws error if invalid property is accessed without providing defaults
*/
import _ from 'lodash';
export default function MapsConfigFactory(Private) {
const DEFAULT_VIS_CONFIG = {
style: {
margin : { top: 10, right: 3, bottom: 5, left: 3 }
},
alerts: {},
categoryAxes: [],
valueAxes: []
};
class MapsConfig {
constructor(mapsConfigArgs) {
this._values = _.defaultsDeep({}, mapsConfigArgs, DEFAULT_VIS_CONFIG);
}
get(property, defaults) {
if (_.has(this._values, property) || typeof defaults !== 'undefined') {
return _.get(this._values, property, defaults);
} else {
throw new Error(`Accessing invalid config property: ${property}`);
return defaults;
}
}
set(property, value) {
return _.set(this._values, property, value);
}
}
return MapsConfig;
}

View file

@ -0,0 +1,106 @@
import _ from 'lodash';
import d3 from 'd3';
import MapsConfigProvider from './lib/maps_config';
import TileMapChartProvider from './visualizations/tile_map';
import EventsProvider from 'ui/events';
import MapsDataProvider from './lib/data';
import LayoutProvider from './lib/layout';
import './styles/_tilemap.less';
export default function MapsFactory(Private) {
const Events = Private(EventsProvider);
const MapsConfig = Private(MapsConfigProvider);
const TileMapChart = Private(TileMapChartProvider);
const Data = Private(MapsDataProvider);
const Layout = Private(LayoutProvider);
class Maps extends Events {
constructor($el, vis, mapsConfigArgs) {
super(arguments);
this.el = $el.get ? $el.get(0) : $el;
this.vis = vis;
this.mapsConfigArgs = mapsConfigArgs;
// 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.emit(event, e);
};
});
this.enable = this.chartEventProxyToggle('on');
this.disable = this.chartEventProxyToggle('off');
}
chartEventProxyToggle(method) {
return function (event, chart) {
const proxyHandler = this.getProxyHandler(event);
_.each(chart ? [chart] : this.charts, function (chart) {
chart.events[method](event, proxyHandler);
});
};
}
on(event, listener) {
const first = this.listenerCount(event) === 0;
const ret = Events.prototype.on.call(this, event, listener);
const added = this.listenerCount(event) > 0;
// if this is the first listener added for the event
// enable the event in the handler
if (first && added && this.handler) this.handler.enable(event);
return ret;
}
off(event, listener) {
const last = this.listenerCount(event) === 1;
const ret = Events.prototype.off.call(this, event, listener);
const removed = this.listenerCount(event) === 0;
// Once all listeners are removed, disable the events in the handler
if (last && removed && this.handler) this.handler.disable(event);
return ret;
}
render(data, uiState) {
if (!data) {
throw new Error('No valid data!');
}
this.uiState = uiState;
this.data = new Data(data, this.uiState);
this.visConfig = new MapsConfig(this.mapsConfigArgs, this.data, this.uiState);
this.layout = new Layout(this.el, this.visConfig, this.data);
this.draw();
}
destroy() {
this.charts.forEach(chart => chart.destroy());
d3.select(this.el).selectAll('*').remove();
}
draw() {
this.layout.render();
// todo: title
const self = this;
this.charts = [];
d3.select(this.el).selectAll('.chart').each(function (chartData) {
const chart = new TileMapChart(self, this, chartData);
self.activeEvents().forEach(function (event) {
self.enable(event, chart);
});
self.charts.push(chart);
chart.render();
});
}
}
return Maps;
}

View file

@ -0,0 +1,77 @@
import _ from 'lodash';
import MapsProvider from 'ui/vis_maps/maps';
import VisRenderbotProvider from 'ui/vis/renderbot';
import MapsVisTypeBuildChartDataProvider from 'ui/vislib_vis_type/build_chart_data';
module.exports = function MapsRenderbotFactory(Private, $injector) {
const AngularPromise = $injector.get('Promise');
const Maps = Private(MapsProvider);
const Renderbot = Private(VisRenderbotProvider);
const buildChartData = Private(MapsVisTypeBuildChartDataProvider);
_.class(MapsRenderbot).inherits(Renderbot);
function MapsRenderbot(vis, $el, uiState) {
MapsRenderbot.Super.call(this, vis, $el, uiState);
this._createVis();
}
MapsRenderbot.prototype._createVis = function () {
if (this.mapsVis) this.destroy();
this.mapsParams = this._getMapsParams();
this.mapsVis = new Maps(this.$el[0], this.vis, this.mapsParams);
_.each(this.vis.listeners, (listener, event) => {
this.mapsVis.on(event, listener);
});
if (this.mapsData) {
this.mapsVis.render(this.mapsData, this.uiState);
}
};
MapsRenderbot.prototype._getMapsParams = function () {
const self = this;
return _.assign(
{},
self.vis.type.params.defaults,
{
type: self.vis.type.name,
// Add attribute which determines whether an index is time based or not.
hasTimeField: self.vis.indexPattern && self.vis.indexPattern.hasTimeField()
},
self.vis.params
);
};
MapsRenderbot.prototype.buildChartData = buildChartData;
MapsRenderbot.prototype.render = function (esResponse) {
this.mapsData = this.buildChartData(esResponse);
return AngularPromise.delay(1).then(() => {
this.mapsVis.render(this.mapsData, this.uiState);
});
};
MapsRenderbot.prototype.destroy = function () {
const self = this;
const mapsVis = self.mapsVis;
_.forOwn(self.vis.listeners, function (listener, event) {
mapsVis.off(event, listener);
});
mapsVis.destroy();
};
MapsRenderbot.prototype.updateParams = function () {
const self = this;
// get full maps params object
const newParams = self._getMapsParams();
// if there's been a change, replace the vis
if (!_.isEqual(newParams, self.mapsParams)) self._createVis();
};
return MapsRenderbot;
};

View file

@ -0,0 +1,22 @@
import _ from 'lodash';
import 'ui/vislib';
import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options';
import VisVisTypeProvider from 'ui/vis/vis_type';
import MapsVisTypeMapsRenderbotProvider from 'ui/vis_maps/maps_renderbot';
export default function MapsVisTypeFactory(Private) {
const VisType = Private(VisVisTypeProvider);
const MapsRenderbot = Private(MapsVisTypeMapsRenderbotProvider);
_.class(MapsVisType).inherits(VisType);
function MapsVisType(opts = {}) {
MapsVisType.Super.call(this, opts);
this.listeners = opts.listeners || {};
}
MapsVisType.prototype.createRenderbot = function (vis, $el, uiState) {
return new MapsRenderbot(vis, $el, uiState);
};
return MapsVisType;
}

View file

@ -0,0 +1,60 @@
import d3 from 'd3';
import _ from 'lodash';
import VislibLibDispatchProvider from '../lib/dispatch';
import TooltipProvider from 'ui/vis/components/tooltip';
export default function ChartBaseClass(Private) {
const Dispatch = Private(VislibLibDispatchProvider);
const Tooltip = Private(TooltipProvider);
/**
* The Base Class for all visualizations.
*
* @class Chart
* @constructor
* @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 Chart {
constructor(handler, el, chartData) {
this.handler = handler;
this.chartEl = el;
this.chartData = chartData;
this.tooltips = [];
const events = this.events = new Dispatch(handler);
if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) {
const $el = this.handler.el;
const formatter = this.handler.data.get('tooltipFormatter');
// Add tooltip
this.tooltip = new Tooltip('chart', $el, formatter, events);
this.tooltips.push(this.tooltip);
}
}
render() {
const selection = d3.select(this.chartEl);
selection.selectAll('*').remove();
selection.call(this.draw());
}
/**
* Removes all DOM elements from the root element
*
* @method destroy
*/
destroy() {
const selection = d3.select(this.chartEl);
this.events.removeAllListeners();
this.tooltips.forEach(function (tooltip) {
tooltip.destroy();
});
selection.remove();
}
}
return Chart;
}

View file

@ -7,10 +7,10 @@ marked.setOptions({
sanitize: true // Sanitize HTML tags
});
import VislibVisualizationsMarkerTypesScaledCirclesProvider from 'ui/vislib/visualizations/marker_types/scaled_circles';
import VislibVisualizationsMarkerTypesShadedCirclesProvider from 'ui/vislib/visualizations/marker_types/shaded_circles';
import VislibVisualizationsMarkerTypesGeohashGridProvider from 'ui/vislib/visualizations/marker_types/geohash_grid';
import VislibVisualizationsMarkerTypesHeatmapProvider from 'ui/vislib/visualizations/marker_types/heatmap';
import VislibVisualizationsMarkerTypesScaledCirclesProvider from './marker_types/scaled_circles';
import VislibVisualizationsMarkerTypesShadedCirclesProvider from './marker_types/shaded_circles';
import VislibVisualizationsMarkerTypesGeohashGridProvider from './marker_types/geohash_grid';
import VislibVisualizationsMarkerTypesHeatmapProvider from './marker_types/heatmap';
export default function MapFactory(Private, tilemap, $sanitize) {
const defaultMapZoom = 2;

View file

@ -39,9 +39,6 @@ export default function TileMapFactory(Private) {
draw() {
const self = this;
// clean up old maps
self.destroy();
return function (selection) {
selection.each(function () {
self._appendMap(this);
@ -95,10 +92,10 @@ export default function TileMapFactory(Private) {
*/
_appendMap(selection) {
const container = $(selection).addClass('tilemap');
const uiStateParams = this.handler.vis ? {
mapCenter: this.handler.vis.uiState.get('mapCenter'),
mapZoom: this.handler.vis.uiState.get('mapZoom')
} : {};
const uiStateParams = {
mapCenter: this.handler.uiState.get('mapCenter'),
mapZoom: this.handler.uiState.get('mapZoom')
};
const params = _.assign({}, _.get(this._chartData, 'geoAgg.vis.params'), uiStateParams);

View file

@ -1,7 +1,8 @@
import d3 from 'd3';
import _ from 'lodash';
import ErrorHandlerProvider from './_error_handler';
import TooltipProvider from '../components/tooltip';
import TooltipProvider from 'ui/vis/components/tooltip';
export default function ChartTitleFactory(Private) {
const ErrorHandler = Private(ErrorHandlerProvider);
const Tooltip = Private(TooltipProvider);

View file

@ -3,7 +3,7 @@ import _ from 'lodash';
import VislibComponentsZeroInjectionInjectZerosProvider from '../components/zero_injection/inject_zeros';
import VislibComponentsZeroInjectionOrderedXKeysProvider from '../components/zero_injection/ordered_x_keys';
import VislibComponentsLabelsLabelsProvider from '../components/labels/labels';
import VislibComponentsColorColorProvider from '../components/color/color';
import VislibComponentsColorColorProvider from 'ui/vis/components/color/color';
export default function DataFactory(Private) {
const injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider);

View file

@ -1,6 +1,5 @@
import VislibLibLayoutTypesColumnLayoutProvider from './types/column_layout';
import VislibLibLayoutTypesPieLayoutProvider from './types/pie_layout';
import VislibLibLayoutTypesMapLayoutProvider from './types/map_layout';
export default function LayoutTypeFactory(Private) {
@ -14,7 +13,6 @@ export default function LayoutTypeFactory(Private) {
*/
return {
pie: Private(VislibLibLayoutTypesPieLayoutProvider),
tile_map: Private(VislibLibLayoutTypesMapLayoutProvider),
point_series: Private(VislibLibLayoutTypesColumnLayoutProvider)
};
};

View file

@ -1,49 +0,0 @@
import VislibLibLayoutSplitsTileMapMapSplitProvider from '../splits/tile_map/map_split';
export default function ColumnLayoutFactory(Private) {
const mapSplit = Private(VislibLibLayoutSplitsTileMapMapSplitProvider);
/*
* Specifies the visualization layout for tile maps.
*
* This is done using an array of objects. The first object has
* a `parent` DOM element, a DOM `type` (e.g. div, svg, etc),
* and a `class` (required). Each child can omit the parent object,
* but must include a type and class.
*
* Optionally, you can specify `datum` to be bound to the DOM
* element, a `splits` function that divides the selected element
* into more DOM elements based on a callback function provided, or
* a children array which nests other layout objects.
*
* Objects in children arrays are children of the current object and return
* DOM elements which are children of their respective parent element.
*/
return function (el, data) {
if (!el || !data) {
throw new Error('Both an el and data need to be specified');
}
return [
{
parent: el,
type: 'div',
class: 'vis-wrapper',
datum: data,
children: [
{
type: 'div',
class: 'vis-col-wrapper',
children: [
{
type: 'div',
class: 'chart-wrapper',
splits: mapSplit
}
]
}
]
}
];
};
};

View file

@ -1,6 +1,5 @@
import VislibLibTypesPointSeriesProvider from './point_series';
import VislibLibTypesPieProvider from './pie';
import VislibLibTypesTileMapProvider from './tile_map';
export default function TypeFactory(Private) {
const pointSeries = Private(VislibLibTypesPointSeriesProvider);
@ -15,7 +14,6 @@ export default function TypeFactory(Private) {
line: pointSeries.line,
pie: Private(VislibLibTypesPieProvider),
area: pointSeries.area,
tile_map: Private(VislibLibTypesTileMapProvider),
point_series: pointSeries.line
};
};

View file

@ -1,19 +0,0 @@
import _ from 'lodash';
export default function MapHandlerProvider(Private) {
return function (config) {
if (!config.chart) {
config.chart = _.defaults({}, config, {
type: 'tile_map'
});
}
config.resize = function () {
this.charts.forEach(function (chart) {
chart.resizeArea();
});
};
return config;
};
};

View file

@ -5,5 +5,4 @@
@import "./_legend";
@import "./_svg";
@import "./_tooltip";
@import "./_tilemap";
@import "./_alerts";

View file

@ -1,10 +1,8 @@
import './lib/types/pie';
import './lib/types/point_series';
import './lib/types/tile_map';
import './lib/types';
import './lib/layout/layout_types';
import './lib/data';
import './visualizations/_map.js';
import './visualizations/vis_types';
import './styles/main.less';
import VislibVisProvider from './vis';

View file

@ -2,12 +2,11 @@ import d3 from 'd3';
import _ from 'lodash';
import dataLabel from 'ui/vislib/lib/_data_label';
import VislibLibDispatchProvider from '../lib/dispatch';
import VislibComponentsTooltipProvider from '../components/tooltip';
import TooltipProvider from 'ui/vis/components/tooltip';
export default function ChartBaseClass(Private) {
const Dispatch = Private(VislibLibDispatchProvider);
const Tooltip = Private(VislibComponentsTooltipProvider);
const Tooltip = Private(TooltipProvider);
/**
* The Base Class for all visualizations.
*

View file

@ -2,7 +2,7 @@ import d3 from 'd3';
import _ from 'lodash';
import $ from 'jquery';
import errors from 'ui/errors';
import TooltipProvider from '../components/tooltip';
import TooltipProvider from 'ui/vis/components/tooltip';
import VislibVisualizationsChartProvider from './_chart';
import VislibVisualizationsTimeMarkerProvider from './time_marker';
import VislibVisualizationsSeriTypesProvider from './point_series/series_types';

View file

@ -1,6 +1,5 @@
import VislibVisualizationsPointSeriesProvider from './point_series';
import VislibVisualizationsPieChartProvider from './pie_chart';
import VislibVisualizationsTileMapProvider from './tile_map';
export default function VisTypeFactory(Private) {
@ -14,7 +13,6 @@ export default function VisTypeFactory(Private) {
*/
return {
pie: Private(VislibVisualizationsPieChartProvider),
tile_map: Private(VislibVisualizationsTileMapProvider),
point_series: Private(VislibVisualizationsPointSeriesProvider)
};
};

View file

@ -1,7 +1,7 @@
import _ from 'lodash';
import html from 'ui/visualize/visualize_legend.html';
import VislibLibDataProvider from 'ui/vislib/lib/data';
import VislibComponentsColorColorProvider from 'ui/vislib/components/color/color';
import VislibComponentsColorColorProvider from 'ui/vis/components/color/color';
import FilterBarFilterBarClickHandlerProvider from 'ui/filter_bar/filter_bar_click_handler';
import uiModules from 'ui/modules';