diff --git a/src/components/timeline/TimelineChart.vue b/src/components/timeline/BaseTimelineChart.vue similarity index 64% rename from src/components/timeline/TimelineChart.vue rename to src/components/timeline/BaseTimelineChart.vue index f1d8e9cf3053fe1f947e01bd66c9fa0178efdc7a..3498c714f0b908672d9d8acf1aa530c5334a8bda 100644 --- a/src/components/timeline/TimelineChart.vue +++ b/src/components/timeline/BaseTimelineChart.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import * as d3 from "d3" -import { ref, watch, computed } from "vue" +import { ref, watch, computed, onMounted } from "vue" import type { TimelineChartDataPoint } from "@/types" import { createTooltip, setEventListeners } from "@/helpers/d3/d3-tooltip" import colors from 'tailwindcss/colors' @@ -12,12 +12,13 @@ interface Props { width?: number, startDate: Date, endDate: Date, + height?: number, tooltipContent: (d: TimelineChartDataPoint) => string } const props = defineProps<Props>() -const height = 60 +const height = props.height || 60 const marginTop = 10 const marginRight = 10 const marginBottom = 30 @@ -41,40 +42,43 @@ function isUp(data: TimelineChartDataPoint[], higherIsUp = true) { else return -1 } -watch([() => props.data, () => props.startDate, () => props.endDate], ([data, startDate, endDate]) => { +function render([data, startDate, endDate]) { if (!data || !startDate || !endDate) return if (data.length === 0) return + console.log(container.value) + + // Declare the x (horizontal position) scale. const x = d3.scaleTime() - .domain([startDate, endDate]) - .range([marginLeft, _width.value - marginRight]) + .domain([startDate, endDate]) + .range([marginLeft, _width.value - marginRight]) // Declare the y (vertical position) scale. const y = d3.scaleLinear() - .domain([0, _maxY.value]) - .range([height - marginBottom, marginTop]) + .domain([0, _maxY.value]) + .range([height - marginBottom, marginTop]) // Create the SVG container. const svg = d3.create("svg") - .attr("width", _width.value) - .attr("height", height) + .attr("width", _width.value) + .attr("height", height) // Add the x-axis. svg.append("g") - .classed('x-axis-group', true) - .attr("transform", `translate(0,${height - marginBottom})`) - .call(d3.axisBottom(x).ticks(0).tickSize(0).tickFormat(d3.utcFormat("%d.%m.%Y"))) + .classed('x-axis-group', true) + .attr("transform", `translate(0,${height - marginBottom})`) + .call(d3.axisBottom(x).ticks(0).tickSize(0).tickFormat(d3.utcFormat("%d.%m.%Y"))) svg.select('.x-axis-group .domain').attr('stroke', colors.gray['400']) svg.selectAll('.x-axis-group .tick text').attr('fill', colors.gray['400']) // Add the y-axis. svg.append("g") - .classed('y-axis-group', true) - .attr("transform", `translate(${marginLeft},0)`) - .call(d3.axisLeft(y).ticks(1).tickSize(0).tickPadding(5)) + .classed('y-axis-group', true) + .attr("transform", `translate(${marginLeft},0)`) + .call(d3.axisLeft(y).ticks(1).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']) @@ -84,39 +88,39 @@ watch([() => props.data, () => props.startDate, () => props.endDate], ([data, st const trend = isUp(data, false) const pathGroup = svg - .append('g') - .classed('path-group', true) - .classed('up', trend === 1) - .classed('down', trend === -1) + .append('g') + .classed('path-group', true) + .classed('up', trend === 1) + .classed('down', trend === -1) pathGroup.append("path") - .datum(data) - .attr("stroke-width", 1.5) - .attr("d", d3.line() - .x(function(d) { return x(d.date) }) - .y(function(d) { return y(d.value) }) - ) + .datum(data) + .attr("stroke-width", 1.5) + .attr("d", d3.line() + .x(function(d) { return x(d.date) }) + .y(function(d) { return y(d.value) }) + ) const pointGroups = pathGroup.selectAll("myCircles") - .data(data) - .enter() - .append("g") + .data(data) + .enter() + .append("g") pointGroups - .append('circle') - .attr("r", 10) - .style('fill', 'transparent') - .style('cursor', 'pointer') - .attr("cx", function(d) { return x(d.date) }) - .attr("cy", function(d) { return y(d.value) }) - .classed('chart-point', true) + .append('circle') + .attr("r", 10) + .style('fill', 'transparent') + .style('cursor', 'pointer') + .attr("cx", function(d) { return x(d.date) }) + .attr("cy", function(d) { return y(d.value) }) + .classed('chart-point', true) pointGroups - .append("circle") - .attr("r", 2) - .classed('pointer-events-none', true) - .attr("cx", function(d) { return x(d.date) }) - .attr("cy", function(d) { return y(d.value) }) + .append("circle") + .attr("r", 2) + .classed('pointer-events-none', true) + .attr("cx", function(d) { return x(d.date) }) + .attr("cy", function(d) { return y(d.value) }) const oldTooltip = document.querySelector('.d3-tooltip') const tooltip = oldTooltip ? d3.select(oldTooltip) : createTooltip(d3.select('body'), { @@ -129,8 +133,14 @@ watch([() => props.data, () => props.startDate, () => props.endDate], ([data, st if (!container.value) return container.value.replaceChildren() container.value.append(svg.node()) +} + +onMounted(() => { + render([props.data, props.startDate, props.endDate]) }) +watch([() => props.data, () => props.startDate, () => props.endDate], render) + </script> diff --git a/src/components/timeline/MetricAverageChart.vue b/src/components/timeline/MetricAverageChart.vue index fd246caf522286fea263e4a078d75748193358db..c36536beca4bf271585b3bab7e84a6e2cfc7fd5c 100644 --- a/src/components/timeline/MetricAverageChart.vue +++ b/src/components/timeline/MetricAverageChart.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { onMounted, ref, watch } from "vue" import api from "@/helpers/api" -import TimelineChart from "@/components/timeline/TimelineChart.vue" +import BaseTimelineChart from "@/components/timeline/BaseTimelineChart.vue" import type {EvaluationResultsDocumentWide, EvaluationRun, TimelineChartDataPoint, Workflow} from "@/types" import { getMaxValueOfMetric } from '@/helpers/metrics' import {useI18n} from "vue-i18n"; @@ -66,7 +66,7 @@ function tooltipContent(d: TimelineChartDataPoint) { </script> <template> - <TimelineChart :data="data" :max-y="maxY" :start-date="startDate" :end-date="endDate" :tooltip-content="tooltipContent"/> + <BaseTimelineChart :data="data" :max-y="maxY" :start-date="startDate" :end-date="endDate" :tooltip-content="tooltipContent"/> </template> <style scoped lang="scss"> diff --git a/src/components/timeline/MetricChart.vue b/src/components/timeline/MetricChart.vue index 789a98c8efb1f039e25b3ceadcc9d692367d64cd..793c1801b6edc65b02c121b510af13e2f4dee202 100644 --- a/src/components/timeline/MetricChart.vue +++ b/src/components/timeline/MetricChart.vue @@ -1,16 +1,18 @@ <script setup lang="ts"> import { onMounted, ref, watch } from "vue" import api from "@/helpers/api" -import TimelineChart from "@/components/timeline/TimelineChart.vue" +import BaseTimelineChart from "@/components/timeline/BaseTimelineChart.vue" import { getMaxValueOfMetric } from '@/helpers/metrics' import type { EvaluationResultsDocumentWide, EvaluationRun } from "@/types" import { TimelineChartDataPoint } from "@/types" import { metricChartTooltipContent } from "@/helpers/metric-chart-tooltip-content" +import OverlayPanel from 'primevue/overlaypanel' const props = defineProps(['gtId', 'workflowId', 'metric', 'startDate', 'endDate']) const runs = ref<EvaluationRun[]>([]) const data = ref([]) const maxY = ref(2) +const op = ref<OverlayPanel | null>(null) onMounted(async () => { @@ -45,7 +47,27 @@ function tooltipContent(d: TimelineChartDataPoint) { </script> <template> - <TimelineChart :data="data" :max-y="maxY" :start-date="startDate" :end-date="endDate" :tooltip-content="tooltipContent" /> + <div @click="op?.toggle($event)" class="cursor-pointer"> + <BaseTimelineChart :data="data" :max-y="maxY" :start-date="startDate" :end-date="endDate" :tooltip-content="tooltipContent" /> + </div> + <OverlayPanel + ref="op" + :pt="{ + root: { + class: 'z-[9999] bg-white border rounded-md shadow-md' + } + }" + > + <BaseTimelineChart + :data="data" + :max-y="maxY" + :start-date="startDate" + :end-date="endDate" + :tooltip-content="tooltipContent" + :height="400" + :width="660" + /> + </OverlayPanel> </template> <style scoped lang="scss">