From 4ed701e6f04d32fb2238d39d3d91f784e603268b Mon Sep 17 00:00:00 2001 From: jamiefeiss Date: Tue, 1 Aug 2023 17:11:13 +1000 Subject: [PATCH 1/3] Added basic FAIR & CARE score widgets --- .env | 1 + env.d.ts | 1 + src/components/CircleProgress.vue | 76 +++++++++++++++++++++++++++ src/components/navs/RightSideBar.vue | 8 +++ src/components/scores/CareScore.vue | 57 ++++++++++++++++++++ src/components/scores/FairScore.vue | 57 ++++++++++++++++++++ src/components/scores/ScoreBadge.vue | 13 +++++ src/components/scores/ScoreWidget.vue | 22 ++++++++ src/config.ts | 1 + src/main.ts | 3 +- src/types.ts | 1 + src/util/helpers.ts | 4 ++ src/views/PropTableView.vue | 48 +++++++++++++++-- 13 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 src/components/CircleProgress.vue create mode 100644 src/components/scores/CareScore.vue create mode 100644 src/components/scores/FairScore.vue create mode 100644 src/components/scores/ScoreBadge.vue create mode 100644 src/components/scores/ScoreWidget.vue diff --git a/.env b/.env index 8a4566f..d9ebe78 100644 --- a/.env +++ b/.env @@ -3,6 +3,7 @@ ## --------------------------------------- VITE_SIDENAV=true VITE_ENABLED_PREZS=CatPrez,SpacePrez,VocPrez +VITE_ENABLE_SCORES=true ## --------------------------------------- ## MAP DISPLAY DEFAULT SETTINGS diff --git a/env.d.ts b/env.d.ts index b10c65f..3002a68 100644 --- a/env.d.ts +++ b/env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_SIDENAV: string; // true | false readonly VITE_ENABLED_PREZS: string; // CatPrez | SpacePrez | VocPrez comma separated + readonly VITE_ENABLE_SCORES: string; // true | false readonly VITE_API_BASE_URL: string; readonly VITE_MAP_SETTINGS_API_KEY: string; readonly VITE_MAP_SETTINGS_OPTIONS_CENTER_LAT: number; diff --git a/src/components/CircleProgress.vue b/src/components/CircleProgress.vue new file mode 100644 index 0000000..a3f0d67 --- /dev/null +++ b/src/components/CircleProgress.vue @@ -0,0 +1,76 @@ + + + + + \ No newline at end of file diff --git a/src/components/navs/RightSideBar.vue b/src/components/navs/RightSideBar.vue index 235348b..4b85347 100644 --- a/src/components/navs/RightSideBar.vue +++ b/src/components/navs/RightSideBar.vue @@ -20,11 +20,13 @@ const teleportChildren = computed(() => { @@ -37,6 +39,12 @@ const teleportChildren = computed(() => { display: flex; flex-direction: column; gap: 12px; + + #score-teleport { + display: flex; + flex-direction: column; + gap: 12px; + } } hr { diff --git a/src/components/scores/CareScore.vue b/src/components/scores/CareScore.vue new file mode 100644 index 0000000..ca8f07e --- /dev/null +++ b/src/components/scores/CareScore.vue @@ -0,0 +1,57 @@ + + + + + \ No newline at end of file diff --git a/src/components/scores/FairScore.vue b/src/components/scores/FairScore.vue new file mode 100644 index 0000000..5f36416 --- /dev/null +++ b/src/components/scores/FairScore.vue @@ -0,0 +1,57 @@ + + + + + \ No newline at end of file diff --git a/src/components/scores/ScoreBadge.vue b/src/components/scores/ScoreBadge.vue new file mode 100644 index 0000000..69dc869 --- /dev/null +++ b/src/components/scores/ScoreBadge.vue @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/src/components/scores/ScoreWidget.vue b/src/components/scores/ScoreWidget.vue new file mode 100644 index 0000000..8f1b838 --- /dev/null +++ b/src/components/scores/ScoreWidget.vue @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 9b7b5fb..88ed457 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ export default { sidenav: import.meta.env.VITE_SIDENAV, enabledPrezs: import.meta.env.VITE_ENABLED_PREZS, + enableScores: import.meta.env.VITE_ENABLE_SCORES, apiBaseUrl: import.meta.env.VITE_API_BASE_URL, map: { settings: { diff --git a/src/main.ts b/src/main.ts index 323eb70..d5505a6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import pinia from "@/stores/pinia"; import App from "@/App.vue"; import router from "@/router"; import config from "@/config"; -import { sidenavConfigKey, enabledPrezsConfigKey, apiBaseUrlConfigKey, mapConfigKey } from "@/types"; +import { sidenavConfigKey, enabledPrezsConfigKey, apiBaseUrlConfigKey, mapConfigKey, enableScoresKey } from "@/types"; import VueGoogleMaps from '@fawmi/vue-google-maps' @@ -13,6 +13,7 @@ const app = createApp(App); app.provide(sidenavConfigKey, config.sidenav === "true"); app.provide(enabledPrezsConfigKey, config.enabledPrezs.split(",")); +app.provide(enableScoresKey, config.enableScores === "true"); app.provide(apiBaseUrlConfigKey, config.apiBaseUrl.replace(/\/$/, "")); app.provide(mapConfigKey, config.map); diff --git a/src/types.ts b/src/types.ts index 86307fa..9718f20 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ export type PrezFlavour = "CatPrez" | "SpacePrez" | "VocPrez"; export const mapConfigKey: InjectionKey = Symbol(); export const sidenavConfigKey: InjectionKey = Symbol(); export const enabledPrezsConfigKey: InjectionKey = Symbol(); +export const enableScoresKey: InjectionKey = Symbol(); export const apiBaseUrlConfigKey: InjectionKey = Symbol(); export interface MapConfig { diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 7a7b9bd..fd8a137 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -29,3 +29,7 @@ export function ensureProfiles() { export function copyToClipboard(text: string) { navigator.clipboard.writeText(text.trim()); } + +export function titleCase(s: string): string { + return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); +} \ No newline at end of file diff --git a/src/views/PropTableView.vue b/src/views/PropTableView.vue index fbaeda1..c18be9d 100644 --- a/src/views/PropTableView.vue +++ b/src/views/PropTableView.vue @@ -5,7 +5,7 @@ import { BlankNode, DataFactory, Quad, Store, Literal } from "n3"; import { useUiStore } from "@/stores/ui"; import { useRdfStore } from "@/composables/rdfStore"; import { useGetRequest } from "@/composables/api"; -import { apiBaseUrlConfigKey, type ListItem, type AnnotatedQuad, type Breadcrumb, type Concept, type PrezFlavour, type Profile, type ListItemExtra, type ListItemSortable } from "@/types"; +import { apiBaseUrlConfigKey, enableScoresKey, type ListItem, type AnnotatedQuad, type Breadcrumb, type Concept, type PrezFlavour, type Profile, type ListItemExtra, type ListItemSortable } from "@/types"; import PropTable from "@/components/proptable/PropTable.vue"; import ConceptComponent from "@/components/ConceptComponent.vue"; import AdvancedSearch from "@/components/search/AdvancedSearch.vue"; @@ -16,11 +16,13 @@ import MapClient from "@/components/MapClient.vue"; import type { WKTResult } from "@/stores/mapSearchStore.d"; import SortableTabularList from "@/components/SortableTabularList.vue"; import LoadingMessage from "@/components/LoadingMessage.vue"; -import { ensureProfiles } from "@/util/helpers"; +import { ensureProfiles, titleCase } from "@/util/helpers"; +import ScoreWidget from "@/components/scores/ScoreWidget.vue"; const { namedNode } = DataFactory; const apiBaseUrl = inject(apiBaseUrlConfigKey) as string; +const enableScores = inject(enableScoresKey) as boolean; const route = useRoute(); const ui = useUiStore(); const { store, prefixes, parseIntoStore, qnameToIri, iriToQname } = useRdfStore(); @@ -49,7 +51,8 @@ const childrenPredicate = ref(""); const hiddenPredicates = ref([ qnameToIri("a"), qnameToIri("dcterms:identifier"), - qnameToIri("prez:count") + qnameToIri("prez:count"), + "https://linked.data.gov.au/def/scores/hasScore" ]); const defaultProfile = ref(null); const childrenConfig = ref({ @@ -59,6 +62,8 @@ const childrenConfig = ref({ buttonTitle: "", buttonLink: "" }); +const hasScores = ref(false); +const scores = ref<{[key: string]: {[key: string]: number}}>({}); // {fair: {f: 0, a: 0, i: 0, r: 0}, ...} function configByBaseClass(baseClass: string) { item.value.baseClass = baseClass; @@ -163,6 +168,8 @@ function getProperties() { link: `/object?uri=${item.value.iri}` }) }, q.object, namedNode(qnameToIri("geo:asWKT")), null, null) + } else if (q.predicate.value === "https://linked.data.gov.au/def/scores/hasScore" && enableScores && !hasScores.value) { + hasScores.value = true; } if (!isAltView.value) { @@ -174,12 +181,42 @@ function getProperties() { } }, subject, null, null, null); + if (hasScores.value) { + getScores(); + } + // set the item title after the item title has been set geoResults.value.forEach(result => { result.label = item.value.title ? item.value.title : item.value.iri }); } +function getScore(scoreName: string, normalised: boolean = false): {[key: string]: number} { + const scores: {[key: string]: number} = {}; + + store.value.forObjects(o => { + store.value.forEach(q => { + store.value.forObjects(o2 => { + store.value.forEach(q2 => { + const match = q2.predicate.value.match(`https:\/\/linked.data.gov.au\/def\/scores\/${scoreName}([A-Z]){1}Score${normalised ? "Normalised" : ""}`); + if (match) { + scores[match[1].toLowerCase()] = Number(q2.object.value); + } + }, o2, null, null, null); + }, q.subject, namedNode("http://purl.org/linked-data/cube#observation"), null); + }, o, namedNode(qnameToIri("a")), namedNode(`https://linked.data.gov.au/def/scores/${titleCase(scoreName)}Score${normalised ? "Normalised" : ""}`), null); + }, namedNode(item.value.iri), namedNode("https://linked.data.gov.au/def/scores/hasScore"), null); + + return scores; +} + +function getScores() { + scores.value = { + fair: getScore("fair"), + care: getScore("care"), + }; +} + function getBreadcrumbs(): Breadcrumb[] { // if /object, then use home/object/ // else, build out the breadcrumbs using the URL path @@ -558,9 +595,12 @@ onMounted(() => { - + + + + From 0c98ed5f54de6867d7c6e7f9fbbbf1f9f2026c74 Mon Sep 17 00:00:00 2001 From: jamiefeiss Date: Mon, 7 Aug 2023 14:35:17 +1000 Subject: [PATCH 2/3] Added modals for FAIR & CARE scores, added smooth linear colour scale option for CircleProgress --- src/components/BaseModal.vue | 2 +- src/components/CircleProgress.vue | 29 ++++-- src/components/scores/CareScore.vue | 120 +++++++++++++++++++++++-- src/components/scores/FairScore.vue | 121 ++++++++++++++++++++++++-- src/components/scores/ScoreWidget.vue | 1 - 5 files changed, 253 insertions(+), 20 deletions(-) diff --git a/src/components/BaseModal.vue b/src/components/BaseModal.vue index 37526ca..b5dab50 100644 --- a/src/components/BaseModal.vue +++ b/src/components/BaseModal.vue @@ -107,7 +107,7 @@ onUnmounted(() => { $borderColor: #c9c9c9; display: flex; flex-direction: column; - padding: 8px; + padding: 16px; gap: 8px; overflow-y: auto; border-top: 1px solid $borderColor; diff --git a/src/components/CircleProgress.vue b/src/components/CircleProgress.vue index a3f0d67..9c9a8fa 100644 --- a/src/components/CircleProgress.vue +++ b/src/components/CircleProgress.vue @@ -7,6 +7,8 @@ const props = defineProps<{ max?: number; percentage?: number; label?: string; + tickWhenComplete?: boolean; + gradient?: boolean; // tooltip?: boolean; }>(); @@ -21,24 +23,39 @@ const percent = computed(() => { }); const percentColour = computed(() => { - if (percent.value < 30) { - return "255, 0, 0"; // red - } else if (percent.value < 70) { + if (percent.value < 34) { + return "230, 0, 0"; // red + } else if (percent.value <= 80) { return "255, 165, 0"; // orange - } else if (percent.value <= 100) { - return "0, 255, 0"; // green + } else if (percent.value < 100) { + return "175, 230, 0"; // light yellowish-green + } else if (percent.value == 100) { + return "0, 230, 38"; // green } else { return "128, 128, 128"; // grey, invalid } }); + +const percentColourHsl = computed(() => { + const startColour = 0; // red + const endColour = 75; // light yellowish-green + const completeColour = 130; // green + + const hue = percent.value === 100 ? completeColour : (endColour - startColour) * percent.value / 100; + + return `${hue}, 100%, 45%`; + + // 0, 0%, 50% // invalid +});