kodadot/nft-gallery

View on GitHub
components/shared/collection/PriceChart.vue

Summary

Maintainability
Test Coverage
<template>
  <div class="price-chart mt-4">
    <div class="flex items-center justify-between">
      <p class="label text-xs/normal">
        {{ $t('Chart') }}
      </p>
      <NeoButton
        variant="primary"
        no-shadow
        @click="emit('resetZoom')"
      >
        Reset zoom
      </NeoButton>
    </div>
    <div class="chart-container mt-5">
      <LineChart
        ref="lineChart"
        :datasets="datasets"
        :labels="labels"
        :options="options"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import type { ChartDataset } from 'chart.js'
import type {
  CollectionChartData as ChartData } from '@/utils/chart'
import {
  getCollectionChartData,
  getCollectionMedian,
  getLabel,
  getMovingAverage,
  mapToAverage,
} from '@/utils/chart'

// types
import LineChart from '@/components/shared/chart/LineChart.vue'

const baseLineOptions = {
  tension: 0.3,
  pointBackgroundColor: 'white',
  pointBorderColor: 'blue',
  pointRadius: 4,
  pointHoverRadius: 6,
}

const props = defineProps<{
  priceData: [ChartData[], ChartData[]] // [listings, buys]
}>()
const emit = defineEmits(['resetZoom'])

const { unit } = useChain()

const labels = ref<Date[]>([])
const datasets = ref<ChartDataset[]>([])
const options = ref({})

onMounted(() => {
  priceChart()
})

watch(
  () => props.priceData,
  () => priceChart,
)

const priceChart = () => {
  if (props.priceData[0]?.length || props.priceData[1]?.length) {
    const median = getCollectionMedian(props.priceData[1])
    const labelsListing = props.priceData[0].map(getLabel)
    const labelsBuy = props.priceData[1].map(getLabel)

    labels.value = [...new Set([...labelsListing, ...labelsBuy])]
    datasets.value = [
      {
        label: 'Floor Price',
        data: getCollectionChartData(props.priceData[0]) as any,
        borderColor: '#d32e79',
        ...baseLineOptions,
      },
      {
        label: 'Sold NFT Price',
        data: getCollectionChartData(props.priceData[1]),
        borderColor: '#00BB7F',
        ...baseLineOptions,
      },
    ]

    if (props.priceData[1][0]?.average) {
      datasets.value.push({
        label: 'Trailing Average',
        data: getMovingAverage(
          getCollectionChartData(props.priceData[1], mapToAverage),
        ),
        borderColor: 'yellow',
        ...baseLineOptions,
      })
    }

    options.value = {
      maintainAspectRatio: false,
      plugins: {
        tooltip: {
          xAlign: 'center',
          yAlign: 'top',
          callbacks: {
            afterLabel: ({ dataIndex, dataset }) => {
              return `Count: ${dataset.data[dataIndex]?.count || 0}`
            },
          },
        },
        annotation: {
          annotations: {
            median: {
              type: 'line',
              yMin: median,
              yMax: median,
              borderColor: '#00BB7F',
              borderWidth: 2,
              borderDash: [10, 5],
            },
          },
        },
        zoom: {
          pan: {
            enabled: false,
          },
          zoom: {
            drag: {
              enabled: true,
              backgroundColor: '',
            },
            pinch: {
              enabled: true,
            },
            mode: 'xy',
            onZoomComplete({ chart }) {
              chart.update('none')
            },
          },
        },
      },
      scales: {
        x: {
          type: 'time',
          time: {
            unit: 'day',
          },
          ticks: {
            maxRotation: 0,
            minRotation: 0,
            color: '#fff',
          },
        },
        y: {
          ticks: {
            callback: (value) => {
              return `${Number(value).toFixed(2)} ${unit.value}`
            },
            maxTicksLimit: 7,
            color: '#fff',
          },
          grid: {
            color: '#3a3a3a',
          },
        },
      },
    }
  }
}
</script>

<style scoped lang="scss">
@import 'bulma/sass/utilities/mixins.sass';

.chart-container {
  position: relative;
  height: 45vh;
  width: 100%;
}
@media screen and (orientation: landscape) {
  .chart-container {
    height: 80vh;
  }
}
@include tablet-only {
  .chart-container {
    height: 80vh;
  }
}
@include desktop {
  .chart-container {
    height: 656px;
  }
}
</style>