Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Region map visualization #10937

Merged
merged 57 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1107f0c
add vector map
thomasneirynck Mar 29, 2017
5e9e79a
do not use light colors in the vector map
thomasneirynck Apr 6, 2017
4544daa
rebase and add vector map to map category
thomasneirynck Apr 10, 2017
b6de16e
show warning when not all data can be joined (with optional advanced …
thomasneirynck Apr 10, 2017
9b1a943
some cleanup
thomasneirynck Apr 10, 2017
f6f966d
feedback
thomasneirynck Apr 11, 2017
a0c88fb
name value
thomasneirynck Apr 11, 2017
b392eb4
fix tooltip
thomasneirynck Apr 11, 2017
58ba775
fix bucket labels
thomasneirynck Apr 11, 2017
8c56bbc
move up in closure
thomasneirynck Apr 11, 2017
457cfcc
remove boilerplate
thomasneirynck Apr 11, 2017
3bbcf46
check in test stub
thomasneirynck Apr 11, 2017
966b641
add selenium tests
thomasneirynck Apr 12, 2017
b9572a5
remove log
thomasneirynck Apr 12, 2017
7524634
remove cruft
thomasneirynck Apr 14, 2017
0446b84
fix faulty merge after rebase
thomasneirynck Apr 14, 2017
34801b8
simplify geojson files, use natural earth data
thomasneirynck Apr 25, 2017
9659efc
fix imports after rebase
thomasneirynck Apr 26, 2017
d010705
remove default services/add boilerplate
thomasneirynck Apr 26, 2017
45737ba
stub-in retrieval of vector map metadata
thomasneirynck Apr 26, 2017
c7b4e91
use manifest
thomasneirynck Apr 27, 2017
d897daf
test new manifest structure on vector map
thomasneirynck May 3, 2017
c527dec
rename map types
thomasneirynck May 5, 2017
4b97689
update to use new production urls
thomasneirynck May 12, 2017
3569b39
rename to region_map
thomasneirynck May 16, 2017
424a507
use relative paths
thomasneirynck May 16, 2017
f7b54c3
incorporate feedback (1)
thomasneirynck May 17, 2017
94c2e54
start stripping old manifest loading from codebase
thomasneirynck May 23, 2017
edda3d6
fix typo
thomasneirynck May 23, 2017
92b674e
remove obsolete tilemap-settings file
thomasneirynck May 23, 2017
3cac581
remove obsolete tilemap-settings file
thomasneirynck May 23, 2017
1885256
remove obsolete tilemap-settings file
thomasneirynck May 23, 2017
b36f895
mixin query parameters for each request
thomasneirynck May 23, 2017
7915fc6
prevent same layer from being added twice
thomasneirynck May 23, 2017
ecbee2b
review feedback
thomasneirynck May 23, 2017
9010b42
fill in combo-box
thomasneirynck May 23, 2017
2cfabf5
fix error message
thomasneirynck May 23, 2017
ae4bcc2
fix error messages
thomasneirynck May 23, 2017
fbddb91
cleanup
thomasneirynck May 23, 2017
333c2a7
add new icon
thomasneirynck May 25, 2017
7cd8065
fix .eslintignore file
thomasneirynck May 25, 2017
dd91337
fix kibana_map test
thomasneirynck May 25, 2017
6bdf4d5
move old tilemaptests to new service_test module
thomasneirynck May 25, 2017
c3045b1
move old tilemaptests to new service_test module
thomasneirynck May 25, 2017
4efb34f
add vector layer test
thomasneirynck May 25, 2017
16c3bf2
fix rebase
thomasneirynck May 25, 2017
a45b6b7
redo selenium test after rebase
thomasneirynck May 26, 2017
91e2ffb
move loading of vector-layers to directive/disable play button on upd…
thomasneirynck May 31, 2017
c739884
add sibling aggregations
thomasneirynck May 31, 2017
5c75704
fix rename
thomasneirynck May 31, 2017
91c5ac2
remove log statement
thomasneirynck May 31, 2017
f462397
remove unnecessary files
thomasneirynck May 31, 2017
a4c3a5c
minor feedback
thomasneirynck May 31, 2017
ab4fe96
improve naming/remove obsolete configs
thomasneirynck May 31, 2017
fa93788
remove unused setting
thomasneirynck May 31, 2017
5122ce1
should respect overridden urls
thomasneirynck May 31, 2017
59cf9df
remove test cruft
thomasneirynck May 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/getting-started/tutorial-visualizing.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ make the differences stand out, starting the Y-axis at a value closer to the min

Save this chart with the name _Bar Example_.

Next, we're going to use a tile map chart to visualize geographic information in our log file sample data.
Next, we're going to use a coordinate map chart to visualize geographic information in our log file sample data.

. Click *New*.
. Select *Tile map*.
. Select *Coordinate map*.
. Select the `logstash-*` index pattern.
. Set the time window for the events we're exploring:
. Click the time picker in the Kibana toolbar.
Expand Down
2 changes: 1 addition & 1 deletion docs/management/advanced-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ increase request processing time.
when necessary.
`visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high,
12 is the maximum. {es-ref}search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[Explanation of cell dimensions].
`visualization:tileMap:WMSdefaults`:: Default properties for the WMS map server support in the tile map.
`visualization:tileMap:WMSdefaults`:: Default properties for the WMS map server support in the coordinate map.
`visualization:colorMapping`:: Maps values to specified colors within visualizations.
`visualization:loadingDelay`:: Time to wait before dimming visualizations during query.
`visualization:dimmingOpacity`:: When part of a visualization is highlighted, by hovering over it for example, ths is the opacity applied to the other elements. A higher number means other elements will be less opaque.
Expand Down
2 changes: 1 addition & 1 deletion docs/visualize.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ To create a visualization:
<<metric-chart,Metric>>:: Display a single number.
* *Maps*
[horizontal]
<<tilemap,Tile map>>:: Associate the results of an aggregation with geographic
<<tilemap,Coordinate map>>:: Associate the results of an aggregation with geographic
locations.
* *Time Series*
[horizontal]
Expand Down
4 changes: 2 additions & 2 deletions docs/visualize/tilemap.asciidoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[[tilemap]]
== Tile Maps

A tile map displays a geographic area overlaid with circles keyed to the data determined by the buckets you specify.
A coordinate map displays a geographic area overlaid with circles keyed to the data determined by the buckets you specify.

NOTE: By default, Kibana uses the https://www.elastic.co/elastic-tile-service[Elastic Tile Service]
to display map tiles. To use other tile service providers, configure the <<tilemap-settings,tilemap settings>>
Expand All @@ -13,7 +13,7 @@ in `kibana.yml`.

===== Metrics

The default _metrics_ aggregation for a tile map is the *Count* aggregation. You can select any of the following
The default _metrics_ aggregation for a coordinate map is the *Count* aggregation. You can select any of the following
aggregations as the metrics aggregation:

*Count*:: The {es-ref}search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of
Expand Down
Empty file removed optimize/.empty
Empty file.
2 changes: 1 addition & 1 deletion src/core_plugins/kbn_vislib_vis_types/public/tile_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function TileMapVisType(Private, getAppState, courier, config) {

return new MapsVisType({
name: 'tile_map',
title: 'Tile Map',
title: 'Coordinate Map',
image,
description: 'Plot latitude and longitude coordinates on a map',
category: VisType.CATEGORY.MAP,
Expand Down
12 changes: 9 additions & 3 deletions src/core_plugins/kibana/inject_vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ export function injectVars(server) {
const configuredUrl = server.config().get('tilemap.url');
const isOverridden = typeof configuredUrl === 'string' && configuredUrl !== '';
const tilemapConfig = serverConfig.get('tilemap');
const regionmapsConfig = serverConfig.get('regionmap');
const mapConfig = serverConfig.get('map');


regionmapsConfig.layers = (regionmapsConfig.layers) ? regionmapsConfig.layers : [];

return {
kbnDefaultAppId: serverConfig.get('kibana.defaultAppId'),
regionmapsConfig: regionmapsConfig,
mapConfig: mapConfig,
tilemapsConfig: {
deprecated: {
isOverridden,
isOverridden: isOverridden,
config: tilemapConfig,
},
manifestServiceUrl: serverConfig.get('tilemap.manifestServiceUrl')
}
}
};
}
9 changes: 9 additions & 0 deletions src/core_plugins/region_map/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function (kibana) {

return new kibana.Plugin({
uiExports: {
visTypes: ['plugins/region_map/region_map_vis']
}
});

}
4 changes: 4 additions & 0 deletions src/core_plugins/region_map/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "region_map",
"version": "kibana"
}
291 changes: 291 additions & 0 deletions src/core_plugins/region_map/public/choropleth_layer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import $ from 'jquery';
import L from 'leaflet';
import _ from 'lodash';
import d3 from 'd3';
import { KibanaMapLayer } from 'ui/vis_maps/kibana_map_layer';
import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps';

export default class ChoroplethLayer extends KibanaMapLayer {

constructor(geojsonUrl) {
super();


this._metrics = null;
this._joinField = null;
this._colorRamp = truncatedColorMaps[Object.keys(truncatedColorMaps)[0]];
this._tooltipFormatter = () => '';

this._geojsonUrl = geojsonUrl;
this._leafletLayer = L.geoJson(null, {
onEachFeature: (feature, layer) => {
layer.on('click', () => {
this.emit('select', feature.properties[this._joinField]);
});
let location = null;
layer.on({
mouseover: () => {
const tooltipContents = this._tooltipFormatter(feature);
if (!location) {
const leafletGeojon = L.geoJson(feature);
location = leafletGeojon.getBounds().getCenter();
}

this.emit('showTooltip', {
content: tooltipContents,
position: location
});
},
mouseout: () => {
this.emit('hideTooltip');
}
});
},
style: emptyStyle
});

this._loaded = false;
this._error = false;
$.ajax({
dataType: 'json',
url: geojsonUrl,
success: (data) => {
this._leafletLayer.addData(data);
this._loaded = true;
this._setStyle();
},
error: () => {
this._loaded = true;
this._error = true;
}
});
}

_setStyle() {
if (this._error || (!this._loaded || !this._metrics || !this._joinField)) {
return;
}

const styler = makeChoroplethStyler(this._metrics, this._colorRamp, this._joinField);
this._leafletLayer.setStyle(styler.getLeafletStyleFunction);

if (this._metrics && this._metrics.length > 0) {
const { min, max } = getMinMax(this._metrics);
this._legendColors = getLegendColors(this._colorRamp);
const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain();
this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors);
}
this.emit('styleChanged', {
mismatches: styler.getMismatches()
});
}

getMetrics() {
return this._metrics;
}

getMetricsAgg() {
return this._metricsAgg;
}

getUrl() {
return this._geojsonUrl;
}

setTooltipFormatter(tooltipFormatter, metricsAgg, fieldName) {
this._tooltipFormatter = (geojsonFeature) => {
if (!this._metrics) {
return '';
}
const match = this._metrics.find((bucket) => {
return bucket.term === geojsonFeature.properties[this._joinField];
});
return tooltipFormatter(metricsAgg, match, fieldName);
};
}

setJoinField(joinfield) {
if (joinfield === this._joinField) {
return;
}
this._joinField = joinfield;
this._setStyle();
}


setMetrics(metrics, metricsAgg) {
this._metrics = metrics;
this._metricsAgg = metricsAgg;
this._valueFormatter = this._metricsAgg.fieldFormatter();
this._setStyle();
}

setColorRamp(colorRamp) {
if (_.isEqual(colorRamp, this._colorRamp)) {
return;
}
this._colorRamp = colorRamp;
this._setStyle();
}

equalsGeoJsonUrl(geojsonUrl) {
return this._geojsonUrl === geojsonUrl;
}

appendLegendContents(jqueryDiv) {


if (!this._legendColors || !this._legendQuantizer || !this._metricsAgg) {
return;
}

const titleText = this._metricsAgg.makeLabel();
const $title = $('<div>').addClass('tilemap-legend-title').text(titleText);
jqueryDiv.append($title);

this._legendColors.forEach((color) => {

const labelText = this._legendQuantizer
.invertExtent(color)
.map(this._valueFormatter)
.join(' – ');

const label = $('<div>');
const icon = $('<i>').css({
background: color,
'border-color': makeColorDarker(color)
});

const text = $('<span>').text(labelText);
label.append(icon);
label.append(text);

jqueryDiv.append(label);
});

}

}


function makeColorDarker(color) {
const amount = 1.3;//magic number, carry over from earlier
return d3.hcl(color).darker(amount).toString();
}


function getMinMax(data) {
let min = data[0].value;
let max = data[0].value;
for (let i = 1; i < data.length; i += 1) {
min = Math.min(data[i].value, min);
max = Math.max(data[i].value, max);
}
return { min, max };
}


function makeChoroplethStyler(data, colorramp, joinField) {


if (data.length === 0) {
return {
getLeafletStyleFunction: function () {
return emptyStyle();
},
getMismatches: function () {
return [];
}
};
}

const { min, max } = getMinMax(data);
const outstandingFeatures = data.slice();
return {
getLeafletStyleFunction: function (geojsonFeature) {
let lastIndex = -1;
const match = outstandingFeatures.find((bucket, index) => {
lastIndex = index;
if (typeof bucket.term === 'string' && typeof geojsonFeature.properties[joinField] === 'string') {
return normalizeString(bucket.term) === normalizeString(geojsonFeature.properties[joinField]);
} else {
return bucket.term === geojsonFeature.properties[joinField];
}
});

if (!match) {
return emptyStyle();
}

outstandingFeatures.splice(lastIndex, 1);
return {
fillColor: getChoroplethColor(match.value, min, max, colorramp),
weight: 2,
opacity: 1,
color: 'white',
fillOpacity: 0.7
};
},
/**
* should not be called until getLeafletStyleFunction has been called
* @return {Array}
*/
getMismatches: function () {
return outstandingFeatures.map((bucket) => bucket.term);
}
};


}


function normalizeString(string) {
return string.trim().toLowerCase();
}


function getLegendColors(colorRamp) {
const colors = [];
colors[0] = getColor(colorRamp, 0);
colors[1] = getColor(colorRamp, Math.floor(colorRamp.length * 1 / 4));
colors[2] = getColor(colorRamp, Math.floor(colorRamp.length * 2 / 4));
colors[3] = getColor(colorRamp, Math.floor(colorRamp.length * 3 / 4));
colors[4] = getColor(colorRamp, colorRamp.length - 1);
return colors;
}

function getColor(colorRamp, i) {

if (!colorRamp[i]) {
return getColor();
}

const color = colorRamp[i][1];
const red = Math.floor(color[0] * 255);
const green = Math.floor(color[1] * 255);
const blue = Math.floor(color[2] * 255);
return `rgb(${red},${green},${blue})`;
}


function getChoroplethColor(value, min, max, colorRamp) {
if (min === max) {
return getColor(colorRamp, colorRamp.length - 1);
}
const fraction = (value - min) / (max - min);
const index = Math.round(colorRamp.length * fraction) - 1;
const i = Math.max(Math.min(colorRamp.length - 1, index), 0);

return getColor(colorRamp, i);
}

const emptyStyleObject = {
weight: 1,
opacity: 0.6,
color: 'rgb(200,200,200)',
fillOpacity: 0
};
function emptyStyle() {
return emptyStyleObject;
}

Loading