diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/CatalogMap.vue b/lib/assets/javascripts/new-dashboard/components/Catalog/CatalogMap.vue index 31458fd56b..79c4c61131 100644 --- a/lib/assets/javascripts/new-dashboard/components/Catalog/CatalogMap.vue +++ b/lib/assets/javascripts/new-dashboard/components/Catalog/CatalogMap.vue @@ -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 { } } } - diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/legends/BasicLegend.vue b/lib/assets/javascripts/new-dashboard/components/Catalog/legends/BasicLegend.vue deleted file mode 100644 index d5761c8132..0000000000 --- a/lib/assets/javascripts/new-dashboard/components/Catalog/legends/BasicLegend.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorBinsStyle.js b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorBinsStyle.js index 0e597516af..952b19fc8b 100644 --- a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorBinsStyle.js +++ b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorBinsStyle.js @@ -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) diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorCategoriesStyle.js b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorCategoriesStyle.js index f1947d1278..9f49001a09 100644 --- a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorCategoriesStyle.js +++ b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorCategoriesStyle.js @@ -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]; diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorStyles.js b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorStyles.js new file mode 100644 index 0000000000..b58a813506 --- /dev/null +++ b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/colorStyles.js @@ -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'; +} diff --git a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/utils.js b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/utils.js index 249f237a56..c3c6571222 100644 --- a/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/utils.js +++ b/lib/assets/javascripts/new-dashboard/components/Catalog/map-styles/utils.js @@ -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); +} diff --git a/lib/assets/javascripts/new-dashboard/components/NavigationBar/NavigationBar.vue b/lib/assets/javascripts/new-dashboard/components/NavigationBar/NavigationBar.vue index 816cc46c9d..6dbda609be 100644 --- a/lib/assets/javascripts/new-dashboard/components/NavigationBar/NavigationBar.vue +++ b/lib/assets/javascripts/new-dashboard/components/NavigationBar/NavigationBar.vue @@ -45,14 +45,6 @@ :message="$t('FeedbackMessage.message')" @click.native.stop.prevent="toggleDropdown"/> - - diff --git a/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorBinsStyle.spec.js b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorBinsStyle.spec.js new file mode 100644 index 0000000000..49d25e18c4 --- /dev/null +++ b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorBinsStyle.spec.js @@ -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 + ); + }); + }); +}); diff --git a/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorCategoriesStyle.spec.js b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorCategoriesStyle.spec.js new file mode 100644 index 0000000000..49d25e18c4 --- /dev/null +++ b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorCategoriesStyle.spec.js @@ -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 + ); + }); + }); +}); diff --git a/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorStyles.spec.js b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorStyles.spec.js new file mode 100644 index 0000000000..49d25e18c4 --- /dev/null +++ b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/colorStyles.spec.js @@ -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 + ); + }); + }); +}); diff --git a/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/utils.spec.js b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/utils.spec.js new file mode 100644 index 0000000000..49d25e18c4 --- /dev/null +++ b/lib/assets/test/spec/new-dashboard/unit/specs/components/Catalog/map-styles/utils.spec.js @@ -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 + ); + }); + }); +}); diff --git a/package.json b/package.json index 6d60abd6ac..23addfd892 100644 --- a/package.json +++ b/package.json @@ -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",