Refactor colorStyle features

pull/15973/head
Jesús Arroyo Torrens 4 years ago
parent da943d8e8c
commit 24c7b8cf2e

@ -34,31 +34,14 @@ import mapboxgl from 'mapbox-gl';
import { Deck } from '@deck.gl/core';
import { CartoBQTilerLayer, BASEMAP } from '@deck.gl/carto';
import colorBinsStyle from './map-styles/colorBinsStyle';
import colorCategoriesStyle from './map-styles/colorCategoriesStyle';
import { formatNumber, capitalize, compare } from './map-styles/utils';
import { generateColorStyleProps, resetColorStyleProps } from './map-styles/colorStyles';
import ColorBinsLegend from './legends/ColorBinsLegend';
import ColorCategoriesLegend from './legends/ColorCategoriesLegend';
let deck;
let propId;
let colorStyle;
let getFillColor;
let getLineColor;
let getLineWidth;
let getRadius;
const CATEGORY_PALETTES = {
demographics: 'BrwnYl',
environmental: 'BluGrn',
derived: 'Teal',
housing: 'Burg',
human_mobility: 'RedOr',
road_traffic: 'Sunset',
financial: 'PurpOr',
covid19: 'Peach',
behavioral: 'TealGrn'
};
let styleProps = {};
export default {
name: 'CatalogMap',
@ -92,9 +75,6 @@ export default {
categoryId () {
return this.dataset.category_id;
},
categoryIdPalette () {
return CATEGORY_PALETTES[this.categoryId] || 'OrYel';
},
isGeography () {
return this.$route.params.entity_type === 'geography';
},
@ -105,28 +85,28 @@ export default {
return this.variable && this.variable.description;
},
variableMin () {
return this.formatNumber(this.variable && this.variable.min);
return formatNumber(this.variable && this.variable.min);
},
variableMax () {
return this.formatNumber(this.variable && this.variable.max);
return formatNumber(this.variable && this.variable.max);
},
variableAvg () {
return this.formatNumber(this.variable && this.variable.avg);
return formatNumber(this.variable && this.variable.avg);
},
variableBins () {
if (this.variable && this.variable.quantiles && colorStyle) {
if (this.variable && this.variable.quantiles && styleProps.colorStyle) {
const breaks = [...this.variable.quantiles[2]['5'], this.variable.max];
const bins = [...new Set(breaks)].map(q => ({
color: `rgb(${colorStyle(q)})`
color: `rgb(${styleProps.colorStyle(q)})`
}));
return bins;
}
},
variableCategories () {
if (this.variable && this.variable.categories && colorStyle) {
if (this.variable && this.variable.categories && styleProps.colorStyle) {
const categories = this.variable.categories.slice(0, 10).map(c => ({
color: `rgb(${colorStyle(c.category)})`,
name: this.capitalize(c.category)
color: `rgb(${styleProps.colorStyle(c.category)})`,
name: capitalize(c.category)
}));
categories.push({
name: 'Others',
@ -174,14 +154,14 @@ export default {
let index = 0;
const items = {};
for (const o of objects) {
if (this.compare(o.object.geometry.coordinates, object.geometry.coordinates)) {
if (compare(o.object.geometry.coordinates, object.geometry.coordinates)) {
// Display the points that are fully overlapped (same coordinates)
let value = o.object.properties[this.variable.attribute];
if (value !== undefined && value !== null) {
if (typeof value === 'number') {
value = this.formatNumber(value);
value = formatNumber(value);
} else if (typeof value === 'string') {
value = this.capitalize(value);
value = capitalize(value);
}
} else {
value = 'No data';
@ -219,7 +199,7 @@ export default {
let style = document.createElement('link');
style.type = 'text/css';
style.rel = 'stylesheet';
style.href = 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css';
style.href = 'https://libs.cartocdn.com/mapbox-gl/v1.13.0/mapbox-gl.css';
document.head.appendChild(style);
},
syncMapboxViewState (viewState) {
@ -251,16 +231,14 @@ export default {
// To test in staging:
// mapsUrl: 'https://maps-api-v2.carto-staging.com/user/{user}'
},
getFillColor,
getLineColor,
getLineWidth,
getRadius,
...styleProps.deck,
lineWidthUnits: 'pixels',
pointRadiusUnits: 'pixels',
pickable: true,
onDataLoad: (tileJSON) => {
const { center, tilestats } = tileJSON;
this.initialViewState = {
// Zoom out the original zoom value
zoom: parseFloat(center[2]) - 1,
latitude: parseFloat(center[1]),
longitude: parseFloat(center[0]),
@ -270,8 +248,7 @@ export default {
this.recenterMap();
this.setGeomType(tilestats);
this.setVariable(tilestats);
this.resetColorStyle();
this.generateColorStyle();
this.setStyleProps();
this.renderLayer();
this.showMap = true;
}
@ -298,99 +275,14 @@ export default {
...variableExtra
};
},
formatNumber (value) {
if (value !== undefined && value !== null) {
if (!Number.isInteger(value)) {
return value.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
return value.toLocaleString();
}
},
capitalize (string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
},
generateColorStyle () {
const g = this.geomType;
const v = this.variable && this.variable.type;
const stats = this.variable;
propId = this.variable && this.variable.attribute;
if (g === 'Polygon' && this.isGeography) {
getFillColor = [234, 200, 100, 168];
getLineColor = [44, 44, 44, 60];
getLineWidth = 1;
} else if (g === 'Polygon' && v === 'Number') {
colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: this.categoryIdPalette
});
getFillColor = (d) => colorStyle(d.properties[propId]);
getLineColor = [44, 44, 44, 60];
getLineWidth = 1;
} else if (g === 'Polygon' && v === 'String') {
colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Prism'
});
getFillColor = (d) => colorStyle(d.properties[propId]);
getLineColor = [44, 44, 44, 60];
getLineWidth = 1;
} else if (g === 'LineString' && this.isGeography) {
getLineColor = [234, 200, 100, 255];
getLineWidth = 2;
} else if (g === 'LineString' && v === 'Number') {
colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: this.categoryIdPalette
});
getLineColor = (d) => colorStyle(d.properties[propId]);
getLineWidth = 2;
} else if (g === 'LineString' && v === 'String') {
colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Prism'
});
getLineColor = (d) => colorStyle(d.properties[propId]);
getLineWidth = 2;
} else if (g === 'Point' && this.isGeography) {
getFillColor = [234, 200, 100, 255];
getLineColor = [44, 44, 44, 255];
getLineWidth = 1;
getRadius = 4;
} else if (g === 'Point' && v === 'Number') {
colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: this.categoryIdPalette
});
getFillColor = (d) => colorStyle(d.properties[propId]);
getLineColor = [100, 100, 100, 255];
getLineWidth = 1;
getRadius = 4;
} else if (g === 'Point' && v === 'String') {
colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Bold'
});
getFillColor = (d) => colorStyle(d.properties[propId]);
getLineColor = [100, 100, 100, 255];
getLineWidth = 1;
getRadius = 4;
}
},
resetColorStyle () {
propId = undefined;
colorStyle = undefined;
getFillColor = undefined;
getLineColor = undefined;
getLineWidth = undefined;
getRadius = undefined;
},
compare (a, b) {
return JSON.stringify(a) === JSON.stringify(b);
setStyleProps () {
styleProps = resetColorStyleProps();
styleProps = generateColorStyleProps({
geomType: this.geomType,
variable: this.variable,
categoryId: this.categoryId,
isGeography: this.isGeography
});
}
}
};
@ -463,5 +355,4 @@ export default {
}
}
}
</style>

@ -1,18 +0,0 @@
<template>
<div class="basic-legend is-small">
<p class="legend-title is-semibold">{{ title }}</p>
</div>
</template>
<script>
export default {
name: 'BasicLegend',
props: {
title: String
}
};
</script>
<style lang="scss" scoped>
// .basic-legend {}
</style>

@ -1,6 +1,6 @@
import { range } from 'd3-array';
import { scaleThreshold } from 'd3-scale';
import { gePalette, NULL_COLOR } from './utils';
import { getPalette, NULL_COLOR } from './utils';
const N_BINS = 5;
@ -30,7 +30,7 @@ export default function colorBinsStyle ({ breaks, colors, nullColor = NULL_COLOR
}
}
const palette = typeof colors === 'string' ? gePalette(colors, domain.length) : colors;
const palette = typeof colors === 'string' ? getPalette(colors, domain.length) : colors;
const color = scaleThreshold()
.domain(domain)

@ -1,4 +1,4 @@
import { gePalette, NULL_COLOR } from './utils';
import { getPalette, NULL_COLOR } from './utils';
const OTHERS_COLOR = [165, 170, 153];
const TOP = 10;
@ -19,7 +19,7 @@ export default function colorCategoriesStyle ({
categoryList = stats.categories.map(c => c.category).slice(0, top);
}
const palette = typeof colors === 'string' ? gePalette(colors, categoryList.length) : colors;
const palette = typeof colors === 'string' ? getPalette(colors, categoryList.length) : colors;
for (const [i, c] of categoryList.entries()) {
colorsByCategory[c] = palette[i];

@ -0,0 +1,104 @@
import colorBinsStyle from './colorBinsStyle';
import colorCategoriesStyle from './colorCategoriesStyle';
const CATEGORY_PALETTES = {
demographics: 'BrwnYl',
environmental: 'BluGrn',
derived: 'Teal',
housing: 'Burg',
human_mobility: 'RedOr',
road_traffic: 'Sunset',
financial: 'PurpOr',
covid19: 'Peach',
behavioral: 'TealGrn'
};
export function generateColorStyleProps ({ geomType, variable, categoryId, isGeography }) {
const g = geomType;
const v = variable && variable.type;
const stats = variable;
const props = { deck: {} };
props.propId = variable && variable.attribute;
if (g === 'Polygon' && isGeography) {
props.deck.getFillColor = [234, 200, 100, 168];
props.deck.getLineColor = [44, 44, 44, 60];
props.deck.getLineWidth = 1;
} else if (g === 'Polygon' && v === 'Number') {
props.colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: categoryIdPalette(categoryId)
});
props.deck.getFillColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineColor = [44, 44, 44, 60];
props.deck.getLineWidth = 1;
} else if (g === 'Polygon' && v === 'String') {
props.colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Prism'
});
props.deck.getFillColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineColor = [44, 44, 44, 60];
props.deck.getLineWidth = 1;
} else if (g === 'LineString' && isGeography) {
props.deck.getLineColor = [234, 200, 100, 255];
props.deck.getLineWidth = 2;
} else if (g === 'LineString' && v === 'Number') {
props.colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: categoryIdPalette(categoryId)
});
props.deck.getLineColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineWidth = 2;
} else if (g === 'LineString' && v === 'String') {
props.colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Prism'
});
props.deck.getLineColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineWidth = 2;
} else if (g === 'Point' && isGeography) {
props.deck.getFillColor = [234, 200, 100, 255];
props.deck.getLineColor = [44, 44, 44, 255];
props.deck.getLineWidth = 1;
props.deck.getRadius = 4;
} else if (g === 'Point' && v === 'Number') {
props.colorStyle = colorBinsStyle({
breaks: { stats, method: 'quantiles', bins: 5 },
colors: categoryIdPalette(categoryId)
});
props.deck.getFillColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineColor = [100, 100, 100, 255];
props.deck.getLineWidth = 1;
props.deck.getRadius = 4;
} else if (g === 'Point' && v === 'String') {
props.colorStyle = colorCategoriesStyle({
categories: { stats, top: 10 },
colors: 'Bold'
});
props.deck.getFillColor = (d) => props.colorStyle(d.properties[props.propId]);
props.deck.getLineColor = [100, 100, 100, 255];
props.deck.getLineWidth = 1;
props.deck.getRadius = 4;
}
return props;
}
export function resetColorStyleProps () {
return {
propId: undefined,
colorStyle: undefined,
deck: {
getFillColor: undefined,
getLineColor: undefined,
getLineWidth: undefined,
getRadius: undefined
}
};
}
function categoryIdPalette (categoryId) {
return CATEGORY_PALETTES[categoryId] || 'OrYel';
}

@ -2,7 +2,7 @@ import * as cartoColors from 'cartocolor';
export const NULL_COLOR = [204, 204, 204];
export function gePalette (name, numCategories) {
export function getPalette (name, numCategories) {
const palette = cartoColors[name];
let paletteIndex = numCategories;
@ -78,3 +78,23 @@ function hexToRgb (hex) {
throw new Error(`Error parsing hexadecimal color: ${hex}`);
}
export function formatNumber (value) {
if (value !== undefined && value !== null) {
if (!Number.isInteger(value)) {
return value.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
return value.toLocaleString();
}
}
export function capitalize (string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
export function compare (a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}

@ -45,14 +45,6 @@
:message="$t('FeedbackMessage.message')"
@click.native.stop.prevent="toggleDropdown"/>
<!-- <NotificationPopup
v-if="!popupWasShown('dataObservatory.popupWasShown') && hasDOEnabled"
class="notification-popup"
:title="$t('DataObservatoryMessage.title')"
:message="$t('DataObservatoryMessage.message', { path: this.$router.resolve({ name: 'spatial-data-catalog' }).href })"
:messageHasHTML="true"
@click.native="markPopupAsRead('dataObservatory.popupWasShown')"/> -->
<NotificationPopup
v-if="!popupWasShown('popups.DataObservatorySamples')"
class="notification-popup"

@ -48,8 +48,6 @@ export default {
hasSample () {
return this.dataset.sample_info && !!this.dataset.sample_info.id;
}
},
methods: {
}
};
</script>

@ -0,0 +1,46 @@
import * as Metrics from 'new-dashboard/core/metrics';
import store from 'new-dashboard/store';
describe('Internal Metrics Tracker', () => {
describe('sendMetric', () => {
let previousState;
beforeEach(() => {
global.fetch = jest.fn();
previousState = {
...store.state,
config: { ...store.state.config },
user: { ...store.state.user }
};
});
afterEach(() => {
store.replaceState(previousState);
});
it('should return call fetch with proper parameters', () => {
const eventName = 'fake_event';
const eventProperties = { page: 'dashboard', user_id: 'fake_id' };
const baseURL = 'https://user.carto.com';
store.state.config.base_url = baseURL;
store.state.user = { id: 'fake_id' };
Metrics.sendMetric(eventName, eventProperties);
const requestURL = `${baseURL}/api/v3/metrics`;
const requestProperties = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: eventName,
properties: eventProperties
})
};
expect(global.fetch).toHaveBeenCalledWith(
requestURL,
requestProperties
);
});
});
});

@ -0,0 +1,46 @@
import * as Metrics from 'new-dashboard/core/metrics';
import store from 'new-dashboard/store';
describe('Internal Metrics Tracker', () => {
describe('sendMetric', () => {
let previousState;
beforeEach(() => {
global.fetch = jest.fn();
previousState = {
...store.state,
config: { ...store.state.config },
user: { ...store.state.user }
};
});
afterEach(() => {
store.replaceState(previousState);
});
it('should return call fetch with proper parameters', () => {
const eventName = 'fake_event';
const eventProperties = { page: 'dashboard', user_id: 'fake_id' };
const baseURL = 'https://user.carto.com';
store.state.config.base_url = baseURL;
store.state.user = { id: 'fake_id' };
Metrics.sendMetric(eventName, eventProperties);
const requestURL = `${baseURL}/api/v3/metrics`;
const requestProperties = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: eventName,
properties: eventProperties
})
};
expect(global.fetch).toHaveBeenCalledWith(
requestURL,
requestProperties
);
});
});
});

@ -0,0 +1,46 @@
import * as Metrics from 'new-dashboard/core/metrics';
import store from 'new-dashboard/store';
describe('Internal Metrics Tracker', () => {
describe('sendMetric', () => {
let previousState;
beforeEach(() => {
global.fetch = jest.fn();
previousState = {
...store.state,
config: { ...store.state.config },
user: { ...store.state.user }
};
});
afterEach(() => {
store.replaceState(previousState);
});
it('should return call fetch with proper parameters', () => {
const eventName = 'fake_event';
const eventProperties = { page: 'dashboard', user_id: 'fake_id' };
const baseURL = 'https://user.carto.com';
store.state.config.base_url = baseURL;
store.state.user = { id: 'fake_id' };
Metrics.sendMetric(eventName, eventProperties);
const requestURL = `${baseURL}/api/v3/metrics`;
const requestProperties = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: eventName,
properties: eventProperties
})
};
expect(global.fetch).toHaveBeenCalledWith(
requestURL,
requestProperties
);
});
});
});

@ -0,0 +1,46 @@
import * as Metrics from 'new-dashboard/core/metrics';
import store from 'new-dashboard/store';
describe('Internal Metrics Tracker', () => {
describe('sendMetric', () => {
let previousState;
beforeEach(() => {
global.fetch = jest.fn();
previousState = {
...store.state,
config: { ...store.state.config },
user: { ...store.state.user }
};
});
afterEach(() => {
store.replaceState(previousState);
});
it('should return call fetch with proper parameters', () => {
const eventName = 'fake_event';
const eventProperties = { page: 'dashboard', user_id: 'fake_id' };
const baseURL = 'https://user.carto.com';
store.state.config.base_url = baseURL;
store.state.user = { id: 'fake_id' };
Metrics.sendMetric(eventName, eventProperties);
const requestURL = `${baseURL}/api/v3/metrics`;
const requestProperties = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: eventName,
properties: eventProperties
})
};
expect(global.fetch).toHaveBeenCalledWith(
requestURL,
requestProperties
);
});
});
});

@ -55,7 +55,7 @@
"jquery": "2.1.4",
"leaflet": "CartoDB/Leaflet#v1.3.1-carto1",
"loader-utils": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
"mapbox-gl": "^1.12.0",
"mapbox-gl": "^1.13.0",
"moment": "2.18.1",
"moment-timezone": "^0.5.13",
"node-polyglot": "1.0.0",

Loading…
Cancel
Save