diff --git a/.gitignore b/.gitignore index d5a2735..3bf23f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -node_modules +node_modules* npm-debug.log .DS_Store .vscode @@ -10,4 +10,4 @@ artifacts/ .venv* bin/ -build/ +build/ \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..abc26f9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +10.24.1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 438d233..7fb4fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Entries +## v1.0.2 +- Added filtering by minimum value and color as second metric + ## v1.0.1 - Release for Grafana 7.0 with plugin signing diff --git a/README.md b/README.md index 8a0a44a..c42af0b 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,10 @@ This is minimum size for a circle in pixels. This is the maximum size for a circle in pixels. Depending on the zoom level you might want a larger or smaller max circle size to avoid overlapping. +**Min Value** + +This is the minimum value to show a circle. If provided, data points with values lower than this minimum value won't be shown at the map. + **Unit** The Unit is shown in the popover when you hover over a circle. There are two fields the singular form and the plural form. E.g. visit/visits or error/errors @@ -269,7 +273,15 @@ The Unit is shown in the popover when you hover over a circle. There are two fie Shows/hide the legend on the bottom left that shows the threshold ranges and their associated colors. -### Threshold Options +### Coloring Options + +The coloring options allows the use of the circle colors with a second metric. In case of a single metric, leave the Color metric field empty and the same value will be used for circle size and color. + +Using the field color metric will enable the color to read from this second metric and generate thresholds based on this number. + +The Color label is used on the tooltip, showing a "Label: Value Unit" as a second line on the map's tooltip, together with the Color unit field. + +The Color value decimals limits the decimal cases from the color metric on the map's tooltip. Thresholds control the color of the circles. @@ -277,6 +289,7 @@ If one value is specified then two colors are used. For example, if the threshol The threshold field also accepts 2 or more comma-separated values. For example, if you have 2 values that represents 3 ranges that correspond to the three colors. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90. + ### CHANGELOG The latest changes can be found here: [CHANGELOG.md](https://github.com/grafana/worldmap-panel/blob/master/CHANGELOG.md) diff --git a/package.json b/package.json index d5cc8fe..5997fc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "worldmap-panel", - "version": "1.0.1", + "version": "1.0.2", "description": "Worldmap Panel Plugin for Grafana", "main": "src/module.js", "scripts": { diff --git a/src/data_formatter.ts b/src/data_formatter.ts index 13121f0..9880b49 100644 --- a/src/data_formatter.ts +++ b/src/data_formatter.ts @@ -53,8 +53,9 @@ export default class DataFormatter { } } - createDataValue(encodedGeohash, decodedGeohash, locationName, value) { + createDataValue(encodedGeohash, decodedGeohash, locationName, value, colorValue) { const dataValue = { + colorValue: colorValue, key: encodedGeohash, locationName: locationName, locationLatitude: decodedGeohash.latitude, @@ -65,6 +66,7 @@ export default class DataFormatter { }; dataValue.valueRounded = kbn.roundValue(dataValue.value, this.ctrl.panel.decimals || 0); + dataValue.colorValue = kbn.roundValue(dataValue.colorValue, this.ctrl.panel.colorDecimals || 0); return dataValue; } @@ -92,8 +94,8 @@ export default class DataFormatter { ? row[columnNames[this.ctrl.panel.esLocationName]] : encodedGeohash; const value = row[columnNames[this.ctrl.panel.esMetric]]; - - const dataValue = this.createDataValue(encodedGeohash, decodedGeohash, locationName, value); + const colorValue = row[columnNames[this.ctrl.panel.colorMetric]]; + const dataValue = this.createDataValue(encodedGeohash, decodedGeohash, locationName, value, colorValue); if (dataValue.value > highestValue) { highestValue = dataValue.value; } @@ -116,8 +118,8 @@ export default class DataFormatter { ? datapoint[this.ctrl.panel.esLocationName] : encodedGeohash; const value = datapoint[this.ctrl.panel.esMetric]; - - const dataValue = this.createDataValue(encodedGeohash, decodedGeohash, locationName, value); + const colorValue = datapoint[this.ctrl.panel.colorMetric]; + const dataValue = this.createDataValue(encodedGeohash, decodedGeohash, locationName, value, colorValue); if (dataValue.value > highestValue) { highestValue = dataValue.value; } diff --git a/src/partials/editor.html b/src/partials/editor.html index 63c474e..dc385e9 100644 --- a/src/partials/editor.html +++ b/src/partials/editor.html @@ -29,6 +29,11 @@
Map Visual Options
+
+ + +
@@ -189,7 +194,27 @@
-
Threshold Options
+
Coloring Options
+
+ + +
+
+ + +
+
+ + +
+
+ + +
{ }); }); + describe('when a second metric is set to the color', () => { + const lowVal = 7; + const avgVal = 50; + const highVal = 99; + beforeEach(() => { + ctrl.data = new DataBuilder() + .withCountryAndValue('SE', 1, highVal) + .withCountryAndValue('IE', 2, avgVal) + .withCountryAndValue('US', 1, lowVal) + .withDataRange(0, 100, 50) + .withThresholdValues([33,66]) + .build(); + worldMap.drawCircles(); + }); + + it('should create circle popups with the second metrics there', () => { + expect(worldMap.circles[0]._popup._content).toBe(`Sweden: 1
${highVal}`); + expect(worldMap.circles[1]._popup._content).toBe(`Ireland: 2
${avgVal}`); + expect(worldMap.circles[2]._popup._content).toBe(`United States: 1
${lowVal}`); + }); + + it('should set the right colors using the second metric', () => { + expect(worldMap.circles[0].options.color).toBe('green'); + expect(worldMap.circles[1].options.color).toBe('blue'); + expect(worldMap.circles[2].options.color).toBe('red'); + }); + }); + + describe('when a second metric is set to the color with labels', () => { + const lowVal = 7; + const avgVal = 50; + const highVal = 99; + const label = 'Metric'; + const unit = '%'; + + beforeEach(() => { + ctrl.data = new DataBuilder() + .withCountryAndValue('SE', 1, highVal) + .withCountryAndValue('IE', 2, avgVal) + .withCountryAndValue('US', 1, lowVal) + .build(); + ctrl.panel.colorLabel = label; + ctrl.panel.colorUnit = unit; + worldMap.drawCircles(); + }); + + it('should create circle popups with the second metrics there', () => { + expect(worldMap.circles[0]._popup._content).toBe(`Sweden: 1
${label}: ${highVal}${unit}`); + expect(worldMap.circles[1]._popup._content).toBe(`Ireland: 2
${label}: ${avgVal}${unit}`); + expect(worldMap.circles[2]._popup._content).toBe(`United States: 1
${label}: ${lowVal}${unit}`); + }); + }); + afterEach(() => { const fixture: HTMLElement = document.getElementById('fixture')!; document.body.removeChild(fixture); diff --git a/src/worldmap.ts b/src/worldmap.ts index 341f9a6..3a4e43c 100644 --- a/src/worldmap.ts +++ b/src/worldmap.ts @@ -103,7 +103,9 @@ export default class WorldMap { filterEmptyAndZeroValues(data) { return _.filter(data, o => { - return !(this.ctrl.panel.hideEmpty && _.isNil(o.value)) && !(this.ctrl.panel.hideZero && o.value === 0); + return !(this.ctrl.panel.hideEmpty && _.isNil(o.value)) + && !(this.ctrl.panel.hideZero && o.value === 0) + && !(this.ctrl.panel.minValue && o.value <= this.ctrl.panel.minValue) }); } @@ -148,29 +150,33 @@ export default class WorldMap { }); if (circle) { + const colorValue = dataPoint.colorValue !== undefined ? dataPoint.colorValue : dataPoint.value; + const color = this.getColor(colorValue); circle.setRadius(this.calcCircleSize(dataPoint.value || 0)); circle.setStyle({ - color: this.getColor(dataPoint.value), - fillColor: this.getColor(dataPoint.value), + color, + fillColor: color, fillOpacity: 0.5, location: dataPoint.key, }); circle.unbindPopup(); - this.createPopup(circle, dataPoint.locationName, dataPoint.valueRounded); + this.createPopup(circle, dataPoint.locationName, dataPoint.valueRounded, dataPoint.colorValue); } }); } createCircle(dataPoint) { + const colorValue = dataPoint.colorValue !== undefined ? dataPoint.colorValue : dataPoint.value; + const color = this.getColor(colorValue); const circle = (window).L.circleMarker([dataPoint.locationLatitude, dataPoint.locationLongitude], { radius: this.calcCircleSize(dataPoint.value || 0), - color: this.getColor(dataPoint.value), - fillColor: this.getColor(dataPoint.value), + color, + fillColor: color, fillOpacity: 0.5, location: dataPoint.key, }); - this.createPopup(circle, dataPoint.locationName, dataPoint.valueRounded); + this.createPopup(circle, dataPoint.locationName, dataPoint.valueRounded, dataPoint.colorValue); return circle; } @@ -188,9 +194,11 @@ export default class WorldMap { return circleSizeRange * dataFactor + circleMinSize; } - createPopup(circle, locationName, value) { + createPopup(circle, locationName, value, colorValue) { const unit = value && value === 1 ? this.ctrl.panel.unitSingular : this.ctrl.panel.unitPlural; - const label = (locationName + ': ' + value + ' ' + (unit || '')).trim(); + const firstLine = `${locationName}: ${value} ${(unit || '')}`.trim(); + const secondLine = colorValue !== undefined ? `${(this.ctrl.panel.colorLabel !== undefined ? this.ctrl.panel.colorLabel + ': ':'') + colorValue + (this.ctrl.panel.colorUnit || '')}`.trim(): ''; + const label = `${firstLine}${secondLine !== '' ? '
' + secondLine : ''}`; circle.bindPopup(label, { offset: (window).L.point(0, -2), className: 'worldmap-popup', diff --git a/src/worldmap_ctrl.ts b/src/worldmap_ctrl.ts index 4aa746c..c40d4b3 100644 --- a/src/worldmap_ctrl.ts +++ b/src/worldmap_ctrl.ts @@ -16,6 +16,11 @@ const panelDefaults = { mapCenterLongitude: 0, initialZoom: 1, valueName: "total", + minValue: 0, + colorMetric:"", + colorLabel: "", + colorUnit:"%", + colorDecimals: 2, circleMinSize: 2, circleMaxSize: 30, locationData: "countries", diff --git a/test/data_builder.ts b/test/data_builder.ts index 34bc99d..887738b 100644 --- a/test/data_builder.ts +++ b/test/data_builder.ts @@ -6,7 +6,7 @@ export default class DataBuilder { this.data.thresholds = []; } - withCountryAndValue(countryCode, value) { + withCountryAndValue(countryCode, value, colorValue?) { let dataPoint; if (countryCode === 'SE') { dataPoint = { @@ -16,6 +16,7 @@ export default class DataBuilder { locationLongitude: 18, value: value, valueRounded: value, + colorValue }; } else if (countryCode === 'IE') { dataPoint = { @@ -25,6 +26,7 @@ export default class DataBuilder { locationLongitude: 8, value: value, valueRounded: value, + colorValue }; } else if (countryCode === 'US') { dataPoint = { @@ -34,6 +36,7 @@ export default class DataBuilder { locationLongitude: -95, value: value, valueRounded: value, + colorValue }; } this.data.push(dataPoint); diff --git a/tsconfig.json b/tsconfig.json index 0436f5e..a94ab96 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,4 +22,5 @@ "moduleResolution": "node", "esModuleInterop": true }, + "exclude": ["node_modules*", ".vscode"] }