Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make chart legend more compact #1120

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions src/components/charts/ChartLegend.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<!-- Fills up space as v-sheet is absolutely positioned -->
<div class="mt-1" :style="{ height: `${props.height}px` }" />
<v-sheet
v-click-outside="onOutsideClick"
class="mt-1 chart-controls"
:style="chartControlsStyle"
:elevation="expanded ? 5 : 0"
rounded
>
<div
ref="chartLegendContainer"
:style="chartLegendContainerStyle"
class="chart-legend-container w-100"
>
<v-chip-group ref="chartLegend" column multiple>
<v-chip
v-for="tag in tags"
:key="tag.id"
label
size="small"
role="button"
:variant="tag.disabled ? 'text' : 'tonal'"
@click="toggleLine(tag)"
>
<div
v-if="tag.legendSvg"
class="legend-symbol me-2"
v-html="tag.legendSvg"
/>

<span>{{ tag.name }}</span>
</v-chip>
</v-chip-group>
</div>
<v-btn
v-show="requiresExpand"
:icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
size="small"
variant="plain"
@click="onToggleExpand()"
/>
</v-sheet>
</template>

<script setup lang="ts">
import * as wbCharts from '@deltares/fews-web-oc-charts'
import type { Tag } from '@/lib/charts/tags'
import { useElementSize, useToggle } from '@vueuse/core'
import { computed, ref, watch } from 'vue'

interface Props {
tags: Tag[]
height: number
margin: wbCharts.Margin
}

const props = defineProps<Props>()

const emit = defineEmits(['toggleLine'])

const chartLegend = ref<HTMLElement>()
const chartLegendContainer = ref<HTMLElement>()
const { height: legendHeight } = useElementSize(chartLegend)
const requiresExpand = computed(() => legendHeight.value > props.height)
watch(requiresExpand, () => {
if (!requiresExpand.value) {
toggleExpand(false)
}
})
const [expanded, toggleExpand] = useToggle(false)

function toggleLine(tag: Tag) {
tag.disabled = !tag.disabled
emit('toggleLine', tag)
}

const legendContainerHeight = computed(() => {
return Math.min(props.height, legendHeight.value)
})
const chipMargin = 5
const chartControlsStyle = computed(() => ({
maxHeight: expanded.value ? '95%' : `${legendContainerHeight.value}px`,
minHeight: `${legendContainerHeight.value}px`,
marginRight: props.margin.right
? `${props.margin.right - chipMargin}px`
: undefined,
marginLeft: props.margin.left
? `${props.margin.left - chipMargin}px`
: undefined,
width: `calc(100% - ${(props.margin.left ?? 0) + (props.margin.right ?? 0) - 2 * chipMargin}px)`,
}))

const chartLegendContainerStyle = computed(() => ({
overflow: expanded.value ? 'auto' : 'hidden',
}))

function onOutsideClick() {
if (expanded.value) {
onToggleExpand()
}
}

function onToggleExpand() {
toggleExpand()
if (chartLegendContainer.value) {
// Scroll to top when closing expand
chartLegendContainer.value.scrollTop = 0
}
}
</script>

<style scoped>
.chart-controls {
display: flex;
position: absolute;
flex: 0 0 auto;
overflow: hidden;
z-index: 10;
}

.chart-legend-container {
max-height: 100%;
margin-left: v-bind(chipMargin + 'px');
margin-right: v-bind(chipMargin + 'px');
}

.legend-symbol {
display: flex;
align-items: center;
justify-content: center;
}

:deep(.v-slide-group__content) {
justify-content: flex-end;
}

:deep(.v-chip--outlined) {
opacity: 0.5;
}
</style>
108 changes: 17 additions & 91 deletions src/components/charts/ElevationChart.vue
Original file line number Diff line number Diff line change
@@ -1,44 +1,13 @@
<template>
<div class="chart-with-chips">
<LoadingOverlay v-if="isLoading" :offsets="margin" height="90%" />
<ChartLegend
:tags="legendTags"
:height="40"
:margin="margin"
@toggleLine="toggleLine"
/>
<LoadingOverlay v-if="isLoading" :offsets="loadingMargin" />
<div ref="chartContainer" class="chart-container" v-show="!isLoading"></div>
<v-sheet
class="chart-controls"
rounded
:max-height="expanded ? undefined : LEGEND_HEIGHT"
:min-height="LEGEND_HEIGHT"
:elevation="expanded ? 6 : 0"
>
<v-chip-group
ref="chipGroup"
column
:class="['chart-legend', { 'chart-legend--large': requiresExpand }]"
>
<v-chip
size="small"
:variant="tag.disabled ? 'text' : 'tonal'"
label
v-for="tag in legendTags"
:key="tag.id"
@click="toggleLine(tag.id)"
>
<div>
<div
style="margin-top: 6px; margin-right: 5px"
v-html="tag.legendSvg"
></div>
</div>
{{ tag.name }}
</v-chip>
</v-chip-group>
<v-btn
v-show="requiresExpand"
:icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
size="small"
variant="plain"
@click="toggleExpand"
></v-btn>
</v-sheet>
</div>
</template>

Expand All @@ -58,19 +27,17 @@ import {
ZoomHandler,
} from '@deltares/fews-web-oc-charts'
import LoadingOverlay from '@/components/charts/LoadingOverlay.vue'
import ChartLegend from '@/components/charts/ChartLegend.vue'
import type { ChartConfig } from '../../lib/charts/types/ChartConfig.js'
import type { ChartSeries } from '../../lib/charts/types/ChartSeries.js'
import { Series } from '../../lib/timeseries/timeSeries.js'
import uniq from 'lodash-es/uniq'
import { VChipGroup } from 'vuetify/components'
import { difference } from 'lodash-es'
import {
dataFromResources,
removeUnreliableData,
} from '@/lib/charts/dataFromResources'

const LEGEND_HEIGHT = 76

interface Props {
config?: ChartConfig
series?: Record<string, Series>
Expand Down Expand Up @@ -100,17 +67,19 @@ const props = withDefaults(defineProps<Props>(), {
let axis!: CartesianAxes
const legendTags = ref<Tag[]>([])
const chartContainer = ref<HTMLElement>()
const chipGroup = ref<VChipGroup>()
const expanded = ref(false)
const requiresExpand = ref(false)

const margin = {
top: 110,
top: 30,
left: 70,
right: 30,
bottom: 50,
}

const loadingMargin = {
...margin,
top: margin.top + 40,
}

onMounted(() => {
const axisOptions: CartesianAxesOptions = {
x: [
Expand All @@ -119,6 +88,7 @@ onMounted(() => {
position: AxisPosition.Bottom,
showGrid: true,
label: ' ',
labelOffset: 10,
unit: ' ',
nice: true,
},
Expand Down Expand Up @@ -291,42 +261,20 @@ const setTags = () => {
}
}

const toggleLine = (id: string) => {
const tag = legendTags.value.find((tag) => {
return tag.id === id
})
if (tag) {
tag.disabled = !tag.disabled
}

toggleChartVisibility(axis, id)
const toggleLine = (tag: Tag) => {
toggleChartVisibility(axis, tag.id)
}

const resize = () => {
nextTick(() => {
axis.resize()
setLegendSize()
})
}

function setLegendSize() {
const contentHeight = chipGroup.value?.$el.scrollHeight
if (contentHeight && contentHeight > LEGEND_HEIGHT) {
requiresExpand.value = true
} else {
requiresExpand.value = false
}
}

function toggleExpand() {
expanded.value = !expanded.value
}

const onValueChange = () => {
clearChart()
refreshChart()
setTags()
setLegendSize()
}

const beforeDestroy = () => {
Expand Down Expand Up @@ -366,15 +314,6 @@ onBeforeUnmount(() => {
overflow: hidden;
}

.chart-controls {
position: absolute;
display: flex;
flex: 0;
margin: 5px 10px;
padding: 0px 0px 0px 40px;
overflow: hidden;
}

.chart-container.hidden > svg {
display: none;
}
Expand All @@ -383,24 +322,11 @@ onBeforeUnmount(() => {
max-height: none;
}

.chart-legend {
overflow-y: hidden;
align-self: end;
}

.chart-legend.chart-legend--large {
align-self: start;
}

.chart-with-chips {
display: flex;
position: relative;
flex-direction: column;
flex: 1 1 80%;
height: 100%;
}

.v-chip--outlined {
opacity: 0.5;
}
</style>
11 changes: 3 additions & 8 deletions src/components/charts/LoadingOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ interface Props {
bottom?: number
left?: number
}
height?: string
width?: string
}

const props = defineProps<Props>()

const style = computed(() => ({
paddingTop: props.offsets?.top ? `${props.offsets.top - 32}px` : undefined,
paddingTop: props.offsets?.top ? `${props.offsets.top}px` : undefined,
paddingRight: props.offsets?.right ? `${props.offsets.right}px` : undefined,
paddingBottom: props.offsets?.bottom
? `${props.offsets.bottom}px`
Expand All @@ -36,14 +34,11 @@ const style = computed(() => ({
z-index: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}

:deep(.v-skeleton-loader__image) {
height: v-bind(height);
width: v-bind(width);
height: 100%;
width: 100%;
border-radius: 4px;
}
</style>
Loading
Loading