Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request getredash#2374 from kravets-levko/feature/choropleth
Browse files Browse the repository at this point in the history
Choropleth visualization
  • Loading branch information
arikfr authored Mar 25, 2018
2 parents c19ff41 + 6a61057 commit d34d58b
Show file tree
Hide file tree
Showing 16 changed files with 791 additions and 24 deletions.
34 changes: 34 additions & 0 deletions client/app/assets/less/inc/visualizations/map.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
.map-visualization-container {
height: 500px;

> div:first-child {
width: 100%;
height: 100%;
z-index: 0;
}

.map-custom-control.leaflet-bar {
background: #fff;
padding: 10px;
margin: 10px;
position: absolute;
z-index: 1;

&.top-left {
left: 0;
top: 0;
}

&.top-right {
right: 0;
top: 0;
}

&.bottom-left {
left: 0;
bottom: 0;
}

&.bottom-right {
right: 0;
bottom: 0;
}
}
}
17 changes: 5 additions & 12 deletions client/app/components/dynamic-table/default-cell/utils.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { isUndefined, isFunction } from 'underscore';

const hasOwnProperty = Object.prototype.hasOwnProperty;
import { isFunction, extend } from 'underscore';
import { formatSimpleTemplate } from '@/lib/value-format';

function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}

function processTags(str, data, defaultColumn) {
return str.replace(/{{\s*([^\s]+)\s*}}/g, (match, column) => {
if (column === '@') {
column = defaultColumn;
}
if (hasOwnProperty.call(data, column) && !isUndefined(data[column])) {
return data[column];
}
return match;
});
return formatSimpleTemplate(str, extend({
'@': data[defaultColumn],
}, data));
}

export function renderDefault(column, row) {
Expand Down
16 changes: 15 additions & 1 deletion client/app/lib/value-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import _ from 'underscore';
// eslint-disable-next-line
const urlPattern = /(^|[\s\n]|<br\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi;

const hasOwnProperty = Object.prototype.hasOwnProperty;

function createDefaultFormatter(highlightLinks) {
if (highlightLinks) {
return (value) => {
Expand Down Expand Up @@ -50,11 +52,23 @@ function createNumberFormatter(format) {
return value => value;
}

export default function createFormatter(column) {
export function createFormatter(column) {
switch (column.displayAs) {
case 'number': return createNumberFormatter(column.numberFormat);
case 'boolean': return createBooleanFormatter(column.booleanValues);
case 'datetime': return createDateTimeFormatter(column.dateTimeFormat);
default: return createDefaultFormatter(column.allowHTML && column.highlightLinks);
}
}

export function formatSimpleTemplate(str, data) {
if (!_.isString(str)) {
return '';
}
return str.replace(/{{\s*([^\s]+)\s*}}/g, (match, prop) => {
if (hasOwnProperty.call(data, prop) && !_.isUndefined(data[prop])) {
return data[prop];
}
return match;
});
}
2 changes: 1 addition & 1 deletion client/app/visualizations/chart/plotly/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
each, values, sortBy, pluck, identity, filter, map,
} from 'underscore';
import moment from 'moment';
import createFormatter from '@/lib/value-format';
import { createFormatter } from '@/lib/value-format';

// The following colors will be used if you pick "Automatic" color.
const BaseColors = {
Expand Down
251 changes: 251 additions & 0 deletions client/app/visualizations/choropleth/choropleth-editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<div>
<ul class="tab-nav">
<li ng-class="{active: currentTab == 'general'}">
<a ng-click="changeTab('general')">General</a>
</li>
<li ng-class="{active: currentTab == 'colors'}">
<a ng-click="changeTab('colors')">Colors</a>
</li>
<li ng-class="{active: currentTab == 'bounds'}">
<a ng-click="changeTab('bounds')">Bounds</a>
</li>
</ul>
<div ng-if="currentTab == 'general'" class="m-t-10 m-b-10">
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Country code column</label>
<select ng-options="name for name in queryResult.getColumnNames()"
ng-model="options.countryCodeColumn" class="form-control"></select>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>Country code type</label>
<select ng-options="key as value for (key, value) in countryCodeTypes"
ng-model="options.countryCodeType" class="form-control"></select>
</div>
</div>
</div>

<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Value column</label>
<select ng-options="name for name in queryResult.getColumnNames()"
ng-model="options.valueColumn" class="form-control"></select>
</div>
</div>

<div class="col-xs-6">
<div class="form-group">
<label for="legend-value-format">
Value format
<span class="m-l-5"
uib-popover-html="'Format <a href=&quot;http://numeraljs.com/&quot; target=&quot;_blank&quot;>specs.</a>'"
popover-trigger="'click outsideClick'"><i class="fa fa-question-circle"></i></span>
</label>
<input class="form-control" id="legend-value-format"
ng-model="options.valueFormat" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
</div>

<div class="col-xs-6">
<div class="form-group">
<label for="legend-value-placeholder">Value placeholder</label>
<input class="form-control" id="legend-value-placeholder"
ng-model="options.noValuePlaceholder" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
</div>
</div>

<div class="form-group">
<label><input type="checkbox" ng-model="options.legend.visible"> Show legend</label>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label for="legend-position">Legend position</label>
<select class="form-control" id="legend-position"
ng-options="key as value for (key, value) in legendPositions"
ng-model="options.legend.position"
ng-disabled="!options.legend.visible"
></select>
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label for="legend-position">Legend text alignment</label>
<div class="btn-group d-flex">
<button type="button" class="btn btn-default btn-md flex-fill"
ng-click="options.legend.alignText = 'left'"
ng-class="{active: options.legend.alignText == 'left'}"><i class="fa fa-align-left"></i></button>
<button type="button" class="btn btn-default btn-md flex-fill"
ng-click="options.legend.alignText = 'center'"
ng-class="{active: options.legend.alignText == 'center'}"><i class="fa fa-align-center"></i></button>
<button type="button" class="btn btn-default btn-md flex-fill"
ng-click="options.legend.alignText = 'right'"
ng-class="{active: options.legend.alignText == 'right'}"><i class="fa fa-align-right"></i></button>
</div>
</div>
</div>
</div>

<label><input type="checkbox" ng-model="options.tooltip.enabled"> Show tooltip</label>
<div class="form-group">
<label for="tooltip-template">Tooltip template</label>
<input class="form-control" id="tooltip-template"
ng-model="options.tooltip.template" ng-model-options="{ allowInvalid: true, debounce: 200 }"
ng-disabled="!options.tooltip.enabled">
</div>

<label><input type="checkbox" ng-model="options.popup.enabled"> Show popup</label>
<div class="form-group">
<label for="popup-template">Popup template</label>
<textarea class="form-control resize-vertical" id="popup-template" rows="3"
ng-model="options.popup.template" ng-model-options="{ allowInvalid: true, debounce: 200 }"
ng-disabled="!options.popup.enabled"></textarea>
</div>

<div class="form-group">
<label class="ui-sortable-bypass text-muted" style="font-weight: normal; cursor: pointer;"
uib-popover-html="templateHint"
popover-trigger="'click outsideClick'" popover-placement="top-left">
Format specs <i class="fa fa-question-circle m-l-5"></i>
</label>
</div>
</div>

<div ng-if="currentTab == 'colors'" class="m-t-10 m-b-10">
<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Steps</label>
<input type="number" min="3" max="11" class="form-control"
ng-model="options.steps">
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
<label>Clustering mode</label>
<select ng-options="key as value for (key, value) in clusteringModes"
ng-model="options.clusteringMode" class="form-control"></select>
</div>
</div>
</div>

<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Min color</label>
<ui-select ng-model="options.colors.min">
<ui-select-match>
<color-box color="$select.selected.value"></color-box>
<span ng-bind-html="$select.selected.key | capitalize"></span>
</ui-select-match>
<ui-select-choices repeat="color.value as (key, color) in colors">
<color-box color="color.value"></color-box>
<span ng-bind-html="color.key | capitalize | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>

<div class="col-xs-6">
<div class="form-group">
<label>Max color</label>
<ui-select ng-model="options.colors.max">
<ui-select-match>
<color-box color="$select.selected.value"></color-box>
<span ng-bind-html="$select.selected.key | capitalize"></span>
</ui-select-match>
<ui-select-choices repeat="color.value as (key, color) in colors">
<color-box color="color.value"></color-box>
<span ng-bind-html="color.key | capitalize | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>

<div class="col-xs-6">
<div class="form-group">
<label>No value color</label>
<ui-select ng-model="options.colors.noValue">
<ui-select-match>
<color-box color="$select.selected.value"></color-box>
<span ng-bind-html="$select.selected.key | capitalize"></span>
</ui-select-match>
<ui-select-choices repeat="color.value as (key, color) in colors">
<color-box color="color.value"></color-box>
<span ng-bind-html="color.key | capitalize | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
</div>

<div class="row">
<div class="col-xs-6">
<div class="form-group">
<label>Background color</label>
<ui-select ng-model="options.colors.background">
<ui-select-match>
<color-box color="$select.selected.value"></color-box>
<span ng-bind-html="$select.selected.key | capitalize"></span>
</ui-select-match>
<ui-select-choices repeat="color.value as (key, color) in colors">
<color-box color="color.value"></color-box>
<span ng-bind-html="color.key | capitalize | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>

<div class="col-xs-6">
<div class="form-group">
<label>Borders color</label>
<ui-select ng-model="options.colors.borders">
<ui-select-match>
<color-box color="$select.selected.value"></color-box>
<span ng-bind-html="$select.selected.key | capitalize"></span>
</ui-select-match>
<ui-select-choices repeat="color.value as (key, color) in colors">
<color-box color="color.value"></color-box>
<span ng-bind-html="color.key | capitalize | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
</div>
</div>

<div ng-if="currentTab == 'bounds'" class="m-t-10 m-b-10">
<div class="form-group">
<label>North-East latitude and longitude</label>
<div class="row">
<div class="col-xs-6">
<input class="form-control" type="text"
ng-model="options.bounds[1][0]" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
<div class="col-xs-6">
<input class="form-control" type="text"
ng-model="options.bounds[1][1]" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
</div>
</div>

<div class="form-group">
<label>South-West latitude and longitude</label>
<div class="row">
<div class="col-xs-6">
<input class="form-control" type="text"
ng-model="options.bounds[0][0]" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
<div class="col-xs-6">
<input class="form-control" type="text"
ng-model="options.bounds[0][1]" ng-model-options="{ allowInvalid: true, debounce: 200 }">
</div>
</div>
</div>
</div>
</div>
11 changes: 11 additions & 0 deletions client/app/visualizations/choropleth/choropleth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="map-visualization-container">
<div resize-event="handleResize()" ng-style="{ background: options.colors.background }"></div>
<div ng-if="options.legend.visible && (legendItems.length > 0)"
class="leaflet-bar map-custom-control" ng-class="options.legend.position"
>
<div ng-repeat="item in legendItems" class="d-flex align-items-center">
<color-box color="item.color" class="m-0" style="line-height: 1px"></color-box>
<div class="flex-fill text-{{ options.legend.alignText }}">{{ formatValue(item.limit) }}</div>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions client/app/visualizations/choropleth/countries.geo.json

Large diffs are not rendered by default.

Loading

0 comments on commit d34d58b

Please sign in to comment.