From 8a2ffa5f63d7186948d843644f71d00943539d97 Mon Sep 17 00:00:00 2001 From: Robin Kaggl <48437174+kaggl@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:59:37 +0100 Subject: [PATCH] Feat hiarchy second try (#82) resolves #81, resolves #52 --------- Co-authored-by: Robin Kaggl --- .vscode/extensions.json | 3 +- components/app-header.vue | 12 +- components/autocomplete.vue | 89 +++++++++++++ components/detail-disclosure.vue | 2 +- components/download-menu.vue | 11 +- components/facet-disclosures.vue | 4 +- components/generic-listbox.vue | 47 +++++++ components/hierarchy-wrapper.vue | 75 +++++++++++ components/info-menu.vue | 11 +- components/locale-switch.vue | 11 +- components/menu-transition.vue | 12 ++ components/search-table.vue | 4 +- components/vis-container.vue | 25 ++++ lib/get-tree-data.ts | 40 ++++++ lib/tree.js | 138 +++++++++++++++++++++ lib/types.ts | 15 ++- locales/de.json | 18 +++ locales/en.json | 18 +++ package.json | 2 + pages/hierarchy.vue | 167 +++++++++++++++++++++++++ pages/search.vue | 14 +-- pnpm-lock.yaml | 207 +++++++++++++++++++++++++------ 22 files changed, 835 insertions(+), 90 deletions(-) create mode 100644 components/autocomplete.vue create mode 100644 components/generic-listbox.vue create mode 100644 components/hierarchy-wrapper.vue create mode 100644 components/menu-transition.vue create mode 100644 components/vis-container.vue create mode 100644 lib/get-tree-data.ts create mode 100644 lib/tree.js create mode 100644 pages/hierarchy.vue diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7a74cd2..54de482 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,7 +6,6 @@ "esbenp.prettier-vscode", "mikestead.dotenv", "stylelint.vscode-stylelint", - "vue.volar", - "vue.vscode-typescript-vue-plugin" + "vue.volar" ] } diff --git a/components/app-header.vue b/components/app-header.vue index e187d4b..b8740c0 100644 --- a/components/app-header.vue +++ b/components/app-header.vue @@ -11,6 +11,7 @@ const links = computed(() => { return { home: { href: { path: localePath("/") }, label: t("pages.home.label") }, search: { href: { path: localePath("/search/persons") }, label: t("pages.search.label") }, + hierarchy: { href: { path: localePath("/hierarchy") }, label: t("pages.hierarchy.label") }, documentation: { href: { path: localePath("/documentation/project") }, label: t("pages.documentation.label"), @@ -52,14 +53,7 @@ const links = computed(() => { Open/Close Menu - + { - + diff --git a/components/autocomplete.vue b/components/autocomplete.vue new file mode 100644 index 0000000..0ec4ece --- /dev/null +++ b/components/autocomplete.vue @@ -0,0 +1,89 @@ + + + diff --git a/components/detail-disclosure.vue b/components/detail-disclosure.vue index 61cdba1..245f777 100644 --- a/components/detail-disclosure.vue +++ b/components/detail-disclosure.vue @@ -104,7 +104,7 @@ const all = props.rels.length; class="grid grid-cols-[1fr_auto] items-center gap-1" :class="linkTo && 'rounded transition hover:bg-slate-200 active:bg-slate-300 p-1 -ml-1'" > -
+
diff --git a/components/facet-disclosures.vue b/components/facet-disclosures.vue index b0a62c0..63c81f0 100644 --- a/components/facet-disclosures.vue +++ b/components/facet-disclosures.vue @@ -19,10 +19,10 @@ const route = useRoute(); const facetObject: Record> = getFacetObjectFromURL(true); -const facetChange = async (facets: Array, field: string) => { +const facetChange = (facets: Array, field: string) => { facetObject[field] = facets; - await router.push({ + void router.push({ query: { ...route.query, page: 1, diff --git a/components/generic-listbox.vue b/components/generic-listbox.vue new file mode 100644 index 0000000..23270d9 --- /dev/null +++ b/components/generic-listbox.vue @@ -0,0 +1,47 @@ + + + diff --git a/components/hierarchy-wrapper.vue b/components/hierarchy-wrapper.vue new file mode 100644 index 0000000..d9b5743 --- /dev/null +++ b/components/hierarchy-wrapper.vue @@ -0,0 +1,75 @@ + + + diff --git a/components/info-menu.vue b/components/info-menu.vue index 88789ef..97b011a 100644 --- a/components/info-menu.vue +++ b/components/info-menu.vue @@ -9,19 +9,12 @@ import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue"; - + - + diff --git a/components/locale-switch.vue b/components/locale-switch.vue index 889a0d7..69d9f4c 100644 --- a/components/locale-switch.vue +++ b/components/locale-switch.vue @@ -21,14 +21,7 @@ defineProps<{ {{ locale.toUpperCase() }} - + @@ -43,7 +36,7 @@ defineProps<{ {{ loc.code.toUpperCase() }} - +
diff --git a/components/menu-transition.vue b/components/menu-transition.vue new file mode 100644 index 0000000..6e0c34e --- /dev/null +++ b/components/menu-transition.vue @@ -0,0 +1,12 @@ + diff --git a/components/search-table.vue b/components/search-table.vue index ef348cc..6e24bbb 100644 --- a/components/search-table.vue +++ b/components/search-table.vue @@ -55,7 +55,7 @@ const limitNum = computed(() => { }); const comQuery = computed(() => { - const query = route.query; + const { query } = route; return { q: String(query.q ?? ""), query_by: props.queryBy, @@ -183,7 +183,7 @@ const getDetailLink = (id: string, entity?: string) => {
+const visContainer = ref(null); +const dimensions = ref(null); + +const observer = new ResizeObserver((entries) => { + const [entry] = entries; + if (entry == null) return; + dimensions.value = entry.contentRect; +}); + +onMounted(() => { + if (visContainer.value == null) return; + observer.observe(visContainer.value); +}); + +onUnmounted(() => { + observer.disconnect(); +}); + + + diff --git a/lib/get-tree-data.ts b/lib/get-tree-data.ts new file mode 100644 index 0000000..956a774 --- /dev/null +++ b/lib/get-tree-data.ts @@ -0,0 +1,40 @@ +interface TreeQuery { + direction?: string; + show?: string; + model: string; + id: string; +} + +interface TreeMeta { + end: string; + entity_type: string; + label: string; + pk: number; + start: string; + url: string; +} + +export interface TreeEntity { + name: string; + meta: TreeMeta; + children?: Array; +} + +interface ReturnTree { + graph_type: string; + tree_data: TreeEntity; +} + +export async function getTreeData(q: TreeQuery) { + const data = await fetch( + `https://viecpro.acdh-dev.oeaw.ac.at/visualisations/api/ + ${q.model}/ + ${q.id}/ + ${q.show ?? "normal"}/ + ${q.direction ?? "down"} + `, + ); + const ret = (await data.json()) as ReturnTree; + + return ret.tree_data; +} diff --git a/lib/tree.js b/lib/tree.js new file mode 100644 index 0000000..74a70fc --- /dev/null +++ b/lib/tree.js @@ -0,0 +1,138 @@ +import * as d3 from "d3"; + +export function Tree( + data, + { + // data is either tabular (array of objects) or hierarchy (nested objects) + path, // as an alternative to id and parentId, returns an array identifier, imputing internal nodes + id = Array.isArray(data) ? (d) => d.id : null, // if tabular data, given a d in data, returns a unique identifier (string) + parentId = Array.isArray(data) ? (d) => d.parentId : null, // if tabular data, given a node d, returns its parent’s identifier + children, // if hierarchical data, given a d in data, returns its children + tree = d3.tree, // layout algorithm (typically d3.tree or d3.cluster) + sort, // how to sort nodes prior to layout (e.g., (a, b) => d3.descending(a.height, b.height)) + label, // given a node d, returns the display name + title, // given a node d, returns its hover text + link, // given a node d, its link (if any) + linkTarget = "_blank", // the target attribute for links (if any) + width = 640, // outer width, in pixels + height, // outer height, in pixels + r = 3, // radius of nodes + padding = 1, // horizontal padding for first and last column + fill = "#999", // fill for nodes + fillOpacity, // fill opacity for nodes + stroke = "#555", // stroke for links + strokeWidth = 1.5, // stroke width for links + strokeOpacity = 0.4, // stroke opacity for links + strokeLinejoin, // stroke line join for links + strokeLinecap, // stroke line cap for links + halo = "#fff", // color of label halo + haloWidth = 3, // padding around the labels + curve = d3.curveBumpX, // curve for the link + fontSize = 10, + } = {}, +) { + // If id and parentId options are specified, or the path option, use d3.stratify + // to convert tabular data to a hierarchy; otherwise we assume that the data is + // specified as an object {children} with nested objects (a.k.a. the “flare.json” + // format), and use d3.hierarchy. + const root = + path != null + ? d3.stratify().path(path)(data) + : id != null || parentId != null + ? d3.stratify().id(id).parentId(parentId)(data) + : d3.hierarchy(data, children); + + // Sort the nodes. + if (sort != null) root.sort(sort); + + // Compute labels and titles. + const descendants = root.descendants(); + const L = label == null ? null : descendants.map((d) => label(d.data, d)); + + // Compute the layout. + const dx = 10 + r; + const dy = (width + r) / (root.height + padding); + tree().nodeSize([dx, dy])(root); + + // Center the tree. + let x0 = Infinity; + let x1 = -x0; + root.each((d) => { + if (d.x > x1) x1 = d.x; + if (d.x < x0) x0 = d.x; + }); + + // Compute the default height. + if (height === undefined) height = x1 - x0 + dx * 2; + + // Use the required curve + if (typeof curve !== "function") throw new Error(`Unsupported curve`); + + const svg = d3 + .create("svg") + .attr("viewBox", [(-dy * padding) / 2, x0 - dx, width, height]) + .attr("width", width) + .attr("height", height) + .attr("style", "max-width: 100%; height: auto; height: intrinsic;") + .attr("font-family", "sans-serif") + .attr("font-size", fontSize); + + svg + .append("g") + .attr("fill", "none") + .attr("stroke", stroke) + .attr("stroke-opacity", strokeOpacity) + .attr("stroke-linecap", strokeLinecap) + .attr("stroke-linejoin", strokeLinejoin) + .attr("stroke-width", strokeWidth) + .selectAll("path") + .data(root.links()) + .join("path") + .attr( + "d", + d3 + .link(curve) + .x((d) => d.y) + .y((d) => d.x), + ); + + const node = svg + .append("g") + .selectAll("a") + .data(root.descendants()) + .join("a") + .attr("href", link == null ? null : (d) => link(d.data, d)) + .attr("transform", (d) => `translate(${d.y},${d.x})`); + + node + .append("circle") + .attr("fill", (d) => { + switch (d.data.data.meta.entity_type) { + case "Institution": + return "tomato"; + case "Funktion": + return "yellowgreen"; + case "Person": + return "lightskyblue"; + default: + return fill; + } + }) + .attr("r", r); + + if (title != null) node.append("title").text((d) => title(d.data, d)); + + if (L) + node + .append("text") + .attr("dy", "0.32em") + .attr("x", (d) => (d.children ? -6 - r : 6 + r)) + .attr("text-anchor", (d) => (d.children ? "end" : "start")) + .attr("paint-order", "stroke") + .attr("stroke", halo) + .attr("stroke-width", haloWidth) + .text((d, i) => L[i]) + .attr("font-size", (d) => (d.data.data.meta.label.length > 20 ? "0.7em" : fontSize)); + + return svg.node(); +} diff --git a/lib/types.ts b/lib/types.ts index ff21777..fd7e4af 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,9 +1,16 @@ -import { type Icon } from "lucide-vue-next"; +import type { Icon } from "lucide-vue-next"; -import { type NuxtLinkProps } from "#app"; +import type { NuxtLinkProps } from "#app"; -export type NavLink = { +export interface NavLink { href: NuxtLinkProps["href"]; label: string; icon?: Icon; -}; +} + +export interface HierarchyNode { + group: string; + label: string; + pk: number; + value?: Array; +} diff --git a/locales/de.json b/locales/de.json index e5efeb8..5a99ae2 100644 --- a/locales/de.json +++ b/locales/de.json @@ -13,6 +13,24 @@ "title": "Datenbank", "label": "Datenbank" }, + "hierarchy": { + "title": "Hierarchie", + "label": "Hierarchie", + "options": { + "up": "Nach Oben", + "down": "Nach Unten", + "normal": "Normal", + "show only institutions": "Institutionen", + "add functions": "Institutionen + Funktionen", + "add functions and persons": "Institutionen + Funktionen + Personen", + "show institution hierarchy": "Institutionshierarchie", + "show amt and persons": "Amt und Personen" + }, + "legend": { + "legend": "Legende", + "function": "Funktion" + } + }, "documentation": { "title": "Dokumentation", "label": "Dokumentation", diff --git a/locales/en.json b/locales/en.json index d63b284..6f4ce81 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,6 +13,24 @@ "title": "Database", "label": "Database" }, + "hierarchy": { + "title": "Hierarchy", + "label": "Hierarchy", + "options": { + "up": "Up", + "down": "Down", + "normal": "Normal", + "show only institutions": "Institutions only", + "add functions": "Institutions and functions", + "add functions and persons": "Institutions, functions and persons", + "show institution hierarchy": "Institutions hierarchy", + "show amt and persons": "Amt and persons" + }, + "legend": { + "legend": "Legend", + "function": "Function" + } + }, "documentation": { "title": "Documentation", "label": "Documentation", diff --git a/package.json b/package.json index 59161cb..1810595 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@nuxtjs/i18n": "^8.0.0", "@tanstack/vue-query": "^5.17.2", "@vueuse/core": "^10.7.1", + "d3": "^7.8.5", "instantsearch.js": "^4.63.0", "is-ci": "^3.0.1", "json-as-xlsx": "^2.5.6", @@ -63,6 +64,7 @@ "@headlessui/tailwindcss": "^0.2.0", "@playwright/test": "^1.40.1", "@tailwindcss/typography": "^0.5.10", + "@types/d3": "^7.4.3", "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.6", "autoprefixer": "^10.4.16", diff --git a/pages/hierarchy.vue b/pages/hierarchy.vue new file mode 100644 index 0000000..09baabe --- /dev/null +++ b/pages/hierarchy.vue @@ -0,0 +1,167 @@ + + + diff --git a/pages/search.vue b/pages/search.vue index 36ede33..104b944 100644 --- a/pages/search.vue +++ b/pages/search.vue @@ -55,15 +55,15 @@ const links = computed(() => { } satisfies Record; }); -const updateFacets = async () => { +const updateFacets = () => { const { query } = route; if (query.facets?.start_date_int) { - await addToFacets(typesenseQueryToFacetObject(String(query.facets)).start_date_int); - } else await addToFacets([1600, 1900]); + addToFacets(typesenseQueryToFacetObject(String(query.facets)).start_date_int); + } else addToFacets([1600, 1900]); }; -const addToFacets = async (range: [number, number]) => { +const addToFacets = (range: [number, number]) => { const { query } = route; const router = useRouter(); const facetObject = typesenseQueryToFacetObject(String(query.facets)); @@ -74,11 +74,11 @@ const addToFacets = async (range: [number, number]) => { if (isEmpty(facetObject)) { delete query.facets; - await router.push({ + void router.push({ query, }); } else { - await router.push({ + void router.push({ query: { ...query, facets: facetObjectToTypesenseQuery(facetObject, false, includeDateless.value), @@ -86,7 +86,7 @@ const addToFacets = async (range: [number, number]) => { }); } } else { - await router.push({ + void router.push({ query: { ...query, facets: facetObjectToTypesenseQuery( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3b12da..e1587b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ dependencies: '@vueuse/core': specifier: ^10.7.1 version: 10.7.1(vue@3.4.5) + d3: + specifier: ^7.8.5 + version: 7.8.5 instantsearch.js: specifier: ^4.63.0 version: 4.63.0(algoliasearch@4.20.0) @@ -100,6 +103,9 @@ devDependencies: '@tailwindcss/typography': specifier: ^0.5.10 version: 0.5.10(tailwindcss@3.4.0) + '@types/d3': + specifier: ^7.4.3 + version: 7.4.3 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -2697,6 +2703,105 @@ packages: minimatch: 9.0.3 dev: false + /@types/d3-array@3.2.1: + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + dev: true + + /@types/d3-axis@3.0.6: + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: true + + /@types/d3-brush@3.0.6: + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: true + + /@types/d3-chord@3.0.6: + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + dev: true + + /@types/d3-color@3.1.3: + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + dev: true + + /@types/d3-contour@3.0.6: + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/geojson': 7946.0.14 + dev: true + + /@types/d3-delaunay@6.0.4: + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + dev: true + + /@types/d3-dispatch@3.0.6: + resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} + dev: true + + /@types/d3-drag@3.0.7: + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: true + + /@types/d3-dsv@3.0.7: + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + dev: true + + /@types/d3-ease@3.0.2: + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + dev: true + + /@types/d3-fetch@3.0.7: + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + dependencies: + '@types/d3-dsv': 3.0.7 + dev: true + + /@types/d3-force@3.0.9: + resolution: {integrity: sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==} + dev: true + + /@types/d3-format@3.0.4: + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + dev: true + + /@types/d3-geo@3.1.0: + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + dependencies: + '@types/geojson': 7946.0.14 + dev: true + + /@types/d3-hierarchy@3.1.6: + resolution: {integrity: sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==} + dev: true + + /@types/d3-interpolate@3.0.4: + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + dependencies: + '@types/d3-color': 3.1.3 + dev: true + + /@types/d3-path@3.1.0: + resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} + dev: true + + /@types/d3-polygon@3.0.2: + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + dev: true + + /@types/d3-quadtree@3.0.6: + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + dev: true + + /@types/d3-random@3.0.3: + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + dev: true + /@types/d3-scale-chromatic@3.0.3: resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==} dev: true @@ -2707,10 +2812,76 @@ packages: '@types/d3-time': 3.0.3 dev: true + /@types/d3-selection@3.0.10: + resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} + dev: true + + /@types/d3-shape@3.1.6: + resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + dependencies: + '@types/d3-path': 3.1.0 + dev: true + + /@types/d3-time-format@4.0.3: + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + dev: true + /@types/d3-time@3.0.3: resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} dev: true + /@types/d3-timer@3.0.2: + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + dev: true + + /@types/d3-transition@3.0.8: + resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} + dependencies: + '@types/d3-selection': 3.0.10 + dev: true + + /@types/d3-zoom@3.0.8: + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.10 + dev: true + + /@types/d3@7.4.3: + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.6 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.9 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.6 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.0 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.8 + '@types/d3-scale-chromatic': 3.0.3 + '@types/d3-selection': 3.0.10 + '@types/d3-shape': 3.1.6 + '@types/d3-time': 3.0.3 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.8 + '@types/d3-zoom': 3.0.8 + dev: true + /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: @@ -2726,7 +2897,6 @@ packages: /@types/geojson@7946.0.14: resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} - dev: false /@types/google.maps@3.54.9: resolution: {integrity: sha512-kovzglL9eC/zsMnhIpBsiuUDPwhNsRDQzjtKDHZ3D4lYHi7l7IgZPE8/yz+I4Wb96cQXkz2W0DcOiF5RaNPovA==} @@ -4794,12 +4964,10 @@ packages: engines: {node: '>=12'} dependencies: internmap: 2.0.3 - dev: true /d3-axis@3.0.0: resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} engines: {node: '>=12'} - dev: true /d3-brush@3.0.0: resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} @@ -4810,38 +4978,32 @@ packages: d3-interpolate: 3.0.1 d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - dev: true /d3-chord@3.0.1: resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} engines: {node: '>=12'} dependencies: d3-path: 3.1.0 - dev: true /d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} - dev: true /d3-contour@4.0.2: resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} engines: {node: '>=12'} dependencies: d3-array: 3.2.4 - dev: true /d3-delaunay@6.0.4: resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} engines: {node: '>=12'} dependencies: delaunator: 5.0.0 - dev: true /d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} - dev: true /d3-drag@3.0.0: resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} @@ -4849,7 +5011,6 @@ packages: dependencies: d3-dispatch: 3.0.1 d3-selection: 3.0.0 - dev: true /d3-dsv@3.0.1: resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} @@ -4859,19 +5020,16 @@ packages: commander: 7.2.0 iconv-lite: 0.6.3 rw: 1.3.3 - dev: true /d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} - dev: true /d3-fetch@3.0.1: resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} engines: {node: '>=12'} dependencies: d3-dsv: 3.0.1 - dev: true /d3-force@3.0.0: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} @@ -4880,31 +5038,26 @@ packages: d3-dispatch: 3.0.1 d3-quadtree: 3.0.1 d3-timer: 3.0.1 - dev: true /d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} - dev: true /d3-geo@3.1.0: resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} engines: {node: '>=12'} dependencies: d3-array: 3.2.4 - dev: true /d3-hierarchy@3.1.2: resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} engines: {node: '>=12'} - dev: true /d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} dependencies: d3-color: 3.1.0 - dev: true /d3-path@1.0.9: resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} @@ -4913,22 +5066,18 @@ packages: /d3-path@3.1.0: resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} engines: {node: '>=12'} - dev: true /d3-polygon@3.0.1: resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} engines: {node: '>=12'} - dev: true /d3-quadtree@3.0.1: resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} engines: {node: '>=12'} - dev: true /d3-random@3.0.1: resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} engines: {node: '>=12'} - dev: true /d3-sankey@0.12.3: resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} @@ -4943,7 +5092,6 @@ packages: dependencies: d3-color: 3.1.0 d3-interpolate: 3.0.1 - dev: true /d3-scale@4.0.2: resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} @@ -4954,12 +5102,10 @@ packages: d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 - dev: true /d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} - dev: true /d3-shape@1.3.7: resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} @@ -4972,26 +5118,22 @@ packages: engines: {node: '>=12'} dependencies: d3-path: 3.1.0 - dev: true /d3-time-format@4.1.0: resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} engines: {node: '>=12'} dependencies: d3-time: 3.1.0 - dev: true /d3-time@3.1.0: resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} engines: {node: '>=12'} dependencies: d3-array: 3.2.4 - dev: true /d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - dev: true /d3-transition@3.0.1(d3-selection@3.0.0): resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} @@ -5005,7 +5147,6 @@ packages: d3-interpolate: 3.0.1 d3-selection: 3.0.0 d3-timer: 3.0.1 - dev: true /d3-zoom@3.0.0: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} @@ -5016,7 +5157,6 @@ packages: d3-interpolate: 3.0.1 d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - dev: true /d3@7.8.5: resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==} @@ -5052,7 +5192,6 @@ packages: d3-timer: 3.0.1 d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 - dev: true /dagre-d3-es@7.0.10: resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==} @@ -5170,7 +5309,6 @@ packages: resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==} dependencies: robust-predicates: 3.0.2 - dev: true /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -6593,7 +6731,6 @@ packages: /internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - dev: true /ioredis@5.3.2: resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} @@ -9719,7 +9856,6 @@ packages: /robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - dev: true /rollup-plugin-visualizer@5.12.0(rollup@4.9.3): resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==} @@ -9775,7 +9911,6 @@ packages: /rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - dev: true /sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}