Skip to content

Commit

Permalink
177 feature info marker (#194)
Browse files Browse the repository at this point in the history
Feature values can be selected and will be displayed as petals. The
petal colors can be selected individually.
  • Loading branch information
katharinawuensche authored Dec 4, 2024
2 parents 515ad7d + 0d5d814 commit 234b7a9
Show file tree
Hide file tree
Showing 13 changed files with 475 additions and 32 deletions.
8 changes: 8 additions & 0 deletions assets/svg/petal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions components/geo-map.client.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
import { type GeoMapContext, key, type MarkerProperties } from "@/components/geo-map.context";
import GeoMapPopupContent from "@/components/geo-map-popup-content.vue";
import type { MarkerType } from "@/types/global";
interface Props {
height: number;
markers: Array<Feature<Point, MarkerProperties>>;
width: number;
markerType?: MarkerType;
}
const props = defineProps<Props>();
Expand Down Expand Up @@ -182,6 +184,7 @@ onMounted(async () => {
});
},
pointToLayer(feature, latlng) {
if (props.markerType === "petal") return usePetalMarker(feature, latlng);
if (feature.properties.type === "reg") {
return circleMarker(latlng, config.marker.region);
}
Expand Down Expand Up @@ -243,6 +246,7 @@ provide(key, context);
</script>

<template>
<SvgoPetal v-if="props.markerType === 'petal'" />
<div ref="elementRef" class="absolute inset-0 grid" data-geo-map />
<slot :context="context" />
<GeoMapPopupContent
Expand Down
85 changes: 85 additions & 0 deletions components/geojson-map-toolbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts" setup>
import { ChevronDown } from "lucide-vue-next";
import { useColorsStore } from "@/stores/use-colors-store";
import { useGeojsonStore } from "@/stores/use-geojson-store.ts";
import type { GeojsonMapSchema } from "@/types/global.d";
interface Props {
params: Zod.infer<typeof GeojsonMapSchema>["params"];
}
const props = defineProps<Props>();
const { params } = toRefs(props);
const GeojsonStore = useGeojsonStore();
const { tables } = storeToRefs(GeojsonStore);
const table = computed(() => tables.value.get(params.value.url));
const categories = computed(() => {
return table.value?.getAllColumns().filter((column) => column.getCanHide());
});
function titleCase(s: string) {
return s
.replace(/^[-_]*(.)/, (_, c) => c.toUpperCase()) // Initial char (after -/_)
.replace(/[-_]+(.)/g, (_, c) => " " + c.toUpperCase()); // First char after each -/_
}
const isCollapsibleOpen = ref(categories.value!.map(() => false));
const { colors, addColor, setColor } = useColorsStore();
</script>

<template>
<div class="grid items-center border-b border-border bg-surface px-8 py-3 text-on-surface">
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm font-medium text-on-surface/75">
<div v-for="(group, idx) in categories" :key="group.id">
<DropdownMenu v-slot="{ open }" v-model:open="isCollapsibleOpen[idx]">
<DropdownMenuTrigger class="flex w-full items-center gap-1 p-2 text-sm">
<span>{{ titleCase(group.id) }}</span>
<Badge
v-if="group.getLeafColumns().filter((c) => c.getIsVisible()).length"
variant="outline"
>{{ group.getLeafColumns().filter((c) => c.getIsVisible()).length }}</Badge
>

<ChevronDown class="size-4" :class="open ? 'rotate-180' : ''"></ChevronDown>
</DropdownMenuTrigger>

<DropdownMenuContent class="">
<DropdownMenuCheckboxItem
v-for="column in group.columns"
:key="column.id"
:checked="column.getIsVisible()"
@select.prevent
@update:checked="
(value) => {
column.toggleVisibility(!!value);
column.setFilterValue([]);
if (!colors.has(column.id)) addColor(column.id);
}
"
>
<span class="flex-1">{{ column.columnDef.header }}</span>
<label v-if="column.getIsVisible()" class="grow-0 basis-0 p-0">
<input
class="size-5"
type="color"
:value="colors.get(column.id)?.colorCode || '#cccccc'"
@click.capture.stop
@input="
(event) => {
//@ts-expect-error target.value not recognized
setColor({ id: column.id, colorCode: event.target!.value });
}
"
/>
<span class="sr-only">Select color</span>
</label>
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</template>
33 changes: 19 additions & 14 deletions components/geojson-map-window-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const props = defineProps<Props>();
const { params } = toRefs(props);
const GeojsonStore = useGeojsonStore();
const { tables } = storeToRefs(GeojsonStore);
const filteredMarkers = computed(() => {
Expand All @@ -26,18 +27,22 @@ const filteredMarkers = computed(() => {
</script>

<template>
<VisualisationContainer
v-slot="{ width, height }"
:class="{ 'opacity-50 grayscale': !filteredMarkers }"
>
<GeoMap
v-if="filteredMarkers"
:height="height"
:markers="filteredMarkers as Array<Feature<Point, MarkerProperties>>"
:width="width"
/>
<Centered v-if="!filteredMarkers">
<LoadingIndicator />
</Centered>
</VisualisationContainer>
<div class="relative isolate grid size-full grid-rows-[auto_1fr]">
<GeojsonMapToolbar v-if="filteredMarkers" :params="params"></GeojsonMapToolbar>
<VisualisationContainer
v-slot="{ width, height }"
:class="{ 'opacity-50 grayscale': !filteredMarkers }"
>
<GeoMap
v-if="filteredMarkers"
:height="height"
:marker-type="params.markerType"
:markers="filteredMarkers as Array<Feature<Point, MarkerProperties>>"
:width="width"
/>
<Centered v-if="!filteredMarkers">
<LoadingIndicator />
</Centered>
</VisualisationContainer>
</div>
</template>
3 changes: 2 additions & 1 deletion components/geojson-table-window-content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@tanstack/vue-table";
import { useGeojsonStore } from "@/stores/use-geojson-store.ts";
import type { FeatureType } from "@/types/global";
import type { FeatureType, MarkerType } from "@/types/global";
const GeojsonStore = useGeojsonStore();
const { addWindow, findWindowByTypeAndParam } = useWindowsStore();
Expand Down Expand Up @@ -132,6 +132,7 @@ function registerTable(table: Table<FeatureType>) {
targetType: "GeojsonMap",
params: {
url,
markerType: "petal" as MarkerType,
},
title: "Variety Data - Map View",
});
Expand Down
4 changes: 2 additions & 2 deletions components/ui/dropdown-menu/DropdownMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const forwarded = useForwardPropsEmits(props, emits);
</script>

<template>
<DropdownMenuRoot v-bind="forwarded">
<slot />
<DropdownMenuRoot v-slot="{ open }" v-bind="forwarded">
<slot :open="open" />
</DropdownMenuRoot>
</template>
57 changes: 57 additions & 0 deletions composables/use-petal-marker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Column } from "@tanstack/vue-table";
import type { Feature as GeoJsonFeature, Point } from "geojson";
import { divIcon, type LatLng, marker } from "leaflet";

import type { MarkerProperties } from "@/lib/api-client";
import { useGeojsonStore } from "@/stores/use-geojson-store.ts";

const GeojsonStore = useGeojsonStore();
const { tables } = storeToRefs(GeojsonStore);
const url = "https://raw.githubusercontent.com/wibarab/wibarab-data/main/wibarab_varieties.geojson";

function getPetalSVG(entries: Array<Column<never>>) {
const div = document.createElement("div");
div.className = "hover:scale-150 transition origin-center relative size-6";
const NUM_PETALS = entries.length;
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "12px");
svg.setAttribute("height", "12px");
svg.classList.add("overflow-visible");
// svg.setHTMLUnsafe(String(petal));
for (const [i, value] of entries.entries()) {
const petal = document.createElementNS("http://www.w3.org/2000/svg", "use");
petal.setAttribute("href", "#petal");

petal.style.fill = `var(--${value.id}, #cccccc)`;
petal.style.transformOrigin = "bottom";
petal.style.transform = `rotate(${String((i * 360) / NUM_PETALS)}deg)`;
petal.classList.add("size-3", "absolute", "ml-1.5");
petal.setAttribute("title", value.id);
svg.appendChild(petal);
}

div.appendChild(svg);

return div;
}

export function usePetalMarker(feature: GeoJsonFeature<Point, MarkerProperties>, latlng: LatLng) {
const table = tables.value.get(url);
const columns = table
?.getVisibleLeafColumns()
.filter(
(col) => col.getCanFilter() && Object.keys(feature.properties).find((k) => k === col.id),
);
//@ts-expect-error missing accessorFn
const htmlContent = getPetalSVG(columns).outerHTML; // Example HTML content
const customIcon = divIcon({
html: htmlContent,
className: "custom-marker-icon size-5", // Add custom CSS class for styling
// iconSize: [30, 30], // Adjust size as needed
});

const leafletMarker = marker(latlng, {
icon: customIcon,
});
return leafletMarker;
}
19 changes: 18 additions & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default defineNuxtConfig({
},

imports: { dirs: ["./config/"] },
modules: ["@pinia/nuxt", "@vueuse/nuxt", "@nuxt/eslint", "@nuxt/test-utils/module"],
modules: ["@pinia/nuxt", "@vueuse/nuxt", "@nuxt/eslint", "@nuxt/test-utils/module", "nuxt-svgo"],
nitro: { compressPublicAssets: true },

postcss: {
Expand Down Expand Up @@ -65,6 +65,23 @@ export default defineNuxtConfig({
currentGitSha: process.env.NUXT_PUBLIC_CURRENT_GIT_SHA,
},
},
svgo: {
defaultImport: "component",
autoImportPath: "./assets/svg/",
svgoConfig: {
plugins: [
{
name: "preset-default",
params: {
overrides: {
removeUselessDefs: false,
cleanupIds: false,
},
},
},
],
},
},

vite: {
vue: {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@vueuse/nuxt": "^10.11.0",
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
"colorjs.io": "^0.4.5",
"colorjs.io": "^0.5.2",
"cva": "1.0.0-beta.1",
"is-ci": "^3.0.1",
"leaflet": "^1.9.4",
Expand All @@ -63,6 +63,7 @@
"nanoid": "^5.0.3",
"npm-run-all2": "^6.1.1",
"nuxt": "^3.13.2",
"nuxt-svgo": "^4.0.9",
"pinia": "^2.1.7",
"pino": "^9.2.0",
"radix-vue": "^1.8.3",
Expand Down Expand Up @@ -91,6 +92,7 @@
"@playwright/test": "^1.39.0",
"@tailwindcss/typography": "^0.5.10",
"@tanstack/eslint-plugin-query": "^5.0.5",
"@types/color": "^4.2.0",
"@types/geojson": "^7946.0.12",
"@types/leaflet": "^1.9.7",
"@types/node": "^22.5.4",
Expand Down
Loading

0 comments on commit 234b7a9

Please sign in to comment.