Skip to content

Commit

Permalink
feat: Improve ChartLine.vue
Browse files Browse the repository at this point in the history
  • Loading branch information
HugoRCD committed Jan 9, 2025
1 parent 0af2168 commit 6ec1300
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 210 deletions.
257 changes: 79 additions & 178 deletions apps/currencia/app/components/chart/Line.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
<!-- eslint-disable -->
<script setup lang="ts">
import type { ApexOptions, TimeFrame, Variations } from '~~/types/ApexChart'
import {
VisXYContainer,
VisLine,
VisAxis,
VisArea,
VisCrosshair,
VisTooltip,
VisAnnotations
} from '@unovis/vue'
import type { PriceDataRecord } from "~~/types/Crypto";

type ChartLineProps = {
showTooltip?: boolean
data?: [number, number][]
data: PriceDataRecord[]
}

const colorMode = useColorMode()
const dayjs = useDayjs()

const { showTooltip = false, data } = defineProps<ChartLineProps>()
const emit = defineEmits(['update:currentValue', 'update:variation'])

const emit = defineEmits(['update:currentValue'])

const timeframe = ref<TimeFrame>({
value: '6M',
Expand All @@ -20,39 +31,19 @@ const timeframe = ref<TimeFrame>({

const firstValue = computed(() => {
const { start } = timeframe.value.series
const seriesData = series[0].data

for (let i = 0; i < seriesData.length; i++) {
const dataPoint = seriesData[i]
if (dataPoint[0] >= start) {
return dataPoint[1]
}
}
return data.find(d => dayjs(d.date).isSame(start, 'day'))?.price
})

const lastValue = computed(() => {
const { end } = timeframe.value.series
const seriesData = series[0].data

for (let i = seriesData.length - 1; i >= 0; i--) {
const dataPoint = seriesData[i]
if (dataPoint[0] <= end) {
return dataPoint[1]
}
}
return data.find(d => dayjs(d.date).isSame(end, 'day'))?.price
})

const isPositive = computed(() => {
if (!firstValue.value || !lastValue.value) return false
return lastValue.value > firstValue.value
})

const series = [
{
data: data ? data : getRandomDailyData(),
},
]

const price = ref(0)

function getVariation(start: number, end: number): Variations {
Expand All @@ -63,47 +54,16 @@ function getVariation(start: number, end: number): Variations {
}
}

const variation = computed(() => {
return getVariation(firstValue.value, price.value)
})

const chart = ref()

watch(colorMode, () => {
chart.value.chart.updateOptions({
theme: {
mode: colorMode.value,
},
fill: {
gradient: {
shade: colorMode.value,
opacityFrom: colorMode.value === 'dark' ? 0.6 : 0,
},
},
grid: {
borderColor: colorMode.value === 'dark' ? '#2A2A2B' : '#E5E7EB',
},
xaxis: {
labels: {
style: {
colors: colorMode.value === 'dark' ? '#9CA3AF' : '#4B5563',
},
},
},
})
})

watch(price, () => {
emit('update:currentValue', price.value ? price.value : lastValue.value)
}, { immediate: true })

watch(variation, () => {
emit('update:variation', variation.value)
}, { immediate: true })
const variations = defineModel<Variations>('variations')

function mouseOut() {
emit('update:currentValue', lastValue.value)
emit('update:variation', getVariation(firstValue.value, lastValue.value))
variations.value = getVariation(firstValue.value, lastValue.value)
}
mouseOut()

Expand All @@ -114,134 +74,44 @@ watch(timeframe, () => {
mouseOut()
})

const chartOptions = {
chart: {
id: 'area-datetime',
type: 'area',
zoom: {
enabled: false,
},
animations: {
enabled: true,
easing: 'linear',
dynamicAnimation: {
speed: 1000
}
},
toolbar: {
show: false,
},
background: 'transparent',
},
theme: {
mode: colorMode.value,
},
plotOptions: {
area: {
fillTo: 'end',
},
},
colors: ['var(--graph-curve)'],
fill: {
type: 'gradient',
gradient: {
shade: colorMode.value,
shadeIntensity: 0.1,
opacityFrom: colorMode.value === 'dark' ? 0.6 : 0,
opacityTo: 0,
stops: [0, 90, 100],
},
},
dataLabels: {
enabled: false,
},
stroke: {
curve: 'smooth',
width: 2,
},
grid: {
show: false,
borderColor: colorMode.value === 'dark' ? '#2A2A2B' : '#f2f3f4',
},
markers: {
size: 0,
style: 'hollow',
},
yaxis: {
labels: {
show: false,
},
},
xaxis: {
type: 'datetime',
min: timeframe.value.series.start,
tickAmount: 5,
labels: {
style: {
colors: colorMode.value === 'dark' ? '#9CA3AF' : '#4B5563',
},
formatter: function(value) {
return dayjs(value).format('DD.MM')
},
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
tooltip: {
custom: function({ series, seriesIndex, dataPointIndex }) {
const value = series[seriesIndex][dataPointIndex] as number
price.value = value
if (!showTooltip) return ''
return `<div class="px-4 py-1"><span>${displayNumberValue(value)}$</span></div>`
},
x: {
format: 'dd/MM/yy HH:mm',
formatter: function(value) {
return dayjs(value).format('DD.MM.YYYY')
},
},
y: {
title: {
formatter: function() {
return ''
},
},
formatter: function(value: number) {
return value.toFixed(2)
},
},
},
} satisfies ApexOptions
const xTicks = (i: number) => {
if (i === 0 || i === data.length - 1 || !data[i]) {
return ''
}

watch(() => data, () => {
// sort by timestamp
chart.value.chart.updateSeries([
{
data: data.sort((a, b) => a.timestamp - b.timestamp)
}
])
})
return formatDate(data[i].date)
}
const x = (_: PriceDataRecord, i: number) => i
const y = (d: PriceDataRecord) => d.price

const template = (d: PriceDataRecord) => {
price.value = d.price
variations.value = getVariation(firstValue.value, d.price)
return `${formatDate(d.date)}: ${d.price}$`
}
</script>

<template>
<div class="relative select-none">
{{ firstValue }}
{{ lastValue }}
<ChartTimeFrame @update:timeframe="timeframe = $event" />
<div class="relative">
<div class="relative mt-4">
<DotPattern />
<apexchart
id="chart"
ref="chart"
height="300"
type="area"
:options="chartOptions"
:series
:class="isPositive ? 'positive' : 'negative'"
@mouseout="mouseOut"
/>
<VisXYContainer
:data
:padding="{ top: 10 }"
class="h-80"
>
<VisLine :x :y color="rgb(var(--color-primary-DEFAULT))" :class="isPositive ? 'positive' : 'negative'" />
<VisArea :x :y color="rgb(var(--color-primary-DEFAULT))" :class="isPositive ? 'positive' : 'negative'" :opacity="0.1" />

<VisAxis type="x" :x :tick-format="xTicks" :grid-line="false" />

<VisCrosshair color="rgb(var(--color-primary-DEFAULT))" :template />

<VisTooltip />
</VisXYContainer>
</div>
</div>
</template>
Expand All @@ -254,4 +124,35 @@ watch(() => data, () => {
.negative {
--graph-curve: #ef4444;
}

.unovis-xy-container {
--vis-crosshair-line-stroke-color: rgb(var(--graph-curve));
--vis-crosshair-circle-stroke-color: #fff;

--vis-axis-grid-color: rgb(var(--color-gray-200));
--vis-axis-tick-color: rgb(var(--color-gray-200));
--vis-axis-tick-label-color: rgb(var(--color-gray-400));

--vis-tooltip-background-color: #fff;
--vis-tooltip-border-color: rgb(var(--color-gray-200));
--vis-tooltip-text-color: rgb(var(--color-gray-900));

--vis-annotation-text-color: rgb(var(--color-primary-500));
}

.dark {
.unovis-xy-container {
--vis-crosshair-line-stroke-color: rgb(var(--color-primary-400));
--vis-crosshair-circle-stroke-color: rgb(var(--color-gray-900));

--vis-axis-grid-color: rgb(var(--color-gray-800));
--vis-axis-tick-color: rgb(var(--color-gray-800));
--vis-axis-tick-label-color: rgb(var(--color-gray-500));

--vis-tooltip-background-color: rgb(var(--color-gray-900));
--vis-tooltip-border-color: rgb(var(--color-gray-800));
--vis-tooltip-text-color: #fff;
--vis-annotation-text-color: rgb(var(--color-primary-400));
}
}
</style>
16 changes: 13 additions & 3 deletions apps/currencia/app/pages/app/crypto/[symbol].vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import NumberFlow from '@number-flow/vue'
import type { Variations } from '~~/types/ApexChart'
import type { Crypto } from '~~/types/Crypto'
import type { Crypto, PriceDataRecord } from '~~/types/Crypto'
const dayjs = useDayjs()
Expand All @@ -20,7 +20,17 @@ const variations = ref<Variations>({
const price = useCryptoPrice(symbol)
const isHovered = ref(false)
const data = ref<[number, number][]>()
const { data } = await useAsyncData<PriceDataRecord[]>(async () => {
const dates = ['2024-01-01', '2024-02-02', '2024-03-03', '2024-03-04', '2024-04-05']
const min = 1000
const max = 10000
return dates.map(date => ({ date, price: Math.floor(Math.random() * (max - min + 1)) + min }))
}, {
watch: [],
default: () => []
})
const eventSource = new EventSource(`${location.origin}/api/crypto/${symbol}`)
Expand Down Expand Up @@ -69,11 +79,11 @@ onUnmounted(() => {
</div>
</div>
<ChartLine
v-model:variations="variations"
style="--stagger: 3; --delay: 100ms"
data-animate
:data
@update:current-value="price = $event"
@update:variation="variations = $event"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
/>
Expand Down
Loading

0 comments on commit 6ec1300

Please sign in to comment.