Updated Map View
This commit is contained in:
+109
-10
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import VueApexCharts from "vue3-apexcharts"
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -12,23 +12,63 @@ const props = defineProps<{
|
||||
barHeight?: number
|
||||
colors?: string[]
|
||||
options?: object
|
||||
limit?: number
|
||||
}>()
|
||||
|
||||
const BAR_HEIGHT = computed(() => props.barHeight ?? 32)
|
||||
const MAX_VISIBLE = computed(() => props.maxVisible ?? 12)
|
||||
|
||||
const chartHeight = computed(() => props.categories.length * BAR_HEIGHT.value + 40)
|
||||
// ── Limit / Show More ─────────────────────────────────────────────────────────
|
||||
|
||||
const showAll = ref(false)
|
||||
const isExpanding = ref(false)
|
||||
|
||||
async function expand() {
|
||||
isExpanding.value = true
|
||||
await nextTick() // paint "Loading..." before blocking on chart render
|
||||
showAll.value = true
|
||||
await nextTick() // wait for ApexCharts to finish
|
||||
isExpanding.value = false
|
||||
}
|
||||
|
||||
function collapse() {
|
||||
showAll.value = false
|
||||
}
|
||||
|
||||
const visibleCategories = computed(() =>
|
||||
props.limit && !showAll.value
|
||||
? props.categories.slice(0, props.limit)
|
||||
: props.categories
|
||||
)
|
||||
|
||||
const visibleSeries = computed(() =>
|
||||
props.limit && !showAll.value
|
||||
? props.series.map(s => ({ ...s, data: s.data.slice(0, props.limit) }))
|
||||
: props.series
|
||||
)
|
||||
|
||||
const hasMore = computed(() =>
|
||||
!!props.limit && props.categories.length > props.limit
|
||||
)
|
||||
|
||||
const hiddenCount = computed(() =>
|
||||
props.limit ? props.categories.length - props.limit : 0
|
||||
)
|
||||
|
||||
// ── Chart dimensions ──────────────────────────────────────────────────────────
|
||||
|
||||
const chartHeight = computed(() => visibleCategories.value.length * BAR_HEIGHT.value + 40)
|
||||
const scrollHeight = computed(() => {
|
||||
const visible = Math.min(props.categories.length, MAX_VISIBLE.value)
|
||||
const visible = Math.min(visibleCategories.value.length, MAX_VISIBLE.value)
|
||||
return `${visible * BAR_HEIGHT.value + 40}px`
|
||||
})
|
||||
|
||||
// ── Tooltip state (exposed to parent via scoped slot) ─────────────────────────
|
||||
// ── Tooltip state ─────────────────────────────────────────────────────────────
|
||||
|
||||
const tooltipVisible = ref(false)
|
||||
const tooltipX = ref(0)
|
||||
const tooltipY = ref(0)
|
||||
const hoveredIndex = ref<number | null>(null)
|
||||
const tooltipVisible = ref(false)
|
||||
const tooltipX = ref(0)
|
||||
const tooltipY = ref(0)
|
||||
const hoveredIndex = ref<number | null>(null)
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
tooltipX.value = e.clientX + 14
|
||||
@@ -74,7 +114,7 @@ const chartOptions = computed(() => ({
|
||||
colors: props.colors ?? ['#4da6ff', '#ffc107'],
|
||||
dataLabels: { enabled: false },
|
||||
xaxis: {
|
||||
categories: props.categories,
|
||||
categories: visibleCategories.value,
|
||||
labels: { show: false },
|
||||
axisBorder: { show: false },
|
||||
axisTicks: { show: false },
|
||||
@@ -113,7 +153,7 @@ const chartOptions = computed(() => ({
|
||||
type="bar"
|
||||
:height="chartHeight"
|
||||
:options="options ?? chartOptions"
|
||||
:series="series"
|
||||
:series="visibleSeries"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +165,22 @@ const chartOptions = computed(() => ({
|
||||
:index="hoveredIndex"
|
||||
/>
|
||||
|
||||
<div v-if="hasMore || (limit && showAll)" class="show-more-wrap">
|
||||
<button
|
||||
v-if="!showAll"
|
||||
class="show-more"
|
||||
:disabled="isExpanding"
|
||||
:class="{ loading: isExpanding }"
|
||||
@click="expand"
|
||||
>
|
||||
<span v-if="isExpanding" class="spinner" />
|
||||
<span>{{ isExpanding ? 'Loading…' : `Show ${hiddenCount} more` }}</span>
|
||||
</button>
|
||||
<button v-else class="show-more" @click="collapse">
|
||||
Show less
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="footerValue !== undefined" class="chart-footer">
|
||||
<span class="total-count">{{ footerValue }}</span>
|
||||
<span class="total-label">{{ footerLabel }}</span>
|
||||
@@ -167,6 +223,49 @@ const chartOptions = computed(() => ({
|
||||
.chart-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||
.chart-scroll::-webkit-scrollbar-thumb { background: #334455; border-radius: 2px; }
|
||||
|
||||
.show-more-wrap {
|
||||
display: flex;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: none;
|
||||
border: 1px solid #334455;
|
||||
border-radius: 4px;
|
||||
color: #778899;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.show-more:hover:not(:disabled) {
|
||||
border-color: #4da6ff;
|
||||
color: #4da6ff;
|
||||
}
|
||||
|
||||
.show-more:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1.5px solid #556677;
|
||||
border-top-color: #4da6ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.chart-footer {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
@@ -32,6 +32,8 @@ const chartEvents = computed(() => ({
|
||||
|
||||
<template>
|
||||
<ScrollingHorizontalBarChart
|
||||
:limit="18
|
||||
"
|
||||
title="Top countries"
|
||||
:series="flightStats.countries.value.series"
|
||||
:categories="countries.map(c => c.name)"
|
||||
|
||||
@@ -41,6 +41,7 @@ const chartEvents = computed(() => ({
|
||||
:events="chartEvents"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseleave="onMouseLeave"
|
||||
:limit="12"
|
||||
>
|
||||
<template #tooltip="{ visible, x, y, index }">
|
||||
<ChartTooltip :visible="visible" :x="x" :y="y">
|
||||
|
||||
@@ -40,6 +40,7 @@ const chartEvents = computed(() => ({
|
||||
:categories="airlines.map(a => a.name)"
|
||||
:footer-value="airlines.length"
|
||||
footer-label="total airlines"
|
||||
:limit="12"
|
||||
:events="chartEvents"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseleave="onMouseLeave"
|
||||
|
||||
@@ -42,6 +42,7 @@ const chartEvents = computed(() => ({
|
||||
:events="chartEvents"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseleave="onMouseLeave"
|
||||
:limit="12"
|
||||
>
|
||||
<template #tooltip="{ visible, x, y, index }">
|
||||
<ChartTooltip :visible="visible" :x="x" :y="y">
|
||||
|
||||
@@ -31,6 +31,7 @@ const series = computed(() => props.flightStats.topRoutes.value.series)
|
||||
:footer-value="routes.length"
|
||||
footer-label="total routes"
|
||||
:max-visible="12"
|
||||
:limit="12"
|
||||
>
|
||||
<template #tooltip="{ visible, x, y, index }">
|
||||
<ChartTooltip :visible="visible" :x="x" :y="y">
|
||||
|
||||
Reference in New Issue
Block a user