diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 5b5d42f04eaec0672592c62a10bab5fc188837f8..ab590b837132eeb1f78cba92fe394549226c388d 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -5,7 +5,7 @@ import Dropdown from 'primevue/dropdown' import { computed, onMounted, ref } from "vue" import { EvaluationMetrics } from '@/helpers/metrics' import { useI18n } from "vue-i18n" -import type { DropdownOption, GroundTruth, Workflow } from "@/types" +import type {DropdownOption, EvaluationResultsDocumentWide, GroundTruth, Workflow} from "@/types" import { DropdownPassThroughStyles } from '@/helpers/pt' import { store } from '@/helpers/store' @@ -14,7 +14,7 @@ const gtList = ref<GroundTruth[]>([]) const workflows = ref<Workflow[]>([]) const selectedMetric = ref<DropdownOption | null>(null) const metrics = computed<DropdownOption[]>(() => Object.keys(EvaluationMetrics).map(key => ({ value: EvaluationMetrics[key], label: t(EvaluationMetrics[key]) }))) -const selectedMetricValue = computed<string>(() => selectedMetric.value?.value || EvaluationMetrics.CER_MEAN) +const selectedMetricValue = computed<keyof EvaluationResultsDocumentWide>(() => <keyof EvaluationResultsDocumentWide>selectedMetric.value?.value || EvaluationMetrics.CER_MEAN) onMounted(async () => { selectedMetric.value = metrics.value[0] diff --git a/src/components/timeline/BaseTimelineChart.vue b/src/components/timeline/BaseTimelineChart.vue index 2323405a8899ed8fb11b7326ca61539434a5b649..833c6a767d6d6f0463175054af39e25668e72b8d 100644 --- a/src/components/timeline/BaseTimelineChart.vue +++ b/src/components/timeline/BaseTimelineChart.vue @@ -18,13 +18,12 @@ interface Props { const props = defineProps<Props>() -const height = props.height || 60 +const height = props.height || 45 const marginTop = 10 const marginRight = 10 -const marginBottom = 30 +const marginBottom = 5 const marginLeft = 40 const _width = computed(() => props.width ?? 300) -const _maxY = computed(() => props.maxY ?? 2) const container = ref<HTMLDivElement>() @@ -42,11 +41,14 @@ function isUp(data: TimelineChartDataPoint[], higherIsUp = true) { else return -1 } -function render([data, startDate, endDate]) { +function render([data, startDate, endDate, maxY]) { if (!data || !startDate || !endDate) return if (data.length === 0) return + if (!container.value) return + container.value.replaceChildren() + // Declare the x (horizontal position) scale. const x = d3.scaleTime() .domain([startDate, endDate]) @@ -54,8 +56,10 @@ function render([data, startDate, endDate]) { // Declare the y (vertical position) scale. const y = d3.scaleLinear() - .domain([0, _maxY.value]) + .domain([0, maxY]) .range([height - marginBottom, marginTop]) + .nice() + // Create the SVG container. const svg = d3.create("svg") @@ -75,7 +79,7 @@ function render([data, startDate, endDate]) { svg.append("g") .classed('y-axis-group', true) .attr("transform", `translate(${marginLeft},0)`) - .call(d3.axisLeft(y).ticks(1).tickSize(0).tickPadding(5)) + .call(d3.axisLeft(y).tickValues([0, maxY]).tickSize(0).tickPadding(5)) svg.select('.y-axis-group .domain').attr('stroke', colors.gray['400']) svg.selectAll('.y-axis-group .tick text').attr('fill', colors.gray['400']) @@ -126,18 +130,14 @@ function render([data, startDate, endDate]) { setEventListeners(svg.selectAll('.chart-point'), tooltip, { useData: props.tooltipContent }) // Append the SVG element. - if (!container.value) return - container.value.replaceChildren() container.value.append(svg.node()) } onMounted(() => { - render([props.data, props.startDate, props.endDate]) + render([props.data, props.startDate, props.endDate, props.maxY]) }) -watch([() => props.data, () => props.startDate, () => props.endDate], render) - - +watch([() => props.data, () => props.startDate, () => props.endDate, () => props.maxY], render) </script> <template> diff --git a/src/components/timeline/MetricAverageChart.vue b/src/components/timeline/MetricAverageChart.vue index 42adc8e4be3b858ea1783196c7a8193e9af4093b..d9744c7246c9be5a4e8b6d6b3524e762f5eed639 100644 --- a/src/components/timeline/MetricAverageChart.vue +++ b/src/components/timeline/MetricAverageChart.vue @@ -1,43 +1,40 @@ <script setup lang="ts"> -import { onMounted, ref, watch } from "vue" +import {computed, onMounted, ref, watch} from "vue" import api from "@/helpers/api" import BaseTimelineChart from "@/components/timeline/BaseTimelineChart.vue" -import type {EvaluationResultsDocumentWide, EvaluationRun, TimelineChartDataPoint, Workflow} from "@/types" -import { getMaxValueOfMetric } from '@/helpers/metrics' +import type {EvaluationResultsDocumentWide, EvaluationRun, TimelineChartDataPoint} from "@/types" import {useI18n} from "vue-i18n"; import { metricChartTooltipContent } from "@/helpers/metric-chart-tooltip-content"; import OverlayPanel from "primevue/overlaypanel"; import BaseTimelineDetailedChart from "@/components/timeline/BaseTimelineDetailedChart.vue"; +import timelineStore from "@/store/timeline-store"; const { t } = useI18n() const props = defineProps<{ gtId: string, - metric: string, + metric: keyof EvaluationResultsDocumentWide, startDate: Date, endDate: Date }>() const data = ref<TimelineChartDataPoint[]>([]) -const maxY = ref(2) -const workflows = ref<Workflow[] | null>(null) +const maxY = computed(() => timelineStore.maxValues[props.metric] ?? 0) const runs = ref<EvaluationRun[]>([]) const op = ref<OverlayPanel | null>(null) onMounted(async () => { - const { gtId, metric } = props - workflows.value = await api.getWorkflows() - + const { gtId } = props runs.value = await api.getRuns(gtId) - - data.value = getTimelineData(runs.value, metric) - maxY.value = getMaxValueOfMetric(metric) + init() }) -watch(() => props.metric, async (value) => { - data.value = getTimelineData(runs.value, value) - maxY.value = getMaxValueOfMetric(value) -}) +watch(() => props.metric, init) + +function init() { + if (!runs.value) return + data.value = getTimelineData(runs.value, props.metric) +} function getTimelineData(runs: EvaluationRun[], metric: string): TimelineChartDataPoint[] { const datesValues = runs.reduce((acc, cur) => { diff --git a/src/components/timeline/MetricChart.vue b/src/components/timeline/MetricChart.vue index 75e8928d8d38bb4805a259a803fb8ab31eb26fef..c2590fc72e6a65a1ec513467510ee696fd83613b 100644 --- a/src/components/timeline/MetricChart.vue +++ b/src/components/timeline/MetricChart.vue @@ -1,34 +1,37 @@ <script setup lang="ts"> -import { onMounted, ref, watch } from "vue" +import { computed, onMounted, ref, watch } from "vue" import api from "@/helpers/api" import BaseTimelineChart from "@/components/timeline/BaseTimelineChart.vue" -import { getMaxValueOfMetric } from '@/helpers/metrics' +import { extendMaxValue, getMaxValueByMetric } from '@/helpers/metrics' import type { EvaluationResultsDocumentWide, EvaluationRun, TimelineChartDataPoint } from "@/types" import { metricChartTooltipContent } from "@/helpers/metric-chart-tooltip-content" import OverlayPanel from 'primevue/overlaypanel' import BaseTimelineDetailedChart from "@/components/timeline/BaseTimelineDetailedChart.vue" +import timelineStore from "@/store/timeline-store" const props = defineProps(['gtId', 'workflowId', 'metric', 'startDate', 'endDate']) const runs = ref<EvaluationRun[]>([]) const data = ref([]) -const maxY = ref(2) +const maxY = computed(() => timelineStore.maxValues[props.metric] ?? 0 ) const op = ref<OverlayPanel | null>(null) - onMounted(async () => { - const { gtId, workflowId, metric } = props + const { gtId, workflowId } = props runs.value = await api.getRuns(gtId, workflowId) - data.value = getTimelineData(runs.value, metric) - maxY.value = getMaxValueOfMetric(metric) + init() }) -watch(() => props.metric, - (value) => { - if (!runs.value) return - data.value = getTimelineData(runs.value, value) - maxY.value = getMaxValueOfMetric(value) - }, { immediate: true } -) +watch(() => props.metric, init) + +function init() { + if (!runs.value) return + data.value = getTimelineData(runs.value, props.metric) + + const maxValueByMetric = getMaxValueByMetric(props.metric, runs.value) + if (maxValueByMetric > maxY.value) { + timelineStore.setMaxValue(props.metric, extendMaxValue(maxValueByMetric)) + } +} function getTimelineData(runs: EvaluationRun[], metric: keyof EvaluationResultsDocumentWide) { return runs.map(({ metadata, evaluation_results }) => { @@ -44,6 +47,8 @@ function tooltipContent(d: TimelineChartDataPoint) { return metricChartTooltipContent(d, props.metric) } + + </script> <template> diff --git a/src/components/timeline/TimelineItem.vue b/src/components/timeline/TimelineItem.vue index 0b1b7264cd9a87e39a2850dda0f1e26c62eea412..7247378c0ab07d60dd9fa7fc0b2ae6c003ce005e 100644 --- a/src/components/timeline/TimelineItem.vue +++ b/src/components/timeline/TimelineItem.vue @@ -3,7 +3,7 @@ import Panel from "primevue/panel" import OverlayPanel from 'primevue/overlaypanel' import StepsAcronyms from '@/helpers/workflow-steps-acronyms' import MetricChart from "@/components/timeline/MetricChart.vue" -import type { GroundTruth, Workflow, WorkflowStep } from "@/types" +import type { EvaluationResultsDocumentWide, GroundTruth, Workflow, WorkflowStep } from "@/types" import MetricAverageChart from "@/components/timeline/MetricAverageChart.vue" import { Icon } from '@iconify/vue' import { ref } from "vue" @@ -12,7 +12,7 @@ import { OverlayPanelDropdownStyles } from "@/helpers/pt" const props = defineProps<{ gt: GroundTruth, workflows: Workflow[], - metric: string + metric: keyof EvaluationResultsDocumentWide }>() const op = ref<OverlayPanel>() @@ -67,7 +67,7 @@ function hideParametersOverlay() { </div> </template> <template v-slot:default> - <div class="flex border-t border-gray-300 pt-4 px-4"> + <div class="flex border-t border-gray-300 py-4 px-4"> <table class="table-fixed w-full"> <tr v-for="workflow in workflows" :key="workflow.id"> <td class="font-semibold pe-2">{{ workflow.label }}</td> diff --git a/src/helpers/metrics.ts b/src/helpers/metrics.ts index f3da9b41f0b59f071ba1b76fb4ddd9b3e061cc46..488697d17473293594580c8f067caaa54fccfc0f 100644 --- a/src/helpers/metrics.ts +++ b/src/helpers/metrics.ts @@ -1,3 +1,5 @@ +import type { EvaluationResultsDocumentWide, EvaluationRun } from "@/types" + const EvaluationMetrics = { CER_MEAN: 'cer_mean', CER_MEDIAN: 'cer_median', @@ -8,7 +10,7 @@ const EvaluationMetrics = { CPU_TIME: 'cpu_time' } -function getMaxValueOfMetric(metric: string): number { +function getDefaultMaxValueOfMetric(metric: string): number { if (metric === EvaluationMetrics.CER_MEAN) return 2 if (metric === EvaluationMetrics.CER_MEDIAN) return 2 if (metric === EvaluationMetrics.CER_STANDARD_DEVIATION) return 2 @@ -20,7 +22,22 @@ function getMaxValueOfMetric(metric: string): number { else return 1 } +function getMaxValueByMetric(metric: keyof EvaluationResultsDocumentWide, runs: EvaluationRun[] = []): number { + const values = runs.map((run) => { + const value = run.evaluation_results.document_wide[metric] + return Array.isArray(value) ? Math.max(...value) : value ?? 0 + }) ?? [] + + return Math.max(...values) +} + +function extendMaxValue(value: number): number { + return Math.floor(value + value * 0.2) +} + export { EvaluationMetrics, - getMaxValueOfMetric + getMaxValueByMetric, + getDefaultMaxValueOfMetric, + extendMaxValue } diff --git a/src/store/timeline-store.ts b/src/store/timeline-store.ts new file mode 100644 index 0000000000000000000000000000000000000000..4652169fa81bfe6a66e767e5d9cca84edd7695f9 --- /dev/null +++ b/src/store/timeline-store.ts @@ -0,0 +1,15 @@ +import { reactive, ref } from "vue" + +export default reactive<{ + maxValues: {[metric: string]: number }, + setMaxValue: (metric: string, value: number) => void, + getMaxValue: (metric: string) => number +}>({ + maxValues:{}, + setMaxValue(metric: string, value: number) { + this.maxValues[metric] = value + }, + getMaxValue(metric: string) { + return this.maxValues[metric] ?? 0 + } +})