Skip to content

feat(VSparkline): markers & tooltips#22748

Draft
J-Sek wants to merge 12 commits intodevfrom
feat/vsparkline-markers-and-tooltips
Draft

feat(VSparkline): markers & tooltips#22748
J-Sek wants to merge 12 commits intodevfrom
feat/vsparkline-markers-and-tooltips

Conversation

@J-Sek
Copy link
Copy Markdown
Contributor

@J-Sek J-Sek commented Mar 23, 2026

  • adds new props to show markers, hover effect, tooltips
  • includes multiple fixes for transitions
Screencast.from.2026-03-23.19-45-00.mp4

Markup:

<template>
  <v-app>
    <v-container class="pt-8 px-0 d-flex flex-column ga-6" max-width="800">
      <v-row>
        <v-col>
          <v-card
            class="pa-5"
            color="#1e88e4"
            height="190"
            rounded="lg"
          >
            <div class="d-flex justify-space-between fill-height">
              <div class="d-flex flex-column align-start fill-height">
                <v-avatar class="mb-3" color="#1564bf" rounded="lg" size="48">
                  <v-icon icon="mdi-cart-outline" size="24" />
                </v-avatar>
                <v-spacer />
                <div class="d-flex align-center ga-2">
                  <span class="text-headline-large font-weight-medium">${{ period === 'month' ? 108 : 961 }}</span>
                  <v-avatar color="#92cbff" size="24">
                    <v-icon color="#1465bf" icon="mdi-arrow-bottom-left" size="16" />
                  </v-avatar>
                </div>
                <div class="text-body-medium">Total Order</div>
              </div>
              <div class="d-flex ga-1">
                <v-btn :color="period === 'month' ? '#1564bf' : undefined" :variant="period === 'month' ? 'flat' : 'text'" rounded="lg" @click="period = 'month'">Month</v-btn>
                <v-btn :color="period === 'year' ? '#1564bf' : undefined" :variant="period === 'year' ? 'flat' : 'text'" rounded="lg" @click="period = 'year'">Year</v-btn>
              </div>
            </div>

            <v-sparkline
              :auto-draw-duration="1200"
              :line-width="useBars ? (175 / (period === 'month' ? series1month : series1year).length - 5) : 3"
              :model-value="period === 'month' ? series1month : series1year"
              :show-markers="showMarkers"
              :smooth="smooth"
              :smooth-mode="useMonotone ? 'monotone' : 'default'"
              :type="useBars ? 'bar' : 'trend'"
              auto-draw="once"
              height="100"
              marker-stroke="#1e88e4"
              max="100"
              min="0"
              stroke-linecap="round"
              style="position: absolute; right: 16px; bottom: 16px; height: 100px;"
              width="175"
              interactive
              tooltip
            >
              <template #tooltip="{ value }">
                Total Order <b>{{ value }}</b>
              </template>
            </v-sparkline>
          </v-card>
        </v-col>
        <v-col>

          <v-card
            class="pa-5"
            color="#f0eaf8"
            height="190"
            rounded="lg"
          >
            <div class="d-flex align-start mb-2">
              <div>
                <div class="text-body-medium font-weight-bold" style="color: #5e35b1">Contoso Corp</div>
                <div class="text-body-small">10% Profit</div>
              </div>
              <div class="font-weight-bold ml-auto">$1839.00</div>
            </div>

            <v-sparkline
              :gradient="['#7c4dff99', '#7c4dff11']"
              :line-width="useBars ? (388 / series2.length - 5) : 1"
              :model-value="series2"
              :show-markers="showMarkers"
              :smooth="smooth"
              :smooth-mode="useMonotone ? 'monotone' : 'default'"
              :type="useBars ? 'bar' : 'trend'"
              class="mb-n1"
              color="#7c4dff"
              height="125"
              marker-size="10"
              marker-stroke="#f0eaf8"
              style="position: absolute; left: 0px; bottom: 0px; height: 125px;"
              width="388"
              auto-draw
              fill
              inset
              interactive
              tooltip
            >
              <template #tooltip="{ value }">
                Ticket <b>{{ value }}</b>
              </template>
            </v-sparkline>
          </v-card>
        </v-col>
      </v-row>

      <div class="w-100">
        <v-slider
          v-model="smooth"
          :max="20"
          :min="0"
          :step="1"
          label="smooth"
          hide-details
          thumb-label
        />
        <v-switch
          v-model="showMarkers"
          label="show markers"
          hide-details
        />
        <v-switch
          v-model="useMonotone"
          label="smooth-mode=monotone"
          hide-details
        />
        <v-switch
          v-model="useBars"
          label="type=bar"
          hide-details
        />
      </div>

      <v-card
        class="pa-5"
        rounded="lg"
      >
        <div class="d-flex align-center ga-2 mb-1">
          <v-icon icon="mdi-chevron-down" size="18" />
          <span class="text-uppercase text-body-small font-weight-bold letter-spacing-2">Weekly Downloads</span>
          <v-spacer />
          <v-btn rounded="lg" size="small" variant="outlined" icon>
            <v-icon icon="mdi-chart-line-variant" size="18" />
          </v-btn>
        </div>
        <div class="text-body-small mb-2" style="color: #888">
          {{ npmHoveredWeek ?? npmLastWeek }}
        </div>
        <div class="d-flex align-center ga-4">
          <span class="text-h4 font-weight-bold" style="min-width: 160px">
            {{ npmHoveredValue ?? npmLastValue }}
          </span>
          <v-sparkline
            :line-width="useBars ? (400 / npmWeeklyValues.length - 2) : 1.5"
            :model-value="npmWeeklyValues"
            :smooth="smooth"
            :smooth-mode="useMonotone ? 'monotone' : 'default'"
            :type="useBars ? 'bar' : 'trend'"
            color="#888"
            height="50"
            marker-stroke="rgb(var(--v-theme-surface))"
            padding="4"
            stroke-linecap="round"
            style="flex: 1"
            width="400"
            interactive
            @update:current-index="npmHoveredIdx = $event"
          />
        </div>
      </v-card>
    </v-container>
    <v-btn
      class="ma-2"
      icon="mdi-theme-light-dark"
      location="top right"
      position="absolute"
      @click="$vuetify.theme.cycle()"
    />
  </v-app>
</template>

<script setup>
  import { computed, onMounted, shallowRef } from 'vue'

  import { useTheme } from '@/composables'
  const { change } = useTheme()
  change('dark')

  const useBars = shallowRef(false)
  const useMonotone = shallowRef(false)
  const showMarkers = shallowRef(false)
  const smooth = shallowRef(5)
  const period = shallowRef('month')
  const series1month = [45, 66, 41, 89, 25, 44, 9, 54]
  const series1year = [35, 44, 9, 54, 45, 66, 41, 69]
  const series2 = [0, 10, 10, 3, 3, 0.1, 0, 0.1, 10, 5]

  // NPM weekly downloads
  const npmWeeks = shallowRef([])
  const npmHoveredIdx = shallowRef(null)

  const npmWeeklyValues = computed(() => npmWeeks.value.map(w => w.total))
  const npmLastValue = computed(() => {
    const v = npmWeeks.value[npmWeeks.value.length - 1]?.total
    return v != null ? v.toLocaleString() : ''
  })
  const npmLastWeek = computed(() => {
    const w = npmWeeks.value[npmWeeks.value.length - 1]
    return w ? `${w.start} to ${w.end}` : ''
  })
  const npmHoveredValue = computed(() => {
    if (npmHoveredIdx.value == null) return null
    return npmWeeks.value[npmHoveredIdx.value]?.total?.toLocaleString()
  })
  const npmHoveredWeek = computed(() => {
    if (npmHoveredIdx.value == null) return null
    const w = npmWeeks.value[npmHoveredIdx.value]
    return w ? `${w.start} to ${w.end}` : null
  })

  onMounted(async () => {
    try {
      const res = await fetch('https://api.npmjs.org/downloads/range/2025-03-24:2026-03-22/vuetify')
      const data = await res.json()
      const days = data.downloads // [{ day, downloads }]

      // Group by week Mon-Sun
      const weeks = []
      let week = null
      for (const d of days) {
        const dow = new Date(d.day).getDay() // 0=Sun
        if (!week || dow === 1) { // Monday starts new week
          if (week) weeks.push(week)
          week = { start: d.day, end: d.day, total: 0 }
        }
        week.end = d.day
        week.total += d.downloads
      }
      if (week) weeks.push(week)

      npmWeeks.value = weeks
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error('Failed to fetch npm data', e)
    }
  })
</script>

<style>
  @import 'https://fonts.bunny.net/css?family=sora';
  body * {
    font-family: 'Sora', sans-serif !important;
  }
</style>

@J-Sek J-Sek self-assigned this Mar 23, 2026
@J-Sek J-Sek added this to the v4.1.0 milestone Mar 23, 2026
@J-Sek J-Sek force-pushed the feat/vsparkline-markers-and-tooltips branch from 2c0aaf0 to 80effcb Compare March 23, 2026 23:23
@J-Sek J-Sek force-pushed the dev branch 2 times, most recently from 5b257fd to 86800d9 Compare March 29, 2026 00:37
@J-Sek J-Sek force-pushed the feat/vsparkline-markers-and-tooltips branch from 3833316 to 22d1d0f Compare April 6, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant