Skip to content

Commit

Permalink
feat(vue): Implement category legend entries
Browse files Browse the repository at this point in the history
  • Loading branch information
donmccurdy committed Aug 1, 2024
1 parent 5468e95 commit c8dc73e
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 16 deletions.
45 changes: 43 additions & 2 deletions packages/create-vue/src/components/common/Legend.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
<script setup lang="ts">
import Card from './Card.vue';
const props = withDefaults(defineProps<{ open?: boolean }>(), { open: true });
import { Color } from '@deck.gl/core';
const props = withDefaults(
defineProps<{
open?: boolean;
entries: {
title: string;
subtitle: string;
values: string[];
getSwatchColor: (value: string) => Color;
}[];
}>(),
{ open: true },
);
function toHexString(color: Color): string {
const hex =
Math.round(color[0]) * 65536 +
Math.round(color[1]) * 256 +
Math.round(color[2]);
return '#' + ('000000' + hex.toString(16)).slice(-6);
}
</script>
<template>
<aside class="legend">
<Card title="Legend" :open="props.open">
<div class="skeleton" style="height: 7em" />
<section v-for="entry in entries" class="legend-section" key="{title}">
<p class="legend-section-title body2">{{ entry.title }}</p>
<p class="legend-section-subtitle caption">{{ entry.subtitle }}</p>
<ul class="legend-list">
<li
v-for="value in entry.values"
class="legend-list-item"
key="value"
>
<span
class="legend-list-item-swatch"
:style="{
backgroundColor: toHexString(entry.getSwatchColor(value)),
}"
/>
<span class="legend-list-item-title overline">
{{ value }}
</span>
</li>
</ul>
</section>
</Card>
</aside>
</template>
140 changes: 126 additions & 14 deletions packages/create-vue/src/components/views/Default.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,114 @@
<script setup lang="ts">
import { Layer } from '@deck.gl/core';
import {
computed,
onMounted,
onUnmounted,
ref,
shallowRef,
watchEffect,
} from 'vue';
import { Map } from 'maplibre-gl';
import { AccessorFunction, Deck, MapViewState, Color } from '@deck.gl/core';
import { colorCategories, VectorTileLayer } from '@deck.gl/carto';
import { vectorQuerySource } from '@carto/api-client';
import Layers from '../common/Layers.vue';
import Legend from '../common/Legend.vue';
import Card from '../common/Card.vue';
const layers: Layer[] = [];
const layerVisibility = {};
const MAP_STYLE =
'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json';
const INITIAL_VIEW_STATE: MapViewState = {
latitude: 37.0902,
longitude: -95.7129,
zoom: 3.5,
};
// TODO: Fetch categories from Widgets API?
const RADIO_DOMAIN = ['LTE', 'UMTS', 'CDMA', 'GSM', 'NR'];
const RADIO_COLORS: AccessorFunction<unknown, Color> = colorCategories({
attr: 'radio',
domain: RADIO_DOMAIN,
colors: 'Bold',
});
/****************************************************************************
* Sources (https://deck.gl/docs/api-reference/carto/data-sources)
*/
const data = computed(() =>
vectorQuerySource({
accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN,
connectionName: 'carto_dw',
sqlQuery:
'SELECT * FROM `carto-demo-data.demo_tables.cell_towers_worldwide`',
}),
);
/****************************************************************************
* Layers (https://deck.gl/docs/api-reference/carto/overview#carto-layers)
*/
const layerVisibility = ref<Record<string, boolean>>({
'Cell towers': true,
});
const onLayerVisibilityChange = () => {};
const layers = computed(() => [
new VectorTileLayer({
id: 'Cell towers',
visible: layerVisibility.value['Cell towers'],
data: data.value,
pointRadiusMinPixels: 4,
getFillColor: RADIO_COLORS,
}),
]);
/****************************************************************************
* DeckGL
*/
const map = shallowRef<Map | null>(null);
const deck = shallowRef<Deck | null>(null);
const viewState = ref<MapViewState>(INITIAL_VIEW_STATE);
const attributionHTML = ref<string>('');
watchEffect(() => {
const { longitude, latitude, ...rest } = viewState.value;
map.value?.jumpTo({ center: [longitude, latitude], ...rest });
});
watchEffect(() => {
deck.value?.setProps({ layers: layers.value });
});
watchEffect(() => {
data.value?.then(({ attribution }) => (attributionHTML.value = attribution));
});
onMounted(() => {
map.value = new Map({
container: 'maplibre-container',
style: MAP_STYLE,
interactive: false,
});
deck.value = new Deck({
canvas: 'deck-canvas',
initialViewState: INITIAL_VIEW_STATE,
controller: true,
layers: [],
onViewStateChange: ({ viewState: _viewState }) => {
viewState.value = _viewState;
},
});
});
onUnmounted(() => {
deck.value?.finalize();
map.value?.remove();
});
</script>
<template>
<aside class="sidebar">
Expand Down Expand Up @@ -34,17 +136,27 @@ const onLayerVisibilityChange = () => {};
</Card>
</aside>
<main class="map">
<!-- <DeckGL
layers={layers}
views={MAP_VIEW}
initialViewState={viewState}
controller={{ dragRotate: false }}
onViewStateChange={({ viewState }) => setViewState(viewState)}
>
<Map mapStyle={MAP_STYLE} />
</DeckGL> -->
<div
id="maplibre-container"
style="position: absolute; width: 100%; height: 100%"
></div>
<canvas
id="deck-canvas"
style="position: absolute; width: 100%; height: 100%"
></canvas>
<Layers :layers :layerVisibility :onLayerVisibilityChange />
<Legend />
<aside class="map-footer">TODO</aside>
<Legend
:entries="[
// TODO: Cleaner way to generate a legend?
{
title: 'Cell towers',
subtitle: 'By Radio',
values: RADIO_DOMAIN,
getSwatchColor: (value: string) =>
RADIO_COLORS({ properties: { radio: value } }, null!),
},
]"
/>
<aside class="map-footer" v-html="attributionHTML"></aside>
</main>
</template>

0 comments on commit c8dc73e

Please sign in to comment.