chore: add feature

This commit is contained in:
zernonia 2024-04-26 00:35:26 +08:00
parent 1f6398f8d1
commit 4159f56616
13 changed files with 140 additions and 35 deletions

View File

@ -41,3 +41,36 @@ Add the following tooltip styling to your `tailwind.css` file:
```
</Steps>
## Colors
By default, we construct the primary theme color, and secondary (`--vis-secondary-color`) color with different opacity for the graph.
However, you can always pass in the desired `color` into each chart.
```vue
<template>
<AreaChart
:data="data"
:colors="['blue', 'pink', 'orange', 'red']"
/>
</template>
```
## Custom tooltip
If you want to customize the `Tooltip` for the chart, you can pass `customTooltip` prop with a custom Vue component.
The custom component would receive `title` and `data` props, check out [ChartTooltip.vue component](https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/lib/registry/default/ui/chart/ChartTooltip.vue) for example.
The expecting prop definition would be
```ts
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
```

View File

@ -1,19 +1,28 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface } from '@unovis/ts'
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { Area, Axis, Line } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
/**
* Controls the visibility of gradient.
* @default true
*/
showGradiant?: boolean
}>(), {
curveType: CurveType.Basis,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
@ -66,13 +75,14 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
</defs>
</svg>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" />
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" :custom-tooltip="customTooltip" />
<template v-for="(category, i) in categories" :key="category">
<VisArea
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
color="auto"
:curve-type="curveType"
:attributes="{
[Area.selectors.area]: {
fill: `url(#color-${i})`,
@ -87,6 +97,7 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
:color="colors[i]"
:curve-type="curveType"
:attributes="{
[Line.selectors.line]: {
opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,

View File

@ -2,12 +2,16 @@
import type { BulletLegendItemInterface, Spacing } from '@unovis/ts'
import { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { Axis, GroupedBar, StackedBar } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Change the type of the chart
* @default "grouped"
@ -57,7 +61,7 @@ const selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.select
<template>
<div :class="cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')">
<ChartLegend v-if="showLegend" v-model:items="legendItems" @legend-item-click="handleLegendItemClick" />
<ChartLegend v-if="showLegend" v-model:items="legendItems" :custom-tooltip="customTooltip" @legend-item-click="handleLegendItemClick" />
<VisXYContainer
:data="data"

View File

@ -1,7 +1,7 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { VisDonut, VisSingleContainer } from '@unovis/vue'
import { Donut } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type DefineComponent, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartSingleTooltip, defaultColors } from '@/lib/registry/default/ui/chart'
import { cn } from '@/lib/utils'
@ -24,7 +24,10 @@ const props = withDefaults(defineProps<Pick<BaseChartProps<T>, 'data' | 'colors'
* Controls the formatting for the label.
*/
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
/**
* Render custom tooltip component.
*/
customTooltip?: DefineComponent
}>(), {
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
@ -63,6 +66,7 @@ const totalValue = computed(() => props.data.reduce((prev, curr) => {
:index="category"
:items="legendItems"
:value-formatter="valueFormatter"
:custom-tooltip="customTooltip"
/>
<VisDonut

View File

@ -1,13 +1,23 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface } from '@unovis/ts'
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { Axis, Line } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T>>(), {
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
}>(), {
curveType: CurveType.Basis,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
@ -49,12 +59,13 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" />
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" :custom-tooltip="customTooltip" />
<template v-for="(category, i) in categories" :key="category">
<VisLine
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
:curve-type="curveType"
:color="colors[i]"
:attributes="{
[Line.selectors.line]: {

View File

@ -2,13 +2,14 @@
import { VisCrosshair, VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { createApp, watch } from 'vue'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/default/ui/chart'
const props = withDefaults(defineProps<{
colors: string[]
index: string
items: BulletLegendItemInterface[]
customTooltip?: Component
}>(), {
colors: () => [],
})
@ -25,7 +26,8 @@ function template(d: any) {
const legendReference = props.items.find(i => i.name === key)
return { ...legendReference, value }
})
createApp(ChartTooltip, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}

View File

@ -2,7 +2,7 @@
import { VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { createApp } from 'vue'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/default/ui/chart'
const props = withDefaults(defineProps<{
@ -10,6 +10,7 @@ const props = withDefaults(defineProps<{
index: string
items?: BulletLegendItemInterface[]
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
customTooltip?: Component
}>(), {
valueFormatter: (tick: number) => `${tick}`,
})
@ -27,11 +28,13 @@ function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
const legendReference = props.items?.find(i => i.name === key)
return { ...legendReference, value: props.valueFormatter(value) }
})
createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
else {
const data = d.data
@ -42,7 +45,8 @@ function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
const style = getComputedStyle(elements[i])
const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]
const componentDiv = document.createElement('div')
createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}

View File

@ -1,19 +1,28 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface, Spacing } from '@unovis/ts'
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { Area, Axis, Line } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
/**
* Controls the visibility of gradient.
* @default true
*/
showGradiant?: boolean
}>(), {
curveType: CurveType.Basis,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
@ -66,13 +75,14 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
</defs>
</svg>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" />
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" :custom-tooltip="customTooltip" />
<template v-for="(category, i) in categories" :key="category">
<VisArea
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
color="auto"
:curve-type="curveType"
:attributes="{
[Area.selectors.area]: {
fill: `url(#color-${i})`,
@ -87,6 +97,7 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
:color="colors[i]"
:curve-type="curveType"
:attributes="{
[Line.selectors.line]: {
opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,

View File

@ -2,12 +2,16 @@
import type { BulletLegendItemInterface, Spacing } from '@unovis/ts'
import { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { Axis, GroupedBar, StackedBar } from '@unovis/ts'
import { computed, ref } from 'vue'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Change the type of the chart
* @default "grouped"
@ -57,7 +61,7 @@ const selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.select
<template>
<div :class="cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')">
<ChartLegend v-if="showLegend" v-model:items="legendItems" @legend-item-click="handleLegendItemClick" />
<ChartLegend v-if="showLegend" v-model:items="legendItems" :custom-tooltip="customTooltip" @legend-item-click="handleLegendItemClick" />
<VisXYContainer
:data="data"

View File

@ -1,7 +1,7 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { VisDonut, VisSingleContainer } from '@unovis/vue'
import { Donut, type Spacing } from '@unovis/ts'
import { computed, ref } from 'vue'
import { Donut } from '@unovis/ts'
import { type DefineComponent, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartSingleTooltip, defaultColors } from '@/lib/registry/new-york/ui/chart'
import { cn } from '@/lib/utils'
@ -24,7 +24,10 @@ const props = withDefaults(defineProps<Pick<BaseChartProps<T>, 'data' | 'colors'
* Controls the formatting for the label.
*/
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
/**
* Render custom tooltip component.
*/
customTooltip?: DefineComponent
}>(), {
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
@ -63,6 +66,7 @@ const totalValue = computed(() => props.data.reduce((prev, curr) => {
:index="category"
:items="legendItems"
:value-formatter="valueFormatter"
:custom-tooltip="customTooltip"
/>
<VisDonut
@ -75,7 +79,6 @@ const totalValue = computed(() => props.data.reduce((prev, curr) => {
:events="{
[Donut.selectors.segment]: {
click: (d: Data, ev: PointerEvent, i: number, elements: HTMLElement[]) => {
console.log(d, ev, i, elements)
if (d?.data?.[index] === activeSegmentKey) {
activeSegmentKey = undefined
elements.forEach(el => el.style.opacity = '1')

View File

@ -1,12 +1,23 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { Axis, type BulletLegendItemInterface, Line, type Spacing } from '@unovis/ts'
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { computed, ref } from 'vue'
import { Axis, Line } from '@unovis/ts'
import { type Component, computed, ref } from 'vue'
import { useMounted } from '@vueuse/core'
import { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/new-york/ui/chart'
import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<BaseChartProps<T>>(), {
const props = withDefaults(defineProps<BaseChartProps<T> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
}>(), {
curveType: CurveType.Basis,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
@ -44,16 +55,17 @@ function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
<ChartLegend v-if="showLegend" v-model:items="legendItems" @legend-item-click="handleLegendItemClick" />
<VisXYContainer
:margin="margin"
:margin="{ left: 20, right: 20 }"
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" />
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :index="index" :custom-tooltip="customTooltip" />
<template v-for="(category, i) in categories" :key="category">
<VisLine
:x="(d: Data, i: number) => i"
:y="(d: Data) => d[category]"
:curve-type="curveType"
:color="colors[i]"
:attributes="{
[Line.selectors.line]: {

View File

@ -2,13 +2,14 @@
import { VisCrosshair, VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { createApp, watch } from 'vue'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/new-york/ui/chart'
const props = withDefaults(defineProps<{
colors: string[]
index: string
items: BulletLegendItemInterface[]
customTooltip?: Component
}>(), {
colors: () => [],
})
@ -25,7 +26,8 @@ function template(d: any) {
const legendReference = props.items.find(i => i.name === key)
return { ...legendReference, value }
})
createApp(ChartTooltip, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}

View File

@ -2,7 +2,7 @@
import { VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { createApp } from 'vue'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/new-york/ui/chart'
const props = withDefaults(defineProps<{
@ -10,6 +10,7 @@ const props = withDefaults(defineProps<{
index: string
items?: BulletLegendItemInterface[]
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
customTooltip?: Component
}>(), {
valueFormatter: (tick: number) => `${tick}`,
})
@ -27,11 +28,13 @@ function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
const legendReference = props.items?.find(i => i.name === key)
return { ...legendReference, value: props.valueFormatter(value) }
})
createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
else {
const data = d.data
@ -42,7 +45,8 @@ function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
const style = getComputedStyle(elements[i])
const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]
const componentDiv = document.createElement('div')
createApp(ChartTooltip, { title: d[props.index], data: omittedData }).mount(componentDiv)
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}