From ef97998f37f53d32ef895bc3aef1473014884905 Mon Sep 17 00:00:00 2001 From: Paul Pestov <10750176+paulpestov@users.noreply.github.com> Date: Sat, 28 Jan 2023 23:28:28 +0100 Subject: [PATCH] Generalize dynamic coloring for metrics, fix display for additional metrics --- src/assets/app.scss | 21 +++ src/components/Workflows.vue | 5 +- src/components/workflows/WorkflowsList.vue | 156 +++++++++-------- src/components/workflows/WorkflowsTable.vue | 61 ++++--- src/helpers/api.js | 181 +------------------- src/helpers/eval-colors.js | 74 -------- src/helpers/shorten-cer.js | 10 +- src/helpers/store.js | 16 +- src/helpers/utils.js | 73 ++++++++ src/locales/de.json | 4 +- src/locales/en.json | 5 +- 11 files changed, 241 insertions(+), 365 deletions(-) delete mode 100644 src/helpers/eval-colors.js create mode 100644 src/helpers/utils.js diff --git a/src/assets/app.scss b/src/assets/app.scss index d134308..2cee20b 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -80,3 +80,24 @@ h3 { ----margin-right: 0.5rem; } } + +.collapsible { + .collapsible-item { + display: flex; + flex-direction: column; + } + .collapsible-header { + font-size: var(--font-size--md); + text-overflow: ellipsis; + white-space: nowrap; + padding-right: 32px !important; + display: inline !important; + overflow: hidden; + + .icon { + position: absolute !important; + right: 10px; + top: 12px; + } + } +} diff --git a/src/components/Workflows.vue b/src/components/Workflows.vue index 3cbfd1a..a15fb2b 100644 --- a/src/components/Workflows.vue +++ b/src/components/Workflows.vue @@ -25,7 +25,7 @@ import WorkflowsList from "@/components/workflows/WorkflowsList.vue"; import WorkflowsTable from "@/components/workflows/WorkflowsTable.vue"; import { useI18n } from "vue-i18n"; - import { setEvalColors } from "@/helpers/eval-colors"; + import { setEvalColors } from "@/helpers/utils"; import { store } from "@/helpers/store"; import MultiFilter from "@/components/workflows/MultiFilter.vue"; @@ -68,7 +68,10 @@ store.setRepos(await api.getProjects()); data.value = await api.getWorkflows(); + store.setEvaluations(data.value); + defs.value = await api.getEvalDefinitions(); + store.setMetricDefinitions(defs.value); filteredData.value = data.value; diff --git a/src/components/workflows/WorkflowsList.vue b/src/components/workflows/WorkflowsList.vue index 5c21db4..b46a5c8 100644 --- a/src/components/workflows/WorkflowsList.vue +++ b/src/components/workflows/WorkflowsList.vue @@ -2,7 +2,7 @@ <div> <div class="_display:flex _margin-bottom:4"> <div class="_display:flex _align-items:center _margin-left:auto"> - <p class="_margin-right:2">{{ $t('sort_by')}}:</p> + <p class="_margin-right:2">{{ $t('sort_by') }}:</p> <i-select v-model="sortBy" :options="sortOptions" @@ -20,82 +20,94 @@ <i-badge class="bg-gray-300 text-gray-700 _margin-left:2">Model: {{ item.metadata.workflow_model }}</i-badge> <template v-if="item.metadata.document_metadata"> <i-badge - v-for="font in item.metadata.document_metadata.data_properties.fonts" - :key="font" - class="_margin-left:1 bg-gray-300 text-gray-700"> - {{font}} + v-for="font in item.metadata.document_metadata.data_properties.fonts" + :key="font" + class="_margin-left:1 bg-gray-300 text-gray-700"> + {{ font }} </i-badge> </template> </div> </template> <template #default> <i-row> - <i-column xs="7"> - <i-row class="_margin-top:2"> - <i-column> - <template v-if="item.metadata.gt_workspace"> - <i-collapsible class="_font-size:sm"> - <i-collapsible-item :title="item.metadata.gt_workspace.label"> - <i-row v-if="item.metadata.document_metadata.data_properties"> - <i-column> - <p class="_font-weight:bold">{{ $t('number_of_pages') }}:</p> - <p>{{ item.metadata.document_metadata.data_properties.number_of_pages }}</p> - <p class="mt-2 _font-weight:bold">{{ $t('publication_year') }}:</p> - <p> {{ item.metadata.document_metadata.data_properties.publication_year }}</p> - </i-column> - <i-column> - <p class="_font-weight:bold">{{ $t('layout') }}:</p> - <p>{{ item.metadata.document_metadata.data_properties.layout }}</p> - </i-column> - </i-row> - <i-row v-else><i-column>{{ $t('no_document_metadata')}}</i-column></i-row> - </i-collapsible-item> - </i-collapsible> - </template> - <template v-else> - <p class="_font-weight:bold text-gray-400 mt-3">{{$t('no_gt_workspace')}}</p> - </template> + <i-column xs="5"> + <i-row> + <i-column xs="3" class="_font-weight:semibold">{{ $t('document') }}:</i-column> + <i-column xs="9" v-if="item.metadata.gt_workspace"> + <i-collapsible size="md" class="_font-size:sm _flex-grow:1"> + <i-collapsible-item :title="item.metadata.gt_workspace.label"> + <i-row v-if="item.metadata.document_metadata.data_properties"> + <i-column> + <p class="_font-weight:bold">{{ $t('number_of_pages') }}:</p> + <p>{{ item.metadata.document_metadata.data_properties.number_of_pages }}</p> + <p class="mt-2 _font-weight:bold">{{ $t('publication_year') }}:</p> + <p> {{ item.metadata.document_metadata.data_properties.publication_year }}</p> + </i-column> + <i-column> + <p class="_font-weight:bold">{{ $t('layout') }}:</p> + <p>{{ item.metadata.document_metadata.data_properties.layout }}</p> + </i-column> + </i-row> + <i-row v-else> + <i-column>{{ $t('no_document_metadata') }}</i-column> + </i-row> + </i-collapsible-item> + </i-collapsible> </i-column> - <i-column class="_display:flex-1"> - <i-collapsible class="_font-size:sm"> + <template v-else> + <p class="_font-weight:bold text-gray-400 mt-3">{{ $t('no_gt_workspace') }}</p> + </template> + </i-row> + + <i-row class="_display:flex _margin-top:1"> + <i-column xs="3" class="_font-weight:semibold">{{ $t('workflow') }}:</i-column> + <i-column xs="9"> + <i-collapsible size="md" class="_font-size:sm _flex-grow:1"> <i-collapsible-item :title="item.metadata.ocr_workflow?.label || $t('unknown_workflow')"> <div class="_display:flex _flex-direction:column" v-if="item.metadata.workflow_steps"> - <span class="_margin-bottom:1">{{ $t('workflow_steps')}}:</span> - <span v-for="({id, url}, i) in item.metadata.workflow_steps" :key="id"> - <span>{{ i + 1 }}. </span> - <i-badge size="lg" class="_font-size:sm _margin-bottom:1/2"> - <a v-if="url" :href="url" target="_blank" :title="$t('external_repo_url')" class="_display:flex _align-items:flex-end"> - {{ id }} + <div class="_margin-bottom:2" v-for="({id, url, params }, i) in item.metadata.workflow_steps" :key="id"> + <div class="_display:flex _align-items:center"> + <span class="_margin-right:1">{{ i + 1 }}. </span> + <a v-if="url" :href="url" target="_blank" :title="$t('external_repo_url')" + class="_display:flex _align-items:center _flex-shrink:0"> + <span class="_font-weight:semibold _font-size:md">{{ id }}</span> <i class="repo-icon _margin-left:1/3" v-html="getIcon('external-link')"></i> - </a> - <template v-else>{{ id }}</template> - </i-badge> - </span> + </a></div> + <div class="_display:flex _flex-wrap:wrap _align-items:flex-start _margin-top:1"> + <div + v-for="{ name, value } in params" + :key="name" + class="_margin-bottom:1 _margin-right:1 _display:flex _align-items:flex-start" + style="line-height:1.2" + > + <span class="_border-top-left-radius _border-bottom-left-radius _background:gray-20 _color:gray-70 _font-weight:semibold _padding-x:1 _padding-y:1/2">{{name }}</span> + <span class="_border-top-right-radius _border-bottom-right-radius _background:gray-10 _color:gray-70 _padding-x:1 _padding-y:1/2">{{value }}</span> + </div> + </div> + </div> </div> <template v-else> - <span class="_font-weight:bold">{{$t('no_ocr_workflow')}}</span> + <span class="_font-weight:bold">{{ $t('no_ocr_workflow') }}</span> </template> </i-collapsible-item> </i-collapsible> </i-column> </i-row> </i-column> - <i-column xs="5" class="_margin-left:auto"> - <i-row> - <i-column class="_display:flex _justify-content:center" v-for="(evalKey, i) in evals" :key="i"> - <span class="_font-weight:bold _font-size:xs">{{defs[evalKey] ? defs[evalKey].label : evalKey}}</span> - </i-column> - </i-row> + <i-column xs="7" class="_margin-left:auto"> <i-row> - <i-column v-for="({ name, value }, i) in item.evaluations" :key="i" class="_text-align:center"> - <i-badge - size="lg" - class="metric _cursor:pointer _padding-x:1" - :class="getEvalColor(name, value)" :title="value"> - <template v-if=" name === 'cer'">{{ shortenCER(value) }}</template> - <template v-else-if="name === 'cer_min_max'">{{ shortenCER(value[0]) + '/' + shortenCER(value[1])}}</template> - <template v-else>{{ value }}</template> - </i-badge> + <i-column v-for="({ name, value }, i) in item.evaluations" :key="i" + class="_display:flex _flex-direction:column _align-items:center _padding-x:1/2"> + <span + class="_font-weight:bold _font-size:xs _margin-bottom:1">{{ + defs[name] ? defs[name].label : name + }}</span> + <i-badge + size="lg" + class="metric _cursor:pointer _padding-x:1" + :class="getEvalColor(name, value)" :title="value"> + {{ createReadableMetricValue(name, value) }} + </i-badge> </i-column> </i-row> </i-column> @@ -107,10 +119,11 @@ <script setup> import { ref, onMounted, watch } from "vue"; -import { getEvalColor } from "@/helpers/eval-colors"; import { useI18n } from "vue-i18n"; import { getIcon } from "@/helpers/icon"; import { store } from "@/helpers/store"; +import { createReadableMetricValue, getEvalColor } from "@/helpers/utils"; + const props = defineProps(['data', 'defs']); const list = ref([]); @@ -215,14 +228,18 @@ const sortByCERMax = (order = 'asc') => { const mapMetadata = ({ workflow_model = t('no_workflow_model'), - document_metadata = { - fonts: [] - }, + document_metadata = { fonts: [] }, gt_workspace = null, ocr_workflow = null, - workflow_steps = null + workflow_steps = {} }) => { - workflow_steps = workflow_steps.map(step => ({ id: step, url: getRepoUrl(step) })); + workflow_steps = workflow_steps + .map(step => { + const id = Object.keys(step)[0]; + const params = Object.keys(step[id]).map(paramKey => ({ name: paramKey, value: step[id][paramKey] })); + + return { id, url: getRepoUrl(id), params }; + }); return { workflow_model, document_metadata, @@ -240,7 +257,7 @@ const mapEvaluationResults = ({ document_wide = [] }) => { }; const setListData = (data) => { - list.value = data.map(({ label, evaluation_results = [], metadata }) => ({ + list.value = data.map(({ label, evaluation_results = {}, metadata }) => ({ label, metadata: mapMetadata(metadata), evaluations: mapEvaluationResults(evaluation_results) @@ -264,13 +281,9 @@ const setEvals = (data) => { : []; }; -const shortenCER = (value) => { - return Math.round(value * 1000) / 1000; -}; - onMounted(() => { - setEvals(props.data); - setListData(props.data); + setEvals(props.data); + setListData(props.data); }); watch(() => props.data, () => { @@ -296,6 +309,7 @@ watch(() => props.data, () => { } .arrow-icon, .repo-icon { + position: relative; width: 16px; height: 16px; diff --git a/src/components/workflows/WorkflowsTable.vue b/src/components/workflows/WorkflowsTable.vue index 2acf347..1c2332d 100644 --- a/src/components/workflows/WorkflowsTable.vue +++ b/src/components/workflows/WorkflowsTable.vue @@ -2,7 +2,7 @@ <div> <div class="_display:flex _margin-bottom:4" v-if="evals.length > 0"> <div class="_display:flex _align-items:center _margin-left:auto"> - <p class="_margin-right:2">{{ $t('group_by')}}:</p> + <p class="_margin-right:2">{{ $t('group_by') }}:</p> <i-select v-model="sortBy" :options="sortOptions" @@ -15,17 +15,17 @@ </div> <i-table v-if="evals.length > 0" class="_width:100%" condensed border="true"> <thead> - <tr> + <tr> <th class="_padding-left:2">{{ sortBy.value === 'documents' ? $t('documents') : $t('workflows') }}</th> <th class="_padding-left:2">{{ sortBy.value === 'documents' ? $t('workflows') : $t('documents') }}</th> <th v-for="(evalKey, i) in evals" :key="i"> <span class="def-label _display:flex _align-items:center _justify-content:center _cursor:pointer"> - {{defs[evalKey] ? defs[evalKey].label : evalKey}} - <i-icon name="ink-info" /> + {{ defs[evalKey] ? defs[evalKey].label : evalKey }} + <i-icon name="ink-info"/> <div class="def-tooltip"> <i-card> {{ defs[evalKey] ? defs[evalKey].short_descr : $t('no_description') }}. - <a v-if="defs[evalKey]" :href="defs[evalKey].url">{{ $t('details')}}</a> + <a v-if="defs[evalKey]" :href="defs[evalKey].url">{{ $t('details') }}</a> </i-card> </div> </span> @@ -33,29 +33,27 @@ </tr> </thead> <tbody> - <template v-for="(key, i) in Object.keys(groupedData)" :key="i"> - <tr v-for="(subject, j) in groupedData[key].subjects" :key="j"> - <td v-if="j === 0" :rowspan="groupedData[key].subjects.length" class="_vertical-align:top _padding-left:2"> - <span class="_font-weight:bold">{{ groupedData[key].label }}</span> - </td> - <td class="_vertical-align:top _padding-left:2">{{ subject.label }}</td> - <td - v-for="({ name, value }, k) in subject.evaluations" - :key="k" - class="_text-align:center" - :class="(j === groupedData[key].subjects.length - 1) ? '_padding-bottom:5' : ''" - > - <i-badge - size="lg" - class="metric _cursor:pointer _padding-x:1" - :class="getEvalColor(name, value)"> - <template v-if=" name === 'cer'">{{ shortenCER(value) }}</template> - <template v-else-if="name === 'cer_min_max'">{{ shortenCER(value[0]) + '/' + shortenCER(value[1])}}</template> - <template v-else>{{ value }}</template> - </i-badge> - </td> - </tr> - </template> + <template v-for="(key, i) in Object.keys(groupedData)" :key="i"> + <tr v-for="(subject, j) in groupedData[key].subjects" :key="j"> + <td v-if="j === 0" :rowspan="groupedData[key].subjects.length" class="_vertical-align:top _padding-left:2"> + <span class="_font-weight:bold">{{ groupedData[key].label }}</span> + </td> + <td class="_vertical-align:top _padding-left:2">{{ subject.label }}</td> + <td + v-for="({ name, value }, k) in subject.evaluations" + :key="k" + class="_text-align:center" + :class="(j === groupedData[key].subjects.length - 1) ? '_padding-bottom:5' : ''" + > + <i-badge + size="lg" + class="metric _cursor:pointer _padding-x:1" + :class="getEvalColor(name, value)"> + {{ createReadableMetricValue(name, value) }} + </i-badge> + </td> + </tr> + </template> </tbody> </i-table> <div>{{ $t('no_table_data') }}</div> @@ -65,8 +63,7 @@ <script setup> import { watch, ref } from "vue"; import { useI18n } from "vue-i18n"; -import { getEvalColor } from "@/helpers/eval-colors"; -import { shortenCER } from "@/helpers/shorten-cer"; +import { createReadableMetricValue, getEvalColor } from "@/helpers/utils"; const { t } = useI18n(); const props = defineProps(['data', 'defs']); @@ -151,12 +148,14 @@ watch(() => props.data, groupByDocuments, { immediate: true }); .def-label { position: relative; + &:hover { .def-tooltip { visibility: visible; } } } + .def-tooltip { visibility: hidden; position: absolute; @@ -172,6 +171,6 @@ watch(() => props.data, groupByDocuments, { immediate: true }); } th, th span { - font-weight:bold; + font-weight: bold; } </style> diff --git a/src/helpers/api.js b/src/helpers/api.js index 4b60dda..585f0d8 100644 --- a/src/helpers/api.js +++ b/src/helpers/api.js @@ -1,183 +1,4 @@ -const baseUrl = 'https://raw.githubusercontent.com/OCR-D/quiver-back-end/main/data'; -const workflowsJson = [ - { - "@id": "https://github.com/OCR-D/quiver/tree/data/evaluations/wf1-data345-eval1.json", - "label": "OCR workflow 1 on workspace 345", - "metadata": { - "ocr_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/1.nf", - "label": "OCR Workflow 1" - }, - "eval_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/eval1.nf", - "label": "Evaluation Workflow 1" - }, - "gt_workspace": { - "@id": "https://gt.ocr-d.de/workspace/789", - "label": "GT workspace 789 (19th century fraktur)" - }, - "ocr_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/3000.ocrd.zip", - "label": "OCR result workspace 3000" - }, - "eval_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/345.ocrd.zip", - "label": "Evaluation Workspace 345" - }, - "workflow_steps": { - "0": "Processor A", - "1": "Processor B" - }, - "workflow_model": "Fraktur_GT4HistOCR", - "document_metadata": { - "fonts": [ - "antiqua", - "fraktur" - ], - "publication_century": "1800-1900", - "publication_decade": "1850-1860", - "publication_year": 1855, - "number_of_pages": 100, - "layout": "simple" - } - }, - "evaluation": { - "document_wide": { - "wall_time": 1234, - "cer": 0.57, - "cer_min_max": [ - 0.2, - 0.57 - ] - }, - "by_page": [ - { - "page_id": "PHYS_0001", - "cer": 0.8, - "processing_time": 2.1 - } - ] - } - }, - { - "@id": "https://github.com/OCR-D/quiver/tree/data/evaluations/wf2-data345-eval1.json", - "label": "OCR Workflow 2 on Data 345", - "metadata": { - "ocr_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/2.nf", - "label": "OCR Workflow 2" - }, - "eval_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/eval1.nf", - "label": "Evaluation Workflow 1" - }, - "gt_workspace": { - "@id": "https://gt.ocr-d.de/workspace/789", - "label": "GT workspace 789 (19th century fraktur)" - }, - "ocr_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/3000.ocrd.zip", - "label": "OCR result workspace 3000" - }, - "eval_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/345.ocrd.zip", - "label": "Evaluation Workspace 345" - }, - "workflow_steps": { - "0": "Processor A", - "1": "Processor B" - }, - "workflow_model": "Fraktur_GT4HistOCR", - "document_metadata": { - "fonts": [ - "antiqua", - "fraktur" - ], - "publication_century": "1800-1900", - "publication_decade": "1850-1860", - "publication_year": 1855, - "number_of_pages": 100, - "layout": "simple" - } - }, - "evaluation": { - "document_wide": { - "wall_time": 4567, - "cer": 0.9, - "cer_min_max": [ - 0.2, - 0.99 - ] - }, - "by_page": [ - { - "page_id": "PHYS_0001", - "cer": 0.9, - "processing_time": 2.1 - } - ] - } - }, - { - "@id": "https://github.com/OCR-D/quiver/tree/data/evaluations/wf2-data345-eval1.json", - "label": "OCR Workflow 3 on Data 345", - "metadata": { - "ocr_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/2.nf", - "label": "OCR Workflow 3" - }, - "eval_workflow": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workflows/eval1.nf", - "label": "Evaluation Workflow 1" - }, - "gt_workspace": { - "@id": "https://gt.ocr-d.de/workspace/123", - "label": "GT workspace 123 (16th century fraktur)" - }, - "ocr_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/3000.ocrd.zip", - "label": "OCR result workspace 3000" - }, - "eval_workspace": { - "@id": "https://github.com/OCR-D/quiver/tree/data/workspaces/345.ocrd.zip", - "label": "Evaluation Workspace 345" - }, - "workflow_steps": { - "0": "Processor A", - "1": "Processor B" - }, - "workflow_model": "Fraktur_GT4HistOCR", - "document_metadata": { - "fonts": [ - "antiqua", - "fraktur" - ], - "publication_century": "1800-1900", - "publication_decade": "1850-1860", - "publication_year": 1855, - "number_of_pages": 100, - "layout": "simple" - } - }, - "evaluation": { - "document_wide": { - "wall_time": 8765, - "cer": 0.4, - "cer_min_max": [ - 0.2, - 0.4 - ] - }, - "by_page": [ - { - "page_id": "PHYS_0001", - "cer": 0.4, - "processing_time": 2.1 - } - ] - } - }, -]; +const baseUrl = 'https://raw.githubusercontent.com/mweidling/quiver-back-end/add-missing-metrics/data'; async function getProjects() { return await request(baseUrl + '/repos.json'); diff --git a/src/helpers/eval-colors.js b/src/helpers/eval-colors.js deleted file mode 100644 index 8d7ef95..0000000 --- a/src/helpers/eval-colors.js +++ /dev/null @@ -1,74 +0,0 @@ -import { ref } from 'vue'; - -const evalColors = ref({ - wall_time: { - 'eval-positive': 2000, - 'eval-medium': 4000, - 'eval-negative': 6000 - }, - cer: { - 'eval-positive': 0.6, - 'eval-medium': 0.8, - 'eval-negative': 0.9 - }, - cer_min_max: { - 'eval-positive': 0.6, - 'eval-medium': 0.8, - 'eval-negative': 0.9 - } -}); - -const getEvalColor = (name, value) => { - const colorMap = evalColors.value[name]; - if (colorMap) { - const keys = Object.keys(colorMap); - return keys.find((key, i) => { - const isLast = i === keys.length - 1; - return value <= colorMap[key] || isLast && value > colorMap[key]; - }); - } - return null; -}; - -const setEvalColors = (data) => { - const allCERs = []; - const allWallTimes = []; - - data - .filter(({ evaluation_results }) => !!(evaluation_results)) - .forEach(({ evaluation_results }) => { - const { document_wide: evals } = evaluation_results; - - if (!evals) return; - - const { cer, wall_time, cer_min_max } = evals; - - if (cer) allCERs.push(cer); - if (wall_time) allWallTimes.push(wall_time); - }); - - const minCER = Math.min(...allCERs); - const maxCER = Math.max(...allCERs); - - const minWallTime = Math.min(...allWallTimes); - const maxWallTime = Math.max(...allWallTimes); - - const diffCER = maxCER - minCER; - const stepCER = diffCER / 3; - - evalColors.value.cer["eval-positive"] = minCER + stepCER; - evalColors.value.cer["eval-medium"] = minCER + 2 * stepCER; - evalColors.value.cer["eval-negative"] = minCER + 3 * stepCER; - - const diffallTime = maxWallTime - minWallTime; - const stepWallTime = diffallTime / 3; - - evalColors.value.wall_time["eval-positive"] = minWallTime + stepWallTime; - evalColors.value.wall_time["eval-medium"] = minWallTime + 2 * stepWallTime; - evalColors.value.wall_time["eval-negative"] = minWallTime + 3 * stepWallTime; -}; - -export { - getEvalColor, - setEvalColors -}; diff --git a/src/helpers/shorten-cer.js b/src/helpers/shorten-cer.js index 2735c55..0c70712 100644 --- a/src/helpers/shorten-cer.js +++ b/src/helpers/shorten-cer.js @@ -1,5 +1,11 @@ -const shortenCER = (value) => { - return Math.round(value * 1000) / 1000; +const createReadableMetricValue = (key, value) => { + if (['cer_mean', 'cer_median', 'wer', 'pages_per_minute', 'cer_standard_deviation'].includes(key)) { + return shortenMetricValue(value); + } else if (key === 'cer_range') { + return shortenMetricValue(value[0]) + ' / ' + shortenMetricValue(value[1]); + } + + return value; }; export { diff --git a/src/helpers/store.js b/src/helpers/store.js index 4a0d404..6d1f179 100644 --- a/src/helpers/store.js +++ b/src/helpers/store.js @@ -1,8 +1,16 @@ import { reactive } from 'vue'; export const store = reactive({ - repos: [], - setRepos(repos) { - this.repos = repos; - } + repos: [], + evaluations: [], + metricDefinitions: {}, + setRepos(repos) { + this.repos = repos; + }, + setEvaluations(evaluations) { + this.evaluations = evaluations; + }, + setMetricDefinitions(defs) { + this.metricDefinitions = defs; + } }); diff --git a/src/helpers/utils.js b/src/helpers/utils.js new file mode 100644 index 0000000..c10fd6b --- /dev/null +++ b/src/helpers/utils.js @@ -0,0 +1,73 @@ +import { ref } from 'vue'; + +const utils = ref({}); + +const getEvalColor = (name, value) => { + const colorMap = utils.value[name]; + if (colorMap) { + const keys = Object.keys(colorMap); + return keys.find((key, i) => { + const isLast = i === keys.length - 1; + return value <= colorMap[key] || isLast && value > colorMap[key]; + }); + } + return null; +}; + +const setEvalColors = (data) => { + const allValues = []; + + data + .filter(({ evaluation_results }) => !!(evaluation_results)) + .forEach(({ evaluation_results }) => { + const { document_wide: evals } = evaluation_results; + + if (!evals) return; + + Object + .keys(evals) + .filter(key => !Array.isArray(key)) + .forEach(key => { + allValues[key] = [...(allValues[key] ?? []), evals[key]]; + }); + }); + + Object + .keys(allValues) + .forEach(key => { + const singleMetricValues = allValues[key]; + const min = Math.min(...singleMetricValues); + const max = Math.max(...singleMetricValues); + + const diff = max - min; + const step = diff / 3; + + if (!utils.value[key]) { + utils.value[key] = {}; + } + + utils.value[key]["eval-positive"] = min + step; + utils.value[key]["eval-medium"] = min + 2 * step; + utils.value[key]["eval-negative"] = min + 3 * step; + }); +}; + +const shortenMetricValue = (value) => { + return Math.round(value * 1000) / 1000; +}; + +const createReadableMetricValue = (key, value) => { + if (['cer_mean', 'cer_median', 'wer', 'pages_per_minute', 'cer_standard_deviation'].includes(key)) { + return shortenMetricValue(value); + } else if (key === 'cer_range') { + return shortenMetricValue(value[0]) + ' / ' + shortenMetricValue(value[1]); + } + + return value; +}; + +export { + getEvalColor, + setEvalColors, + createReadableMetricValue +}; diff --git a/src/locales/de.json b/src/locales/de.json index 2e740eb..0c4bedc 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -32,5 +32,7 @@ "change": "Ändern", "datasets_selected": "Datasets ausgewählt", "of": "von", - "select_all": "Alle auswählen" + "select_all": "Alle auswählen", + "document": "Dokument", + "workflow": "Workflow" } diff --git a/src/locales/en.json b/src/locales/en.json index 4bc3ad7..449f4f1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -26,5 +26,8 @@ "change": "Change", "datasets_selected": "datasets selected", "of": "of", - "select_all": "Select all" + "select_all": "Select all", + "document": "Document", + "workflow": "Workflow" + } -- GitLab