feat: Charts (#166)

* chore: update unovis deps

* chore: update color to use the themePrimary

* docs: use gradient for overview component

* docs: add themePopover to MainLayout

* docs: enable global theme on every page

* feat: introduce area, line, bar, donut chart

* feat: add more props

* fix: revert old pipeline

* fix: patch @unovis/vue deps

* fix: patch @unovis/vue deps again

* chore: revert unovis/ts to 1.2.1

* chore: wip

* docs: add alpha tag, fix tooltipo styling

* docs: add charts installations step

* feat: use generic, add better color

* chore: build registry

* feat: improve generic props

* chore: build registry

* docs: add alpha label

* fix: collapsible not open correctly

* docs: add badge to mobile nav

* chore: better types

* chore: run registry

* chore: wip

* fix: crosshair issue

* chore: fix type, import missing error

* chore: build registry

* chore: arrange interface, expose margin, slot

* chore: build registry

* docs: guide page
feat: add prop to barchart

* chore: fix pnpm-lock

* chore: add feature

* chore: run build registry

* refactor: change color var

* feat: codegen

* chore: add meta tables

* feat: add line, area example

* feat: bar and donut examples

* docs: codege

* chore: build registry

* docs: improve chart doc

* chore: fix missing icon package
This commit is contained in:
zernonia 2024-05-01 11:34:58 +08:00 committed by GitHub
parent 32d7b9ca4a
commit 77c6a16040
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
210 changed files with 5753 additions and 269 deletions

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { capitalize } from 'vue'
defineProps<{
type: 'prop' | 'emit' | 'slot' | 'method'
data: { name: string, description: string, type: string, required: boolean }[]
}>()
</script>
<template>
<div>
<h3>{{ capitalize(type) }}</h3>
<div v-for="(item, index) in data" :key="index" class="py-4 border-b text-sm">
<div class="flex items-center gap-2 flex-wrap">
<h5 class="text-sm">
<code>{{ item.name }}</code>
</h5>
<code>{{ item.type }}</code>
<span v-if="item.required" class="font-normal text-red-500 text-xs">Required*</span>
</div>
<div class="[&_p]:!my-2 ml-1" v-html="item.description" />
</div>
</div>
</template>

View File

@ -5,6 +5,7 @@ import Logo from './Logo.vue'
import { Sheet, SheetContent, SheetTrigger } from '@/lib/registry/default/ui/sheet'
import { Button } from '@/lib/registry/default/ui/button'
import { ScrollArea } from '@/lib/registry/default/ui/scroll-area'
import { Badge } from '@/lib/registry/new-york/ui/badge'
const open = ref(false)
</script>
@ -63,17 +64,26 @@ const open = ref(false)
</div>
<div class="flex flex-col space-y-2">
<div v-for="(items, index) in docsConfig.sidebarNav" :key="index" class="flex flex-col space-y-3 pt-6">
<h4 class="font-medium">
{{ items.title }}
</h4>
<div class="flex items-center">
<h4 class="font-medium">
{{ items.title }}
</h4>
<Badge v-if="items.label" class="ml-2">
{{ items.label }}
</Badge>
</div>
<a
v-for="item in items.items" :key="item.href"
:href="item.href"
class="text-muted-foreground"
class="text-muted-foreground inline-flex items-center"
@click="open = false"
>
{{ item.title }}
<Badge v-if="item.label" class="ml-2">
{{ item.label }}
</Badge>
</a>
</div>
</div>

View File

@ -0,0 +1,47 @@
<script setup lang="ts">
import { Paintbrush } from 'lucide-vue-next'
import { onMounted, watch } from 'vue'
import { allColors } from './theming/utils/data'
import ThemeCustomizer from './ThemeCustomizer.vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
import { useConfigStore } from '@/stores/config'
const { theme, radius } = useConfigStore()
// Whenever the component is mounted, update the document class list
onMounted(() => {
document.documentElement.style.setProperty('--radius', `${radius.value}rem`)
document.documentElement.classList.add(`theme-${theme.value}`)
})
// Whenever the theme value changes, update the document class list
watch(theme, (theme) => {
document.documentElement.classList.remove(
...allColors.map(color => `theme-${color}`),
)
document.documentElement.classList.add(`theme-${theme}`)
})
// Whenever the radius value changes, update the document style
watch(radius, (radius) => {
document.documentElement.style.setProperty('--radius', `${radius}rem`)
})
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button
class="w-9 h-9"
:variant="'ghost'"
:size="'icon'"
>
<Paintbrush class="w-4 h-4" />
</Button>
</PopoverTrigger>
<PopoverContent :side-offset="8" align="end" class="w-96">
<ThemeCustomizer :all-colors="allColors" />
</PopoverContent>
</Popover>
</template>

View File

@ -1,5 +1,6 @@
export { default as CodeWrapper } from './CodeWrapper'
export { default as ComponentPreview } from './ComponentPreview.vue'
export { default as APITable } from './APITable.vue'
export { default as TabPreview } from './TabPreview.vue'
export { default as TabMarkdown } from './TabMarkdown.vue'
export { default as TabsMarkdown } from './TabsMarkdown.vue'

View File

@ -18,9 +18,7 @@ import Metric from '@/lib/registry/new-york/example/Cards/Metric.vue'
import DataTable from '@/lib/registry/new-york/example/Cards/DataTable.vue'
import CardStats from '@/lib/registry/new-york/example/CardStats.vue'
import {
Card,
} from '@/lib/registry/new-york/ui/card'
import { Card } from '@/lib/registry/new-york/ui/card'
import { RangeCalendar } from '@/lib/registry/new-york/ui/range-calendar'
const now = today(getLocalTimeZone())

View File

@ -2,6 +2,36 @@ import { CreditCard } from 'lucide-vue-next'
import RiAppleFill from '~icons/ri/apple-fill'
import RiPaypalFill from '~icons/ri/paypal-fill'
type Color =
| 'zinc'
| 'slate'
| 'stone'
| 'gray'
| 'neutral'
| 'red'
| 'rose'
| 'orange'
| 'green'
| 'blue'
| 'yellow'
| 'violet'
// Create an array of color values
export const allColors: Color[] = [
'zinc',
'rose',
'blue',
'green',
'orange',
'red',
'slate',
'stone',
'gray',
'neutral',
'yellow',
'violet',
]
interface Payment {
status: string
email: string

View File

@ -12,7 +12,7 @@ export type SidebarNavItem = NavItem & {
}
export type NavItemWithChildren = NavItem & {
items: NavItemWithChildren[]
items?: NavItemWithChildren[]
}
interface DocsConfig {
@ -55,22 +55,18 @@ export const docsConfig: DocsConfig = {
{
title: 'Introduction',
href: '/docs/introduction',
items: [],
},
{
title: 'Installation',
href: '/docs/installation',
items: [],
},
{
title: 'components.json',
href: '/docs/components-json',
items: [],
},
{
title: 'Theming',
href: '/docs/theming',
items: [],
},
{
title: 'Dark Mode',
@ -80,27 +76,22 @@ export const docsConfig: DocsConfig = {
{
title: 'CLI',
href: '/docs/cli',
items: [],
},
{
title: 'Typography',
href: '/docs/typography',
items: [],
},
{
title: 'Figma',
href: '/docs/figma',
items: [],
},
{
title: 'Changelog',
href: '/docs/changelog',
items: [],
},
{
title: 'About',
href: '/docs/about',
items: [],
},
{
title: 'Contribution',
@ -115,22 +106,18 @@ export const docsConfig: DocsConfig = {
{
title: 'Vite',
href: '/docs/installation/vite',
items: [],
},
{
title: 'Nuxt',
href: '/docs/installation/nuxt',
items: [],
},
{
title: 'Astro',
href: '/docs/installation/astro',
items: [],
},
{
title: 'Laravel',
href: '/docs/installation/laravel',
items: [],
},
],
},
@ -142,6 +129,12 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/auto-form',
items: [],
},
{
title: 'Charts',
href: '/docs/charts',
label: 'Alpha',
items: [],
},
],
},
{
@ -150,32 +143,26 @@ export const docsConfig: DocsConfig = {
{
title: 'Accordion',
href: '/docs/components/accordion',
items: [],
},
{
title: 'Alert',
href: '/docs/components/alert',
items: [],
},
{
title: 'Alert Dialog',
href: '/docs/components/alert-dialog',
items: [],
},
{
title: 'Aspect Ratio',
href: '/docs/components/aspect-ratio',
items: [],
},
{
title: 'Avatar',
href: '/docs/components/avatar',
items: [],
},
{
title: 'Badge',
href: '/docs/components/badge',
items: [],
},
{
title: 'Breadcrumb',
@ -185,7 +172,6 @@ export const docsConfig: DocsConfig = {
{
title: 'Button',
href: '/docs/components/button',
items: [],
},
{
title: 'Calendar',
@ -196,7 +182,6 @@ export const docsConfig: DocsConfig = {
{
title: 'Card',
href: '/docs/components/card',
items: [],
},
{
title: 'Carousel',
@ -206,32 +191,26 @@ export const docsConfig: DocsConfig = {
{
title: 'Checkbox',
href: '/docs/components/checkbox',
items: [],
},
{
title: 'Collapsible',
href: '/docs/components/collapsible',
items: [],
},
{
title: 'Combobox',
href: '/docs/components/combobox',
items: [],
},
{
title: 'Command',
href: '/docs/components/command',
items: [],
},
{
title: 'Context Menu',
href: '/docs/components/context-menu',
items: [],
},
{
title: 'Data Table',
href: '/docs/components/data-table',
items: [],
},
{
title: 'Date Picker',
@ -242,7 +221,6 @@ export const docsConfig: DocsConfig = {
{
title: 'Dialog',
href: '/docs/components/dialog',
items: [],
},
{
title: 'Drawer',
@ -252,42 +230,34 @@ export const docsConfig: DocsConfig = {
{
title: 'Dropdown Menu',
href: '/docs/components/dropdown-menu',
items: [],
},
{
title: 'Form',
href: '/docs/components/form',
items: [],
},
{
title: 'Hover Card',
href: '/docs/components/hover-card',
items: [],
},
{
title: 'Input',
href: '/docs/components/input',
items: [],
},
{
title: 'Label',
href: '/docs/components/label',
items: [],
},
{
title: 'Menubar',
href: '/docs/components/menubar',
items: [],
},
{
title: 'Navigation Menu',
href: '/docs/components/navigation-menu',
items: [],
},
{
title: 'Pagination',
href: '/docs/components/pagination',
items: [],
},
{
title: 'Pin Input',
@ -297,17 +267,14 @@ export const docsConfig: DocsConfig = {
{
title: 'Popover',
href: '/docs/components/popover',
items: [],
},
{
title: 'Progress',
href: '/docs/components/progress',
items: [],
},
{
title: 'Radio Group',
href: '/docs/components/radio-group',
items: [],
},
{
title: 'Range Calendar',
@ -323,32 +290,26 @@ export const docsConfig: DocsConfig = {
{
title: 'Scroll Area',
href: '/docs/components/scroll-area',
items: [],
},
{
title: 'Select',
href: '/docs/components/select',
items: [],
},
{
title: 'Separator',
href: '/docs/components/separator',
items: [],
},
{
title: 'Sheet',
href: '/docs/components/sheet',
items: [],
},
{
title: 'Skeleton',
href: '/docs/components/skeleton',
items: [],
},
{
title: 'Slider',
href: '/docs/components/slider',
items: [],
},
{
title: 'Sonner',
@ -358,17 +319,14 @@ export const docsConfig: DocsConfig = {
{
title: 'Switch',
href: '/docs/components/switch',
items: [],
},
{
title: 'Table',
href: '/docs/components/table',
items: [],
},
{
title: 'Tabs',
href: '/docs/components/tabs',
items: [],
},
{
title: 'Tags Input',
@ -378,27 +336,22 @@ export const docsConfig: DocsConfig = {
{
title: 'Textarea',
href: '/docs/components/textarea',
items: [],
},
{
title: 'Toast',
href: '/docs/components/toast',
items: [],
},
{
title: 'Toggle',
href: '/docs/components/toggle',
items: [],
},
{
title: 'Toggle Group',
href: '/docs/components/toggle-group',
items: [],
},
{
title: 'Tooltip',
href: '/docs/components/tooltip',
items: [],
},
],
},

View File

@ -27,6 +27,10 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
class="mb-1 rounded-md px-2 py-1 text-sm font-semibold"
>
{{ docsGroup.title }}
<span v-if="docsGroup.label" class="ml-2 font-normal rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ docsGroup.label }}
</span>
</h4>
<div
@ -72,9 +76,14 @@ const sourceLink = 'https://github.com/radix-vue/shadcn-vue/tree/dev/'
</div>
<div class="space-y-2">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
{{ frontmatter.title }}
</h1>
<div class="flex items-center space-x-4">
<h1 class="scroll-m-20 text-4xl font-bold tracking-tight">
{{ frontmatter.title }}
</h1>
<span v-if="frontmatter.label" class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline">
{{ frontmatter.label }}
</span>
</div>
<p class="text-lg text-muted-foreground">
{{ frontmatter.description }}
</p>

View File

@ -8,6 +8,7 @@ import MobileNav from '../components/MobileNav.vue'
import CodeConfigCustomizer from '../components/CodeConfigCustomizer.vue'
import Kbd from '../components/Kbd.vue'
import ThemePopover from '../components/ThemePopover.vue'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/default/ui/command'
import { Button } from '@/lib/registry/default/ui/button'
@ -133,6 +134,8 @@ watch(() => $route.path, (n) => {
</div>
<nav class="flex items-center">
<ThemePopover />
<CodeConfigCustomizer />
<Button

View File

@ -27,6 +27,21 @@
--input: 240 5.9% 90%;
--ring: 240 5% 64.9%;
--radius: 0.5rem;
--vis-primary-color: var(--primary);
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
--vis-font-family: inherit !important;
--vis-area-stroke-width: 2px !important;
--vis-donut-central-label-text-color: hsl(var(--muted-foreground)) !important;
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
}
.dark {
@ -142,7 +157,7 @@
}
div[class^="language-"] {
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-zinc-950 dark:!bg-zinc-900
@apply mb-4 mt-6 max-h-[650px] overflow-x-auto md:rounded-lg border !bg-secondary-foreground dark:!bg-secondary
}
pre {
@apply py-4;

View File

@ -345,14 +345,14 @@
}
.vp-doc [class*='language-'] code .highlighted {
background-color: hsl(240 3.7% 15.9%);
transition: background-color 0.5s;
/* margin: 0 -24px;
padding: 0 24px; */
width: calc(100% + 2 * 24px);
display: inline-block;
@apply bg-[hsl(var(--foreground))] dark:bg-[hsl(var(--background)_/_50%)]
}
hsl(var(--foreground) / 50%)
.vp-doc [class*='language-'] code .highlighted.error {
background-color: var(--vp-code-line-error-color);
}

View File

@ -31,6 +31,27 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/AlertDialogDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AlertDialogDemo.vue"],
},
"AreaChartCustomTooltip": {
name: "AreaChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartCustomTooltip.vue"],
},
"AreaChartDemo": {
name: "AreaChartDemo",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartDemo.vue"],
},
"AreaChartSparkline": {
name: "AreaChartSparkline",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/default/example/AreaChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AreaChartSparkline.vue"],
},
"AspectRatioDemo": {
name: "AspectRatioDemo",
type: "components:example",
@ -129,6 +150,34 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/BadgeSecondaryDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BadgeSecondaryDemo.vue"],
},
"BarChartCustomTooltip": {
name: "BarChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartCustomTooltip.vue"],
},
"BarChartDemo": {
name: "BarChartDemo",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartDemo.vue"],
},
"BarChartRounded": {
name: "BarChartRounded",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartRounded.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartRounded.vue"],
},
"BarChartStacked": {
name: "BarChartStacked",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/default/example/BarChartStacked.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/BarChartStacked.vue"],
},
"BreadcrumbDemo": {
name: "BreadcrumbDemo",
type: "components:example",
@ -444,6 +493,13 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/ContextMenuDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/ContextMenuDemo.vue"],
},
"CustomChartTooltip": {
name: "CustomChartTooltip",
type: "components:example",
registryDependencies: ["card"],
component: () => import("../src/lib/registry/default/example/CustomChartTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/CustomChartTooltip.vue"],
},
"DataTableColumnPinningDemo": {
name: "DataTableColumnPinningDemo",
type: "components:example",
@ -528,6 +584,34 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DialogScrollOverlayDemo.vue"],
},
"DonutChartColor": {
name: "DonutChartColor",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartColor.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartColor.vue"],
},
"DonutChartCustomTooltip": {
name: "DonutChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartCustomTooltip.vue"],
},
"DonutChartDemo": {
name: "DonutChartDemo",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartDemo.vue"],
},
"DonutChartPie": {
name: "DonutChartPie",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/default/example/DonutChartPie.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/DonutChartPie.vue"],
},
"DrawerDemo": {
name: "DrawerDemo",
type: "components:example",
@ -633,6 +717,27 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/LabelDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LabelDemo.vue"],
},
"LineChartCustomTooltip": {
name: "LineChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartCustomTooltip.vue"],
},
"LineChartDemo": {
name: "LineChartDemo",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartDemo.vue"],
},
"LineChartSparkline": {
name: "LineChartSparkline",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/default/example/LineChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/LineChartSparkline.vue"],
},
"MenubarDemo": {
name: "MenubarDemo",
type: "components:example",
@ -1327,6 +1432,27 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/AlertDialogDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AlertDialogDemo.vue"],
},
"AreaChartCustomTooltip": {
name: "AreaChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartCustomTooltip.vue"],
},
"AreaChartDemo": {
name: "AreaChartDemo",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartDemo.vue"],
},
"AreaChartSparkline": {
name: "AreaChartSparkline",
type: "components:example",
registryDependencies: ["chart-area"],
component: () => import("../src/lib/registry/new-york/example/AreaChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AreaChartSparkline.vue"],
},
"AspectRatioDemo": {
name: "AspectRatioDemo",
type: "components:example",
@ -1425,6 +1551,34 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BadgeSecondaryDemo.vue"],
},
"BarChartCustomTooltip": {
name: "BarChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartCustomTooltip.vue"],
},
"BarChartDemo": {
name: "BarChartDemo",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartDemo.vue"],
},
"BarChartRounded": {
name: "BarChartRounded",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartRounded.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartRounded.vue"],
},
"BarChartStacked": {
name: "BarChartStacked",
type: "components:example",
registryDependencies: ["chart-bar"],
component: () => import("../src/lib/registry/new-york/example/BarChartStacked.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/BarChartStacked.vue"],
},
"BreadcrumbDemo": {
name: "BreadcrumbDemo",
type: "components:example",
@ -1740,6 +1894,13 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/ContextMenuDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/ContextMenuDemo.vue"],
},
"CustomChartTooltip": {
name: "CustomChartTooltip",
type: "components:example",
registryDependencies: ["card"],
component: () => import("../src/lib/registry/new-york/example/CustomChartTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/CustomChartTooltip.vue"],
},
"DataTableColumnPinningDemo": {
name: "DataTableColumnPinningDemo",
type: "components:example",
@ -1824,6 +1985,34 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue"],
},
"DonutChartColor": {
name: "DonutChartColor",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartColor.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartColor.vue"],
},
"DonutChartCustomTooltip": {
name: "DonutChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartCustomTooltip.vue"],
},
"DonutChartDemo": {
name: "DonutChartDemo",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartDemo.vue"],
},
"DonutChartPie": {
name: "DonutChartPie",
type: "components:example",
registryDependencies: ["chart-donut"],
component: () => import("../src/lib/registry/new-york/example/DonutChartPie.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/DonutChartPie.vue"],
},
"DrawerDemo": {
name: "DrawerDemo",
type: "components:example",
@ -1929,6 +2118,27 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/LabelDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LabelDemo.vue"],
},
"LineChartCustomTooltip": {
name: "LineChartCustomTooltip",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartCustomTooltip.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartCustomTooltip.vue"],
},
"LineChartDemo": {
name: "LineChartDemo",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartDemo.vue"],
},
"LineChartSparkline": {
name: "LineChartSparkline",
type: "components:example",
registryDependencies: ["chart-line"],
component: () => import("../src/lib/registry/new-york/example/LineChartSparkline.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/LineChartSparkline.vue"],
},
"MenubarDemo": {
name: "MenubarDemo",
type: "components:example",

View File

@ -12,7 +12,8 @@
"typecheck": "vue-tsc",
"typecheck:registry": "vue-tsc -p tsconfig.registry.json",
"build:registry": "tsx ./scripts/build-registry.ts",
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts"
"build:registry-strict": "pnpm typecheck:registry && tsx ./scripts/build-registry.ts",
"docs:gen": "tsx ./scripts/autogen.ts"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
@ -44,9 +45,12 @@
"zod": "^3.23.3"
},
"devDependencies": {
"@babel/traverse": "^7.24.1",
"@iconify-json/gravity-ui": "^1.1.2",
"@iconify-json/lucide": "^1.1.180",
"@iconify-json/ph": "^1.1.12",
"@iconify-json/radix-icons": "^1.1.14",
"@iconify-json/ri": "^1.1.18",
"@iconify-json/simple-icons": "^1.1.94",
"@iconify-json/tabler": "^1.1.106",
"@iconify/vue": "^4.1.2",
@ -60,7 +64,9 @@
"@vue/compiler-dom": "^3.4.24",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.19",
"fast-glob": "^3.3.2",
"lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"pathe": "^1.1.2",
"rimraf": "^5.0.5",
"shiki": "^1.3.0",
@ -70,6 +76,7 @@
"typescript": "^5.4.5",
"unplugin-icons": "^0.18.5",
"vitepress": "^1.1.3",
"vue-component-meta": "^2.0.13",
"vue-tsc": "^2.0.14"
}
}

150
apps/www/scripts/autogen.ts Normal file
View File

@ -0,0 +1,150 @@
import { join, parse, resolve } from 'node:path'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import fg from 'fast-glob'
import MarkdownIt from 'markdown-it'
import type { ComponentMeta, MetaCheckerOptions, PropertyMeta, PropertyMetaSchema } from 'vue-component-meta'
import { createComponentMetaChecker } from 'vue-component-meta'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const md = new MarkdownIt()
const ROOTPATH = '../'
const OUTPUTPATH = '../src/content/meta'
const checkerOptions: MetaCheckerOptions = {
forceUseTs: true,
printer: { newLine: 1 },
}
const tsconfigChecker = createComponentMetaChecker(
resolve(__dirname, ROOTPATH, 'tsconfig.registry.json'),
checkerOptions,
)
const components = fg.sync(['chart/**/*.vue', 'chart*/**/*.vue'], {
cwd: resolve(__dirname, ROOTPATH, 'src/lib/registry/default/ui/'),
absolute: true,
})
components.forEach((componentPath) => {
try {
const componentName = parse(componentPath).name
const meta = parseMeta(tsconfigChecker.getComponentMeta(componentPath))
const metaDirPath = resolve(__dirname, OUTPUTPATH)
// if meta dir doesn't exist create
if (!existsSync(metaDirPath))
mkdirSync(metaDirPath)
const metaMdFilePath = join(metaDirPath, `${componentName}.md`)
let parsedString = '<!-- This file was automatic generated. Do not edit it manually -->\n\n'
if (meta.props.length)
parsedString += `<APITable :type="'prop'" :data="${JSON.stringify(meta.props, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.events.length)
parsedString += `\n<APITable :type="'emit'" :data="${JSON.stringify(meta.events, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.slots.length)
parsedString += `\n<APITable :type="'slot'" :data="${JSON.stringify(meta.slots, null, 2).replace(/"/g, '\'')}" />\n`
if (meta.methods.length)
parsedString += `\n<APITable :type="'method'" :data="${JSON.stringify(meta.methods, null, 2).replace(/"/g, '\'')}" />\n`
writeFileSync(metaMdFilePath, parsedString)
}
catch (err) {
console.log(err)
}
})
function parseTypeFromSchema(schema: PropertyMetaSchema): string {
if (typeof schema === 'object' && (schema.kind === 'enum' || schema.kind === 'array')) {
const isFlatEnum = schema.schema?.every(val => typeof val === 'string')
const enumValue = schema?.schema?.filter(i => i !== 'undefined') ?? []
if (isFlatEnum && /^[A-Z]/.test(schema.type))
return enumValue.join(' | ')
else if (typeof schema.schema?.[0] === 'object' && schema.schema?.[0].kind === 'enum')
return schema.schema.map((s: PropertyMetaSchema) => parseTypeFromSchema(s)).join(' | ')
else
return schema.type
}
else if (typeof schema === 'object' && schema.kind === 'object') {
return schema.type
}
else if (typeof schema === 'string') {
return schema
}
else {
return ''
}
}
// Utilities
function parseMeta(meta: ComponentMeta) {
const props = meta.props
// Exclude global props
.filter(prop => !prop.global)
.map((prop) => {
let defaultValue = prop.default
const { name, description, required } = prop
if (name === 'as')
defaultValue = defaultValue ?? '"div"'
if (defaultValue === 'undefined')
defaultValue = undefined
return ({
name,
description: md.render(description),
type: prop.type.replace(/\s*\|\s*undefined/g, ''),
required,
default: defaultValue ?? undefined,
})
})
const events = meta.events
.map((event) => {
const { name, type } = event
return ({
name,
type: type.replace(/\s*\|\s*undefined/g, ''),
})
})
const defaultSlot = meta.slots?.[0]
const slots: { name: string, description: string, type: string }[] = []
if (defaultSlot && defaultSlot.type !== '{}') {
const schema = defaultSlot.schema
if (typeof schema === 'object' && schema.schema) {
Object.values(schema.schema).forEach((childMeta: PropertyMeta) => {
slots.push({
name: childMeta.name,
description: md.render(childMeta.description),
type: parseTypeFromSchema(childMeta.schema),
})
})
}
}
// exposed method
const methods = meta.exposed
.filter(expose => typeof expose.schema === 'object' && expose.schema.kind === 'event')
.map(expose => ({
name: expose.name,
description: md.render(expose.description),
type: expose.type,
}))
return {
props,
events,
slots,
methods,
}
}

View File

@ -0,0 +1,107 @@
---
title: Charts
description: Versatile visualization tool, allowing users to represent data using various types of charts for effective analysis.
label: Alpha
---
<script setup>
import Area from '~icons/gravity-ui/chart-area-stacked'
import Bar from '~icons/gravity-ui/chart-column'
import Line from '~icons/gravity-ui/chart-line'
import Pie from '~icons/gravity-ui/chart-pie'
</script>
<Callout>
Only works with Vue >3.3
</Callout>
`Charts` components were built on top of [Unovis](https://unovis.dev/) (a modular data visualization framework), and inspired by [tremor](https://www.tremor.so).
## Chart type
<div class="grid gap-4 mt-8 sm:grid-cols-2 sm:gap-6 not-docs">
<LinkedCard href="/docs/charts/area">
<Area class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Area</p>
</LinkedCard>
<LinkedCard href="/docs/charts/line">
<Line class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Line</p>
</LinkedCard>
<LinkedCard href="/docs/charts/bar">
<Bar class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Bar</p>
</LinkedCard>
<LinkedCard href="/docs/charts/donut">
<Pie class="text-foreground/80 w-11 h-11" />
<p class="mt-2 font-medium">Donut</p>
</LinkedCard>
</div>
## Installation
<Steps>
### Update `css`
Add the following tooltip styling to your `tailwind.css` file:
```css
@layer base {
:root {
/* ... */
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
--vis-tooltip-shadow-color: none !important;
--vis-tooltip-backdrop-filter: none !important;
--vis-tooltip-padding: none !important;
--vis-primary-color: var(--primary);
/* change to any hsl value you want */
--vis-secondary-color: 160 81% 40%;
--vis-text-color: var(--muted-foreground);
}
```
If you are not using `css-variables` for your component, you need to update the `--vis-primary-color` and `--vis-text-color` to your desired hsl value.
You may use [this tool](https://redpixelthemes.com/blog/tailwindcss-colors-different-formats/) to help you find the hsl value for your primary color and text color. Be sure to provide `dark` mode styling as well.
</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 expected prop definition would be:
```ts
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
```

View File

@ -0,0 +1,46 @@
---
title: Area
description: An area chart visually represents data over time, displaying trends and patterns through filled-in areas under a line graph.
source: apps/www/src/lib/registry/default/ui/chart-area
label: Alpha
---
<ComponentPreview name="AreaChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-area
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/AreaChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="AreaChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="AreaChartCustomTooltip" />

View File

@ -0,0 +1,50 @@
---
title: Bar
description: An line chart visually represents data using rectangular bars of varying lengths to compare quantities across different categories or groups.
source: apps/www/src/lib/registry/default/ui/chart-bar
label: Alpha
---
<ComponentPreview name="BarChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-bar
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/BarChart.md -->
## Example
### Stacked
You can stack the bar chart by settings prop `type` to `stacked`.
<ComponentPreview name="BarChartStacked" />
### Rounded
<ComponentPreview name="BarChartRounded" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="BarChartCustomTooltip" />

View File

@ -0,0 +1,52 @@
---
title: Donut
description: An line chart visually represents data in a circular form, similar to a pie chart but with a central void, emphasizing proportions within categories.
source: apps/www/src/lib/registry/default/ui/chart-donut
label: Alpha
---
<ComponentPreview name="DonutChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-donut
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/DonutChart.md -->
## Example
### Pie Chart
If you want to render pie chart instead, pass `type` as `pie`.
<ComponentPreview name="DonutChartPie" />
### Color
We generate colors automatically based on the primary and secondary color and assigned them accordingly. Feel free to pass in your own array of colors.
<ComponentPreview name="DonutChartColor" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="DonutChartCustomTooltip" />

View File

@ -0,0 +1,46 @@
---
title: Line
description: An line chart visually displays data points connected by straight lines, illustrating trends or relationships over a continuous axis.
source: apps/www/src/lib/registry/default/ui/chart-line
label: Alpha
---
<ComponentPreview name="LineChartDemo" />
## Installation
<Callout>
Only works with Vue >3.3
</Callout>
<Steps>
### Run the following command
```bash
npx shadcn-vue@latest add chart-line
```
### Setup
Follow the [guide](/docs/charts.html#installation) to complete the setup.
</Steps>
## API
<!-- @include: @/content/meta/LineChart.md -->
## Example
### Sparkline
We can turn the chart into sparkline chart by hiding axis, gridline and legends.
<ComponentPreview name="LineChartSparkline" />
### Custom Tooltip
If you want to render custom tooltip, you can easily pass in a custom component. Refer to prop definition [here](/docs/charts.html#custom-tooltip).
<ComponentPreview name="LineChartCustomTooltip" />

View File

@ -45,7 +45,7 @@ import { useToast } from '@/components/ui/toast/use-toast'
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/toast/use-toast'
import { Toaster } from "@/components/ui/toast"
import { Toaster } from '@/components/ui/toast'
const { toast } = useToast()
</script>

View File

@ -0,0 +1,116 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'curveType',
'description': '<p>Type of curve</p>\n',
'type': 'CurveType',
'required': false,
'default': 'CurveType.MonotoneX'
},
{
'name': 'showGradiant',
'description': '<p>Controls the visibility of gradient.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -0,0 +1,116 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'type',
'description': '<p>Change the type of the chart</p>\n',
'type': '\'stacked\' | \'grouped\'',
'required': false,
'default': '\'grouped\''
},
{
'name': 'roundedCorners',
'description': '<p>Rounded bar corners</p>\n',
'type': 'number',
'required': false,
'default': '0'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -0,0 +1,29 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'colors',
'description': '',
'type': 'string[]',
'required': false,
'default': '[]'
},
{
'name': 'index',
'description': '',
'type': 'string',
'required': true
},
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': true
},
{
'name': 'customTooltip',
'description': '',
'type': 'Component',
'required': false
}
]" />

View File

@ -0,0 +1,22 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': false,
'default': '[]'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
},
{
'name': 'update:items',
'type': '[payload: BulletLegendItemInterface[]]'
}
]" />

View File

@ -0,0 +1,43 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'valueFormatter',
'description': '',
'type': '((tick: number, i?: number, ticks?: number[]) => string)',
'required': false,
'default': '`${tick}`'
},
{
'name': 'index',
'description': '',
'type': 'string',
'required': true
},
{
'name': 'items',
'description': '',
'type': 'BulletLegendItemInterface[]',
'required': false
},
{
'name': 'customTooltip',
'description': '',
'type': 'Component',
'required': false
},
{
'name': 'selector',
'description': '',
'type': 'string',
'required': true
}
]" />
<APITable :type="'method'" :data="[
{
'name': 'valueFormatter',
'description': '',
'type': '(tick: number, i?: number | undefined, ticks?: number[] | undefined) => string'
}
]" />

View File

@ -0,0 +1,16 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'title',
'description': '',
'type': 'string',
'required': false
},
{
'name': 'data',
'description': '',
'type': '{ name: string; color: string; value: any; }[]',
'required': true
}
]" />

View File

@ -0,0 +1,82 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'category',
'description': '<p>Sets the name of the key containing the quantitative chart values.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'type',
'description': '<p>Change the type of the chart</p>\n',
'type': '\'donut\' | \'pie\'',
'required': false,
'default': '\'donut\''
},
{
'name': 'sortFunction',
'description': '<p>Function to sort the segment</p>\n',
'type': '((a: any, b: any) => number)',
'required': false
},
{
'name': 'valueFormatter',
'description': '<p>Controls the formatting for the label.</p>\n',
'type': '((tick: number, i?: number, ticks?: number[]) => string)',
'required': false,
'default': '`${tick}`'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
}
]" />

View File

@ -0,0 +1,109 @@
<!-- This file was automatic generated. Do not edit it manually -->
<APITable :type="'prop'" :data="[
{
'name': 'data',
'description': '<p>The source data, in which each entry is a dictionary.</p>\n',
'type': 'Record<string, any>[]',
'required': true
},
{
'name': 'categories',
'description': '<p>Select the categories from your data. Used to populate the legend and toolip.</p>\n',
'type': 'string[]',
'required': true
},
{
'name': 'index',
'description': '<p>Sets the key to map the data to the axis.</p>\n',
'type': 'string',
'required': true
},
{
'name': 'colors',
'description': '<p>Change the default colors.</p>\n',
'type': 'string[]',
'required': false
},
{
'name': 'margin',
'description': '<p>Margin of each the container</p>\n',
'type': 'Spacing',
'required': false,
'default': '{ top: 0, bottom: 0, left: 0, right: 0 }'
},
{
'name': 'filterOpacity',
'description': '<p>Change the opacity of the non-selected field</p>\n',
'type': 'number',
'required': false,
'default': '0.2'
},
{
'name': 'xFormatter',
'description': '<p>Function to format X label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'yFormatter',
'description': '<p>Function to format Y label</p>\n',
'type': '((tick: number | Date, i: number, ticks: number[] | Date[]) => string)',
'required': false
},
{
'name': 'showXAxis',
'description': '<p>Controls the visibility of the X axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showYAxis',
'description': '<p>Controls the visibility of the Y axis.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showTooltip',
'description': '<p>Controls the visibility of tooltip.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showLegend',
'description': '<p>Controls the visibility of legend.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'showGridLine',
'description': '<p>Controls the visibility of gridline.</p>\n',
'type': 'boolean',
'required': false,
'default': 'true'
},
{
'name': 'customTooltip',
'description': '<p>Render custom tooltip component.</p>\n',
'type': 'Component',
'required': false
},
{
'name': 'curveType',
'description': '<p>Type of curve</p>\n',
'type': 'CurveType',
'required': false,
'default': 'CurveType.MonotoneX'
}
]" />
<APITable :type="'emit'" :data="[
{
'name': 'legendItemClick',
'type': '[d: BulletLegendItemInterface, i: number]'
}
]" />

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { VisAxis, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
type Data = typeof data[number]
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 5000) + 1000 },
{ name: 'Feb', total: Math.floor(Math.random() * 5000) + 1000 },
@ -19,28 +18,5 @@ const data = [
</script>
<template>
<VisXYContainer height="350px" :margin="{ left: 20, right: 20 }" :data="data">
<VisStackedBar
:x="(d: Data, i: number) => i"
:y="(d: Data) => d.total"
color="#41b883"
:rounded-corners="4"
:bar-padding="0.15"
/>
<VisAxis
type="x"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false" color="#888888"
/>
<VisAxis
type="y"
:num-ticks="data.length"
:tick-format="(index: number) => data[index]?.name"
:grid-line="false"
:tick-line="false"
:domain-line="false" color="#888888"
/>
</VisXYContainer>
<BarChart :data="data" :categories="['total']" :index="'name'" :rounded-corners="4" />
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart :data="data" index="name" :categories="['total', 'predicted']" />
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { CurveType } from '@unovis/ts'
import { AreaChart } from '@/lib/registry/default/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Aug', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Sep', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Oct', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Nov', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Dec', total: Math.floor(Math.random() * 2000) + 1000 },
]
</script>
<template>
<AreaChart
class="h-[100px] w-[400px]"
index="name"
:data="data"
:categories="['total']"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
:curve-type="CurveType.Linear"
/>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:rounded-corners="4"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/default/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:type="'stacked'"
/>
</template>

View File

@ -52,14 +52,9 @@ const lineY = (d: Data) => d.revenue
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
>
<VisLine :x="lineX" :y="lineY" color="var(--theme-primary)" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="var(--theme-primary)" :stroke-width="2" color="white" />
<VisLine :x="lineX" :y="lineY" color="hsl(var(--primary))" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="hsl(var(--primary))" :stroke-width="2" color="white" />
</VisXYContainer>
</div>
</CardContent>
@ -91,7 +86,7 @@ const lineY = (d: Data) => d.revenue
:x="lineX"
:y="(d: Data) => d.subscription"
:bar-padding="0.1"
:rounded-corners="0" color="var(--theme-primary)"
:rounded-corners="0" color="hsl(var(--primary))"
/>
</VisXYContainer>
</div>

View File

@ -16,10 +16,6 @@ import {
import { themes } from '@/lib/registry/themes'
import { useConfigStore } from '@/stores/config'
const { isDark } = useData()
const cfg = useConfigStore()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const goal = ref(350)
type Data = typeof data[number]
@ -84,16 +80,13 @@ const data = [
:data="data"
height="60px"
:style="{
'opacity': 0.2,
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
opacity: 0.2,
}"
>
<VisStackedBar
:x="(d: Data, i :number) => i"
:y="(d: Data) => d.goal"
color="var(--theme-primary)"
color="hsl(var(--primary))"
:bar-padding="0.1"
:rounded-corners="0"
/>

View File

@ -10,8 +10,6 @@ import {
} from '@/lib/registry/default/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
type Data = typeof data[number]
const data = [
{ average: 400, today: 240 },
@ -74,15 +72,14 @@ function computeLineOpacity(val: any, index: number) {
bottom: 0,
}"
:style="{
'--theme-primary': themePrimary,
'--vis-tooltip-padding': '0px',
'--vis-tooltip-background-color': 'transparent',
'--vis-tooltip-border-color': 'transparent',
}"
>
<VisTooltip />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="var(--theme-primary)" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="var(--theme-primary)" color="white" />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="hsl(var(--primary))" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="hsl(var(--primary))" color="white" />
<VisCrosshair :template="template" />
</VisXYContainer>
</div>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { Card, CardContent } from '@/lib/registry/default/ui/card'
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
</script>
<template>
<Card class="text-sm">
<CardContent class="p-3 min-w-[180px] flex flex-col gap-2">
<div v-for="(item, key) in data" :key="key" class="flex justify-between items-center">
<div class="flex items-center">
<span class="w-1 h-7 mr-4 rounded-full" :style="{ background: item.color }" />
<span>{{ item.name }}</span>
</div>
<span class="font-semibold ml-4">{{ item.value }}</span>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
const valueFormatter = (tick: number | Date) => typeof tick === 'number' ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}` : ''
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:value-formatter="valueFormatter"
:colors="['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple']"
/>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
/>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/default/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:type="'pie'"
/>
</template>

View File

@ -0,0 +1,281 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,279 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,285 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/default/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
index="year"
class="h-[100px] w-[400px]"
:data="data"
:categories="['Export Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:show-tooltip="false"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
/>
</template>

View File

@ -19,6 +19,7 @@ export const buttonVariants = cva(
},
size: {
default: 'h-10 px-4 py-2',
xs: 'h-7 rounded px-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',

View File

@ -0,0 +1,135 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { Area, 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/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.MonotoneX,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
showGradiant: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
</script>
<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" />
<VisXYContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
<svg width="0" height="0">
<defs>
<linearGradient v-for="(color, i) in colors" :id="`color-${i}`" :key="i" x1="0" y1="0" x2="0" y2="1">
<template v-if="showGradiant">
<stop offset="5%" :stop-color="color" stop-opacity="0.4" />
<stop offset="95%" :stop-color="color" stop-opacity="0" />
</template>
<template v-else>
<stop offset="0%" :stop-color="color" />
</template>
</linearGradient>
</defs>
</svg>
<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})`,
},
}"
:opacity="legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1"
/>
</template>
<template v-for="(category, i) in categories" :key="category">
<VisLine
: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,
},
}"
/>
</template>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as AreaChart } from './AreaChart.vue'

View File

@ -0,0 +1,114 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface, Spacing } from '@unovis/ts'
import { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { Axis, GroupedBar, StackedBar } from '@unovis/ts'
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"
*/
type?: 'stacked' | 'grouped'
/**
* Rounded bar corners
* @default 0
*/
roundedCorners?: number
}>(), {
type: 'grouped',
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
filterOpacity: 0.2,
roundedCorners: 0,
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
const VisBarComponent = computed(() => props.type === 'grouped' ? VisGroupedBar : VisStackedBar)
const selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.selectors.bar : StackedBar.selectors.bar)
</script>
<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" />
<VisXYContainer
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
:margin="margin"
>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :custom-tooltip="customTooltip" :index="index" />
<VisBarComponent
:x="(d: Data, i: number) => i"
:y="categories.map(category => (d: Data) => d[category]) "
:color="colors"
:rounded-corners="roundedCorners"
:bar-padding="0.05"
:attributes="{
[selectorsBar]: {
opacity: (d: Data, i:number) => {
const pos = i % categories.length
return legendItems[pos]?.inactive ? filterOpacity : 1
},
},
}"
/>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as BarChart } from './BarChart.vue'

View File

@ -0,0 +1,99 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { VisDonut, VisSingleContainer } from '@unovis/vue'
import { Donut } from '@unovis/ts'
import { type Component, 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'
const props = withDefaults(defineProps<Pick<BaseChartProps<T>, 'data' | 'colors' | 'index' | 'margin' | 'showLegend' | 'showTooltip' | 'filterOpacity'> & {
/**
* Sets the name of the key containing the quantitative chart values.
*/
category: KeyOfT
/**
* Change the type of the chart
* @default "donut"
*/
type?: 'donut' | 'pie'
/**
* Function to sort the segment
*/
sortFunction?: (a: any, b: any) => number | undefined
/**
* Controls the formatting for the label.
*/
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
/**
* Render custom tooltip component.
*/
customTooltip?: Component
}>(), {
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
valueFormatter: (tick: number) => `${tick}`,
type: 'donut',
filterOpacity: 0.2,
showTooltip: true,
showLegend: true,
})
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const category = computed(() => props.category as KeyOfT)
const index = computed(() => props.index as KeyOfT)
const isMounted = useMounted()
const activeSegmentKey = ref<string>()
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.data.filter(d => d[props.category]).filter(Boolean).length))
const legendItems = computed(() => props.data.map((item, i) => ({
name: item[props.index],
color: colors.value[i],
inactive: false,
})))
const totalValue = computed(() => props.data.reduce((prev, curr) => {
return prev + curr[props.category]
}, 0))
</script>
<template>
<div :class="cn('w-full h-48 flex flex-col items-end', $attrs.class ?? '')">
<VisSingleContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
<ChartSingleTooltip
:selector="Donut.selectors.segment"
:index="category"
:items="legendItems"
:value-formatter="valueFormatter"
:custom-tooltip="customTooltip"
/>
<VisDonut
:value="(d: Data) => d[category]"
:sort-function="sortFunction"
:color="colors"
:arc-width="type === 'donut' ? 20 : 0"
:show-background="false"
:central-label="type === 'donut' ? valueFormatter(totalValue) : ''"
:events="{
[Donut.selectors.segment]: {
click: (d: Data, ev: PointerEvent, i: number, elements: HTMLElement[]) => {
if (d?.data?.[index] === activeSegmentKey) {
activeSegmentKey = undefined
elements.forEach(el => el.style.opacity = '1')
}
else {
activeSegmentKey = d?.data?.[index]
elements.forEach(el => el.style.opacity = `${filterOpacity}`)
elements[i].style.opacity = '1'
}
},
},
}"
/>
<slot />
</VisSingleContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as DonutChart } from './DonutChart.vue'

View File

@ -0,0 +1,104 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisAxis, VisLine, VisXYContainer } from '@unovis/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/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
}>(), {
curveType: CurveType.MonotoneX,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
</script>
<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" />
<VisXYContainer
:margin="{ left: 20, right: 20 }"
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
>
<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]: {
opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,
},
}"
/>
</template>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as LineChart } from './LineChart.vue'

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import { VisCrosshair, VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
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: () => [],
})
// Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap()
function template(d: any) {
if (wm.has(d)) {
return wm.get(d)
}
else {
const componentDiv = document.createElement('div')
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {
const legendReference = props.items.find(i => i.name === key)
return { ...legendReference, value }
})
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
function color(d: unknown, i: number) {
return props.colors[i] ?? 'transparent'
}
</script>
<template>
<VisTooltip :horizontal-shift="20" :vertical-shift="20" />
<VisCrosshair :template="template" :color="color" />
</template>

View File

@ -0,0 +1,50 @@
<script setup lang="ts">
import { VisBulletLegend } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { BulletLegend } from '@unovis/ts'
import { nextTick, onMounted, ref } from 'vue'
import { buttonVariants } from '@/lib/registry/default/ui/button'
const props = withDefaults(defineProps<{ items: BulletLegendItemInterface[] }>(), {
items: () => [],
})
const emits = defineEmits<{
'legendItemClick': [d: BulletLegendItemInterface, i: number]
'update:items': [payload: BulletLegendItemInterface[]]
}>()
const elRef = ref<HTMLElement>()
onMounted(() => {
const selector = `.${BulletLegend.selectors.item}`
nextTick(() => {
const elements = elRef.value?.querySelectorAll(selector)
const classes = buttonVariants({ variant: 'ghost', size: 'xs' }).split(' ')
elements?.forEach(el => el.classList.add(...classes, '!inline-flex', '!mr-2'))
})
})
function onLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
const isBulletActive = !props.items[i].inactive
const isFilterApplied = props.items.some(i => i.inactive)
if (isFilterApplied && isBulletActive) {
// reset filter
emits('update:items', props.items.map(item => ({ ...item, inactive: false })))
}
else {
// apply selection, set other item as inactive
emits('update:items', props.items.map(item => item.name === d.name ? ({ ...d, inactive: false }) : { ...item, inactive: true }))
}
}
</script>
<template>
<div ref="elRef" class="w-max">
<VisBulletLegend
:items="items"
:on-legend-item-click="onLegendItemClick"
/>
</div>
</template>

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/default/ui/chart'
const props = withDefaults(defineProps<{
selector: string
index: string
items?: BulletLegendItemInterface[]
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
customTooltip?: Component
}>(), {
valueFormatter: (tick: number) => `${tick}`,
})
// Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap()
function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
if (props.index in d) {
if (wm.has(d)) {
return wm.get(d)
}
else {
const componentDiv = document.createElement('div')
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {
const legendReference = props.items?.find(i => i.name === key)
return { ...legendReference, value: props.valueFormatter(value) }
})
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
if (wm.has(data)) {
return wm.get(data)
}
else {
const style = getComputedStyle(elements[i])
const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]
const componentDiv = document.createElement('div')
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
}
</script>
<template>
<VisTooltip
:horizontal-shift="20" :vertical-shift="20" :triggers="{
[selector]: template,
}"
/>
</template>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/default/ui/card'
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
</script>
<template>
<Card class="text-sm">
<CardHeader v-if="title" class="p-3 border-b">
<CardTitle>
{{ title }}
</CardTitle>
</CardHeader>
<CardContent class="p-3 min-w-[180px] flex flex-col gap-1">
<div v-for="(item, key) in data" :key="key" class="flex justify-between">
<div class="flex items-center">
<span class="w-2.5 h-2.5 mr-2">
<svg width="100%" height="100%" viewBox="0 0 30 30">
<path
d=" M 15 15 m -14, 0 a 14,14 0 1,1 28,0 a 14,14 0 1,1 -28,0"
:stroke="item.color"
:fill="item.color"
stroke-width="1"
/>
</svg>
</span>
<span>{{ item.name }}</span>
</div>
<span class="font-semibold ml-4">{{ item.value }}</span>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,18 @@
export { default as ChartTooltip } from './ChartTooltip.vue'
export { default as ChartSingleTooltip } from './ChartSingleTooltip.vue'
export { default as ChartLegend } from './ChartLegend.vue'
export { default as ChartCrosshair } from './ChartCrosshair.vue'
export function defaultColors(count: number = 3) {
const quotient = Math.floor(count / 2)
const remainder = count % 2
const primaryCount = quotient + remainder
const secondaryCount = quotient
return [
...Array.from(Array(primaryCount).keys()).map(i => `hsl(var(--vis-primary-color) / ${1 - (1 / primaryCount) * i})`),
...Array.from(Array(secondaryCount).keys()).map(i => `hsl(var(--vis-secondary-color) / ${1 - (1 / secondaryCount) * i})`),
]
}
export * from './interface'

View File

@ -0,0 +1,64 @@
import type { Spacing } from '@unovis/ts'
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>
export interface BaseChartProps<T extends Record<string, any>> {
/**
* The source data, in which each entry is a dictionary.
*/
data: T[]
/**
* Select the categories from your data. Used to populate the legend and toolip.
*/
categories: KeyOf<T>[]
/**
* Sets the key to map the data to the axis.
*/
index: KeyOf<T>
/**
* Change the default colors.
*/
colors?: string[]
/**
* Margin of each the container
*/
margin?: Spacing
/**
* Change the opacity of the non-selected field
* @default 0.2
*/
filterOpacity?: number
/**
* Function to format X label
*/
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string
/**
* Function to format Y label
*/
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string
/**
* Controls the visibility of the X axis.
* @default true
*/
showXAxis?: boolean
/**
* Controls the visibility of the Y axis.
* @default true
*/
showYAxis?: boolean
/**
* Controls the visibility of tooltip.
* @default true
*/
showTooltip?: boolean
/**
* Controls the visibility of legend.
* @default true
*/
showLegend?: boolean
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean
}

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { AreaChart } from '@/lib/registry/new-york/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { AreaChart } from '@/lib/registry/new-york/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<AreaChart :data="data" index="name" :categories="['total', 'predicted']" />
</template>

View File

@ -0,0 +1,33 @@
<script setup lang="ts">
import { CurveType } from '@unovis/ts'
import { AreaChart } from '@/lib/registry/new-york/ui/chart-area'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Aug', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Sep', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Oct', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Nov', total: Math.floor(Math.random() * 2000) + 1000 },
{ name: 'Dec', total: Math.floor(Math.random() * 2000) + 1000 },
]
</script>
<template>
<AreaChart
class="h-[100px] w-[400px]"
index="name"
:data="data"
:categories="['total']"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
:curve-type="CurveType.Linear"
/>
</template>

View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
:data="data"
index="name"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:rounded-corners="4"
/>
</template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jul', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<BarChart
index="name"
:data="data"
:categories="['total', 'predicted']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:type="'stacked'"
/>
</template>

View File

@ -1,10 +1,8 @@
<script setup lang="ts">
import { VisLine, VisScatter, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { computed } from 'vue'
import { useData } from 'vitepress'
import { VisScatter, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
import { themes } from '@/lib/registry/themes'
import { LineChart } from '@/lib/registry/new-york/ui/chart-line'
import { BarChart } from '@/lib/registry/new-york/ui/chart-bar'
type Data = typeof data[number]
const data = [
@ -17,14 +15,6 @@ const data = [
{ revenue: 11244, subscription: 278 },
{ revenue: 26475, subscription: 189 },
]
const cfg = useConfigStore()
const { isDark } = useData()
const theme = computed(() => themes.find(theme => theme.name === cfg.config.value.theme))
const lineX = (d: Data, i: number) => i
const lineY = (d: Data) => d.revenue
</script>
<template>
@ -44,23 +34,27 @@ const lineY = (d: Data) => d.revenue
</p>
<div class="h-20">
<VisXYContainer
height="80px"
:data="data" :margin="{
top: 5,
right: 10,
left: 10,
bottom: 0,
}"
:style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
<LineChart
class="h-[80px]"
:data="data"
:margin="{ top: 5, right: 10, left: 10, bottom: 0 }"
:categories="['revenue']"
:index="'revenue'"
:show-grid-line="false"
:show-legend="false"
:show-tooltip="false"
:show-x-axis="false"
:show-y-axis="false"
>
<VisLine :x="lineX" :y="lineY" color="var(--theme-primary)" />
<VisScatter :x="lineX" :y="lineY" :size="6" stroke-color="var(--theme-primary)" :stroke-width="2" color="white" />
</VisXYContainer>
<VisScatter
color="white"
stroke-color="hsl(var(--primary))"
:x="(d: Data, i: number) => i"
:y="(d: Data) => d.revenue"
:size="6"
:stroke-width="2"
/>
</LineChart>
</div>
</CardContent>
</Card>
@ -80,20 +74,27 @@ const lineY = (d: Data) => d.revenue
</p>
<div class="mt-4 h-20">
<VisXYContainer
height="80px" :data="data" :style="{
'--theme-primary': `hsl(${
theme?.cssVars[isDark ? 'dark' : 'light'].primary
})`,
}"
<BarChart
class="h-[80px]"
:data="data"
:categories="['subscription']"
:index="'subscription'"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
:show-tooltip="false"
/>
<!-- <VisXYContainer
height="80px" :data="data"
>
<VisStackedBar
:x="lineX"
:x="(d: Data, i:number) => i"
:y="(d: Data) => d.subscription"
:bar-padding="0.1"
:rounded-corners="0" color="var(--theme-primary)"
:rounded-corners="0" color="hsl(var(--primary))"
/>
</VisXYContainer>
</VisXYContainer> -->
</div>
</CardContent>
</Card>

View File

@ -12,9 +12,6 @@ import {
CardHeader,
CardTitle,
} from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
const goal = ref(350)
@ -80,14 +77,13 @@ const data = [
:data="data"
height="60px"
:style="{
'opacity': 0.2,
'--theme-primary': themePrimary,
opacity: 0.2,
}"
>
<VisStackedBar
:x="(d: Data, i :number) => i"
:y="(d: Data) => d.goal"
color="var(--theme-primary)"
color="hsl(var(--primary))"
:bar-padding="0.1"
:rounded-corners="0"
/>

View File

@ -10,8 +10,6 @@ import {
} from '@/lib/registry/new-york/ui/card'
import { useConfigStore } from '@/stores/config'
const { themePrimary } = useConfigStore()
type Data = typeof data[number]
const data = [
{ average: 400, today: 240 },
@ -74,15 +72,14 @@ function computeLineOpacity(val: any, index: number) {
bottom: 0,
}"
:style="{
'--theme-primary': themePrimary,
'--vis-tooltip-padding': '0px',
'--vis-tooltip-background-color': 'transparent',
'--vis-tooltip-border-color': 'transparent',
}"
>
<VisTooltip />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="var(--theme-primary)" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="var(--theme-primary)" color="white" />
<VisLine :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :stroke-width="2" color="hsl(var(--primary))" :attributes="{ [Line.selectors.linePath]: { opacity: computeLineOpacity } }" />
<VisScatter :x="x" :y="[(d: Data) => d.average, (d: Data) => d.today]" :size="6" :stroke-width="2" stroke-color="hsl(var(--primary))" color="white" />
<VisCrosshair :template="template" />
</VisXYContainer>
</div>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import { Card, CardContent } from '@/lib/registry/default/ui/card'
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
</script>
<template>
<Card class="text-sm">
<CardContent class="p-3 min-w-[180px] flex flex-col gap-2">
<div v-for="(item, key) in data" :key="key" class="flex justify-between items-center">
<div class="flex items-center">
<span class="w-1 h-7 mr-4 rounded-full" :style="{ background: item.color }" />
<span>{{ item.name }}</span>
</div>
<span class="font-semibold ml-4">{{ item.value }}</span>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
const valueFormatter = (tick: number | Date) => typeof tick === 'number' ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}` : ''
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:value-formatter="valueFormatter"
:colors="['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'purple']"
/>
</template>

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
const valueFormatter = (tick: number | Date) => typeof tick === 'number' ? `$ ${new Intl.NumberFormat('us').format(tick).toString()}` : ''
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:value-formatter="valueFormatter"
/>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import { DonutChart } from '@/lib/registry/new-york/ui/chart-donut'
const data = [
{ name: 'Jan', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Feb', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Mar', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Apr', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'May', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
{ name: 'Jun', total: Math.floor(Math.random() * 2000) + 500, predicted: Math.floor(Math.random() * 2000) + 500 },
]
</script>
<template>
<DonutChart
index="name"
:category="'total'"
:data="data"
:type="'pie'"
/>
</template>

View File

@ -0,0 +1,281 @@
<script setup lang="ts">
import CustomChartTooltip from './CustomChartTooltip.vue'
import { LineChart } from '@/lib/registry/new-york/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:custom-tooltip="CustomChartTooltip"
/>
</template>

View File

@ -0,0 +1,279 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/new-york/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
:data="data"
index="year"
:categories="['Export Growth Rate', 'Import Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
/>
</template>

View File

@ -0,0 +1,285 @@
<script setup lang="ts">
import { LineChart } from '@/lib/registry/new-york/ui/chart-line'
const data = [
{
'year': 1970,
'Export Growth Rate': 2.04,
'Import Growth Rate': 1.53,
},
{
'year': 1971,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.58,
},
{
'year': 1972,
'Export Growth Rate': 1.96,
'Import Growth Rate': 1.61,
},
{
'year': 1973,
'Export Growth Rate': 1.93,
'Import Growth Rate': 1.61,
},
{
'year': 1974,
'Export Growth Rate': 1.88,
'Import Growth Rate': 1.67,
},
{
'year': 1975,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.64,
},
{
'year': 1976,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.62,
},
{
'year': 1977,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.69,
},
{
'year': 1978,
'Export Growth Rate': 1.74,
'Import Growth Rate': 1.7,
},
{
'year': 1979,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.67,
},
{
'year': 1980,
'Export Growth Rate': 1.79,
'Import Growth Rate': 1.7,
},
{
'year': 1981,
'Export Growth Rate': 1.81,
'Import Growth Rate': 1.72,
},
{
'year': 1982,
'Export Growth Rate': 1.84,
'Import Growth Rate': 1.73,
},
{
'year': 1983,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.73,
},
{
'year': 1984,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.78,
},
{
'year': 1985,
'Export Growth Rate': 1.78,
'Import Growth Rate': 1.81,
},
{
'year': 1986,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.89,
},
{
'year': 1987,
'Export Growth Rate': 1.82,
'Import Growth Rate': 1.91,
},
{
'year': 1988,
'Export Growth Rate': 1.77,
'Import Growth Rate': 1.94,
},
{
'year': 1989,
'Export Growth Rate': 1.76,
'Import Growth Rate': 1.94,
},
{
'year': 1990,
'Export Growth Rate': 1.75,
'Import Growth Rate': 1.97,
},
{
'year': 1991,
'Export Growth Rate': 1.62,
'Import Growth Rate': 1.99,
},
{
'year': 1992,
'Export Growth Rate': 1.56,
'Import Growth Rate': 2.12,
},
{
'year': 1993,
'Export Growth Rate': 1.5,
'Import Growth Rate': 2.13,
},
{
'year': 1994,
'Export Growth Rate': 1.46,
'Import Growth Rate': 2.15,
},
{
'year': 1995,
'Export Growth Rate': 1.43,
'Import Growth Rate': 2.17,
},
{
'year': 1996,
'Export Growth Rate': 1.4,
'Import Growth Rate': 2.2,
},
{
'year': 1997,
'Export Growth Rate': 1.37,
'Import Growth Rate': 2.15,
},
{
'year': 1998,
'Export Growth Rate': 1.34,
'Import Growth Rate': 2.07,
},
{
'year': 1999,
'Export Growth Rate': 1.32,
'Import Growth Rate': 2.05,
},
{
'year': 2000,
'Export Growth Rate': 1.33,
'Import Growth Rate': 2.07,
},
{
'year': 2001,
'Export Growth Rate': 1.31,
'Import Growth Rate': 2.08,
},
{
'year': 2002,
'Export Growth Rate': 1.29,
'Import Growth Rate': 2.1,
},
{
'year': 2003,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.15,
},
{
'year': 2004,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.21,
},
{
'year': 2005,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.23,
},
{
'year': 2006,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.29,
},
{
'year': 2007,
'Export Growth Rate': 1.27,
'Import Growth Rate': 2.34,
},
{
'year': 2008,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2009,
'Export Growth Rate': 1.26,
'Import Growth Rate': 2.36,
},
{
'year': 2010,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.35,
},
{
'year': 2011,
'Export Growth Rate': 1.24,
'Import Growth Rate': 2.34,
},
{
'year': 2012,
'Export Growth Rate': 1.25,
'Import Growth Rate': 2.39,
},
{
'year': 2013,
'Export Growth Rate': 1.22,
'Import Growth Rate': 2.3,
},
{
'year': 2014,
'Export Growth Rate': 1.2,
'Import Growth Rate': 2.35,
},
{
'year': 2015,
'Export Growth Rate': 1.17,
'Import Growth Rate': 2.39,
},
{
'year': 2016,
'Export Growth Rate': 1.16,
'Import Growth Rate': 2.41,
},
{
'year': 2017,
'Export Growth Rate': 1.13,
'Import Growth Rate': 2.44,
},
{
'year': 2018,
'Export Growth Rate': 1.07,
'Import Growth Rate': 2.45,
},
{
'year': 2019,
'Export Growth Rate': 1.03,
'Import Growth Rate': 2.47,
},
{
'year': 2020,
'Export Growth Rate': 0.92,
'Import Growth Rate': 2.48,
},
{
'year': 2021,
'Export Growth Rate': 0.82,
'Import Growth Rate': 2.51,
},
]
</script>
<template>
<LineChart
index="year"
class="h-[100px] w-[400px]"
:data="data"
:categories="['Export Growth Rate']"
:y-formatter="(tick, i) => {
return typeof tick === 'number'
? `$ ${new Intl.NumberFormat('us').format(tick).toString()}`
: ''
}"
:show-tooltip="false"
:show-grid-line="false"
:show-legend="false"
:show-x-axis="false"
:show-y-axis="false"
/>
</template>

View File

@ -19,6 +19,7 @@ export const buttonVariants = cva(
},
size: {
default: 'h-9 px-4 py-2',
xs: 'h-7 rounded px-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',

View File

@ -0,0 +1,135 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'
import { Area, 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> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
/**
* Controls the visibility of gradient.
* @default true
*/
showGradiant?: boolean
}>(), {
curveType: CurveType.MonotoneX,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
showGradiant: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
</script>
<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" />
<VisXYContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
<svg width="0" height="0">
<defs>
<linearGradient v-for="(color, i) in colors" :id="`color-${i}`" :key="i" x1="0" y1="0" x2="0" y2="1">
<template v-if="showGradiant">
<stop offset="5%" :stop-color="color" stop-opacity="0.4" />
<stop offset="95%" :stop-color="color" stop-opacity="0" />
</template>
<template v-else>
<stop offset="0%" :stop-color="color" />
</template>
</linearGradient>
</defs>
</svg>
<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})`,
},
}"
:opacity="legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1"
/>
</template>
<template v-for="(category, i) in categories" :key="category">
<VisLine
: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,
},
}"
/>
</template>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as AreaChart } from './AreaChart.vue'

View File

@ -0,0 +1,115 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import type { BulletLegendItemInterface, Spacing } from '@unovis/ts'
import { VisAxis, VisGroupedBar, VisStackedBar, VisXYContainer } from '@unovis/vue'
import { Axis, GroupedBar, StackedBar } 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> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Change the type of the chart
* @default "grouped"
*/
type?: 'stacked' | 'grouped'
/**
* Rounded bar corners
* @default 0
*/
roundedCorners?: number
}>(), {
type: 'grouped',
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
filterOpacity: 0.2,
roundedCorners: 0,
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
const VisBarComponent = computed(() => props.type === 'grouped' ? VisGroupedBar : VisStackedBar)
const selectorsBar = computed(() => props.type === 'grouped' ? GroupedBar.selectors.bar : StackedBar.selectors.bar)
</script>
<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" />
<VisXYContainer
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
:margin="margin"
>
<ChartCrosshair v-if="showTooltip" :colors="colors" :items="legendItems" :custom-tooltip="customTooltip" :index="index" />
<VisBarComponent
:x="(d: Data, i: number) => i"
:y="categories.map(category => (d: Data) => d[category]) "
:color="colors"
:rounded-corners="roundedCorners"
:bar-padding="0.05"
:attributes="{
[selectorsBar]: {
opacity: (d: Data, i:number) => {
const pos = i % categories.length
return legendItems[pos]?.inactive ? filterOpacity : 1
},
},
}"
/>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as BarChart } from './BarChart.vue'

View File

@ -0,0 +1,99 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { VisDonut, VisSingleContainer } from '@unovis/vue'
import { Donut } from '@unovis/ts'
import { type Component, 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'
const props = withDefaults(defineProps<Pick<BaseChartProps<T>, 'data' | 'colors' | 'index' | 'margin' | 'showLegend' | 'showTooltip' | 'filterOpacity'> & {
/**
* Sets the name of the key containing the quantitative chart values.
*/
category: KeyOfT
/**
* Change the type of the chart
* @default "donut"
*/
type?: 'donut' | 'pie'
/**
* Function to sort the segment
*/
sortFunction?: (a: any, b: any) => number | undefined
/**
* Controls the formatting for the label.
*/
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
/**
* Render custom tooltip component.
*/
customTooltip?: Component
}>(), {
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
sortFunction: () => undefined,
valueFormatter: (tick: number) => `${tick}`,
type: 'donut',
filterOpacity: 0.2,
showTooltip: true,
showLegend: true,
})
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const category = computed(() => props.category as KeyOfT)
const index = computed(() => props.index as KeyOfT)
const isMounted = useMounted()
const activeSegmentKey = ref<string>()
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.data.filter(d => d[props.category]).filter(Boolean).length))
const legendItems = computed(() => props.data.map((item, i) => ({
name: item[props.index],
color: colors.value[i],
inactive: false,
})))
const totalValue = computed(() => props.data.reduce((prev, curr) => {
return prev + curr[props.category]
}, 0))
</script>
<template>
<div :class="cn('w-full h-48 flex flex-col items-end', $attrs.class ?? '')">
<VisSingleContainer :style="{ height: isMounted ? '100%' : 'auto' }" :margin="{ left: 20, right: 20 }" :data="data">
<ChartSingleTooltip
:selector="Donut.selectors.segment"
:index="category"
:items="legendItems"
:value-formatter="valueFormatter"
:custom-tooltip="customTooltip"
/>
<VisDonut
:value="(d: Data) => d[category]"
:sort-function="sortFunction"
:color="colors"
:arc-width="type === 'donut' ? 20 : 0"
:show-background="false"
:central-label="type === 'donut' ? valueFormatter(totalValue) : ''"
:events="{
[Donut.selectors.segment]: {
click: (d: Data, ev: PointerEvent, i: number, elements: HTMLElement[]) => {
if (d?.data?.[index] === activeSegmentKey) {
activeSegmentKey = undefined
elements.forEach(el => el.style.opacity = '1')
}
else {
activeSegmentKey = d?.data?.[index]
elements.forEach(el => el.style.opacity = `${filterOpacity}`)
elements[i].style.opacity = '1'
}
},
},
}"
/>
<slot />
</VisSingleContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as DonutChart } from './DonutChart.vue'

View File

@ -0,0 +1,104 @@
<script setup lang="ts" generic="T extends Record<string, any>">
import { type BulletLegendItemInterface, CurveType } from '@unovis/ts'
import { VisAxis, VisLine, VisXYContainer } from '@unovis/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> & {
/**
* Render custom tooltip component.
*/
customTooltip?: Component
/**
* Type of curve
*/
curveType?: CurveType
}>(), {
curveType: CurveType.MonotoneX,
filterOpacity: 0.2,
margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
showXAxis: true,
showYAxis: true,
showTooltip: true,
showLegend: true,
showGridLine: true,
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
}>()
type KeyOfT = Extract<keyof T, string>
type Data = typeof props.data[number]
const index = computed(() => props.index as KeyOfT)
const colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))
const legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({
name: category,
color: colors.value[i],
inactive: false,
})))
const isMounted = useMounted()
function handleLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
}
</script>
<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" />
<VisXYContainer
:margin="{ left: 20, right: 20 }"
:data="data"
:style="{ height: isMounted ? '100%' : 'auto' }"
>
<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]: {
opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,
},
}"
/>
</template>
<VisAxis
v-if="showXAxis"
type="x"
:tick-format="xFormatter ?? ((v: number) => data[v]?.[index])"
:grid-line="false"
:tick-line="false"
tick-text-color="hsl(var(--vis-text-color))"
/>
<VisAxis
v-if="showYAxis"
type="y"
:tick-line="false"
:tick-format="yFormatter"
:domain-line="false"
:grid-line="showGridLine"
:attributes="{
[Axis.selectors.grid]: {
class: 'text-muted',
},
}"
tick-text-color="hsl(var(--vis-text-color))"
/>
<slot />
</VisXYContainer>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as LineChart } from './LineChart.vue'

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import { VisCrosshair, VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
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: () => [],
})
// Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap()
function template(d: any) {
if (wm.has(d)) {
return wm.get(d)
}
else {
const componentDiv = document.createElement('div')
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {
const legendReference = props.items.find(i => i.name === key)
return { ...legendReference, value }
})
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
function color(d: unknown, i: number) {
return props.colors[i] ?? 'transparent'
}
</script>
<template>
<VisTooltip :horizontal-shift="20" :vertical-shift="20" />
<VisCrosshair :template="template" :color="color" />
</template>

View File

@ -0,0 +1,51 @@
<script setup lang="ts">
import { VisBulletLegend } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { BulletLegend } from '@unovis/ts'
import { nextTick, onMounted, ref } from 'vue'
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
const props = withDefaults(defineProps<{ items: BulletLegendItemInterface[] }>(), {
items: () => [],
})
const emits = defineEmits<{
legendItemClick: [d: BulletLegendItemInterface, i: number]
'update:items': [payload: BulletLegendItemInterface[]]
}>()
const elRef = ref<HTMLElement>()
onMounted(() => {
const selector = `.${BulletLegend.selectors.item}`
nextTick(() => {
const elements = elRef.value?.querySelectorAll(selector)
const classes = buttonVariants({ variant: 'ghost', size: 'xs' }).split(' ')
elements?.forEach(el => el.classList.add(...classes, '!inline-flex', '!mr-2'))
})
})
function onLegendItemClick(d: BulletLegendItemInterface, i: number) {
emits('legendItemClick', d, i)
const isBulletActive = !props.items[i].inactive
const isFilterApplied = props.items.some(i => i.inactive)
if (isFilterApplied && isBulletActive) {
// reset filter
emits('update:items', props.items.map(item => ({ ...item, inactive: false })))
}
else {
// apply selection, set other item as inactive
emits('update:items', props.items.map(item => item.name === d.name ? ({ ...d, inactive: false }) : { ...item, inactive: true }))
}
}
</script>
<template>
<div ref="elRef" class="w-max">
<VisBulletLegend
:items="items"
:on-legend-item-click="onLegendItemClick"
/>
</div>
</template>

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { VisTooltip } from '@unovis/vue'
import type { BulletLegendItemInterface } from '@unovis/ts'
import { omit } from '@unovis/ts'
import { type Component, createApp } from 'vue'
import { ChartTooltip } from '@/lib/registry/new-york/ui/chart'
const props = withDefaults(defineProps<{
selector: string
index: string
items?: BulletLegendItemInterface[]
valueFormatter?: (tick: number, i?: number, ticks?: number[]) => string
customTooltip?: Component
}>(), {
valueFormatter: (tick: number) => `${tick}`,
})
// Use weakmap to store reference to each datapoint for Tooltip
const wm = new WeakMap()
function template(d: any, i: number, elements: (HTMLElement | SVGElement)[]) {
if (props.index in d) {
if (wm.has(d)) {
return wm.get(d)
}
else {
const componentDiv = document.createElement('div')
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {
const legendReference = props.items?.find(i => i.name === key)
return { ...legendReference, value: props.valueFormatter(value) }
})
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
if (wm.has(data)) {
return wm.get(data)
}
else {
const style = getComputedStyle(elements[i])
const omittedData = [{ name: data.name, value: props.valueFormatter(data[props.index]), color: style.fill }]
const componentDiv = document.createElement('div')
const TooltipComponent = props.customTooltip ?? ChartTooltip
createApp(TooltipComponent, { title: d[props.index], data: omittedData }).mount(componentDiv)
wm.set(d, componentDiv.innerHTML)
return componentDiv.innerHTML
}
}
}
</script>
<template>
<VisTooltip
:horizontal-shift="20" :vertical-shift="20" :triggers="{
[selector]: template,
}"
/>
</template>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '@/lib/registry/new-york/ui/card'
defineProps<{
title?: string
data: {
name: string
color: string
value: any
}[]
}>()
</script>
<template>
<Card class="text-sm">
<CardHeader v-if="title" class="p-3 border-b">
<CardTitle>
{{ title }}
</CardTitle>
</CardHeader>
<CardContent class="p-3 min-w-[180px] flex flex-col gap-1">
<div v-for="(item, key) in data" :key="key" class="flex justify-between">
<div class="flex items-center">
<span class="w-2.5 h-2.5 mr-2">
<svg width="100%" height="100%" viewBox="0 0 30 30">
<path
d=" M 15 15 m -14, 0 a 14,14 0 1,1 28,0 a 14,14 0 1,1 -28,0"
:stroke="item.color"
:fill="item.color"
stroke-width="1"
/>
</svg>
</span>
<span>{{ item.name }}</span>
</div>
<span class="font-semibold ml-4">{{ item.value }}</span>
</div>
</CardContent>
</Card>
</template>

View File

@ -0,0 +1,18 @@
export { default as ChartTooltip } from './ChartTooltip.vue'
export { default as ChartSingleTooltip } from './ChartSingleTooltip.vue'
export { default as ChartLegend } from './ChartLegend.vue'
export { default as ChartCrosshair } from './ChartCrosshair.vue'
export function defaultColors(count: number = 3) {
const quotient = Math.floor(count / 2)
const remainder = count % 2
const primaryCount = quotient + remainder
const secondaryCount = quotient
return [
...Array.from(Array(primaryCount).keys()).map(i => `hsl(var(--vis-primary-color) / ${1 - (1 / primaryCount) * i})`),
...Array.from(Array(secondaryCount).keys()).map(i => `hsl(var(--vis-secondary-color) / ${1 - (1 / secondaryCount) * i})`),
]
}
export * from './interface'

View File

@ -0,0 +1,64 @@
import type { Spacing } from '@unovis/ts'
type KeyOf<T extends Record<string, any>> = Extract<keyof T, string>
export interface BaseChartProps<T extends Record<string, any>> {
/**
* The source data, in which each entry is a dictionary.
*/
data: T[]
/**
* Select the categories from your data. Used to populate the legend and toolip.
*/
categories: KeyOf<T>[]
/**
* Sets the key to map the data to the axis.
*/
index: KeyOf<T>
/**
* Change the default colors.
*/
colors?: string[]
/**
* Margin of each the container
*/
margin?: Spacing
/**
* Change the opacity of the non-selected field
* @default 0.2
*/
filterOpacity?: number
/**
* Function to format X label
*/
xFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string
/**
* Function to format Y label
*/
yFormatter?: (tick: number | Date, i: number, ticks: number[] | Date[]) => string
/**
* Controls the visibility of the X axis.
* @default true
*/
showXAxis?: boolean
/**
* Controls the visibility of the Y axis.
* @default true
*/
showYAxis?: boolean
/**
* Controls the visibility of tooltip.
* @default true
*/
showTooltip?: boolean
/**
* Controls the visibility of legend.
* @default true
*/
showLegend?: boolean
/**
* Controls the visibility of gridline.
* @default true
*/
showGridLine?: boolean
}

View File

@ -11,6 +11,7 @@ const DEPENDENCIES = new Map<string, string[]>([
['vaul-vue', []],
['v-calendar', []],
['@tanstack/vue-table', []],
['@unovis/vue', ['@unovis/ts']],
['embla-carousel-vue', ['embla-carousel']],
['vee-validate', ['@vee-validate/zod', 'zod']],
])

View File

@ -222,6 +222,95 @@
],
"type": "components:ui"
},
{
"name": "chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [
"chart",
"button",
"card"
],
"files": [
"ui/chart/ChartCrosshair.vue",
"ui/chart/ChartLegend.vue",
"ui/chart/ChartSingleTooltip.vue",
"ui/chart/ChartTooltip.vue",
"ui/chart/index.ts",
"ui/chart/interface.ts"
],
"type": "components:ui"
},
{
"name": "chart-area",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-area/AreaChart.vue",
"ui/chart-area/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-bar",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-bar/BarChart.vue",
"ui/chart-bar/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-donut",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-donut/DonutChart.vue",
"ui/chart-donut/index.ts"
],
"type": "components:ui"
},
{
"name": "chart-line",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
"ui/chart-line/LineChart.vue",
"ui/chart-line/index.ts"
],
"type": "components:ui"
},
{
"name": "checkbox",
"dependencies": [],

View File

@ -0,0 +1,19 @@
{
"name": "area-chart",
"dependencies": [
"@unovis/vue",
"@unovis/ts"
],
"registryDependencies": [],
"files": [
{
"name": "AreaChart.vue",
"content": "<script setup lang=\"ts\">\nimport { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Area } from '@unovis/ts'\n\ntype Data = typeof data[number]\nconst data = [\n { name: 'Jan', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Feb', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Mar', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Apr', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'May', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Jun', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Jul', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Aug', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Sep', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Oct', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Nov', total: Math.floor(Math.random() * 5000) + 1000 },\n { name: 'Dec', total: Math.floor(Math.random() * 5000) + 1000 },\n]\n</script>\n\n<template>\n <VisXYContainer height=\"350px\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <svg width=\"0\" height=\"0\">\n <defs>\n <linearGradient id=\"colorUv\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"5%\" stop-color=\"hsl(var(--primary))\" stop-opacity=\"0.6\" />\n <stop offset=\"95%\" stop-color=\"hsl(var(--primary))\" stop-opacity=\"0\" />\n </linearGradient>\n </defs>\n </svg>\n\n <VisArea\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d.total\"\n color=\"auto\"\n :attributes=\"{\n [Area.selectors.area]: {\n fill: 'url(#colorUv)',\n },\n }\"\n :rounded-corners=\"4\"\n :bar-padding=\"0.15\"\n />\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d.total\"\n color=\"hsl(var(--primary))\"\n />\n <VisAxis\n type=\"x\"\n :num-ticks=\"data.length\"\n :tick-format=\"(index: number) => data[index]?.name\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n <VisAxis\n type=\"y\"\n :num-ticks=\"data.length\"\n :tick-format=\"(index: number) => data[index]?.name\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n :domain-line=\"false\"\n tick-text-color=\"hsl(var(--muted-foreground))\"\n />\n </VisXYContainer>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as AreaChart } from './AreaChart.vue'\n"
}
],
"type": "components:ui"
}

View File

@ -11,7 +11,7 @@
},
{
"name": "index.ts",
"content": "import { type VariantProps, cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>\n"
"content": "import { type VariantProps, cva } from 'class-variance-authority'\n\nexport { default as Button } from './Button.vue'\n\nexport const buttonVariants = cva(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n destructive:\n 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n outline:\n 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n secondary:\n 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n link: 'text-primary underline-offset-4 hover:underline',\n },\n size: {\n default: 'h-10 px-4 py-2',\n xs: 'h-7 rounded px-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n)\n\nexport type ButtonVariants = VariantProps<typeof buttonVariants>\n"
}
],
"type": "components:ui"

View File

@ -0,0 +1,23 @@
{
"name": "chart-area",
"dependencies": [
"@unovis/vue",
"@unovis/ts",
"@vueuse/core"
],
"registryDependencies": [
"chart",
"utils"
],
"files": [
{
"name": "AreaChart.vue",
"content": "<script setup lang=\"ts\" generic=\"T extends Record<string, any>\">\nimport { type BulletLegendItemInterface, CurveType } from '@unovis/ts'\nimport { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/vue'\nimport { Area, Axis, Line } from '@unovis/ts'\nimport { type Component, computed, ref } from 'vue'\nimport { useMounted } from '@vueuse/core'\nimport { type BaseChartProps, ChartCrosshair, ChartLegend, defaultColors } from '@/lib/registry/default/ui/chart'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(defineProps<BaseChartProps<T> & {\n /**\n * Render custom tooltip component.\n */\n customTooltip?: Component\n /**\n * Type of curve\n */\n curveType?: CurveType\n /**\n * Controls the visibility of gradient.\n * @default true\n */\n showGradiant?: boolean\n}>(), {\n curveType: CurveType.MonotoneX,\n filterOpacity: 0.2,\n margin: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),\n showXAxis: true,\n showYAxis: true,\n showTooltip: true,\n showLegend: true,\n showGridLine: true,\n showGradiant: true,\n})\n\nconst emits = defineEmits<{\n legendItemClick: [d: BulletLegendItemInterface, i: number]\n}>()\n\ntype KeyOfT = Extract<keyof T, string>\ntype Data = typeof props.data[number]\n\nconst index = computed(() => props.index as KeyOfT)\nconst colors = computed(() => props.colors?.length ? props.colors : defaultColors(props.categories.length))\n\nconst legendItems = ref<BulletLegendItemInterface[]>(props.categories.map((category, i) => ({\n name: category,\n color: colors.value[i],\n inactive: false,\n})))\n\nconst isMounted = useMounted()\n\nfunction handleLegendItemClick(d: BulletLegendItemInterface, i: number) {\n emits('legendItemClick', d, i)\n}\n</script>\n\n<template>\n <div :class=\"cn('w-full h-[400px] flex flex-col items-end', $attrs.class ?? '')\">\n <ChartLegend v-if=\"showLegend\" v-model:items=\"legendItems\" @legend-item-click=\"handleLegendItemClick\" />\n\n <VisXYContainer :style=\"{ height: isMounted ? '100%' : 'auto' }\" :margin=\"{ left: 20, right: 20 }\" :data=\"data\">\n <svg width=\"0\" height=\"0\">\n <defs>\n <linearGradient v-for=\"(color, i) in colors\" :id=\"`color-${i}`\" :key=\"i\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <template v-if=\"showGradiant\">\n <stop offset=\"5%\" :stop-color=\"color\" stop-opacity=\"0.4\" />\n <stop offset=\"95%\" :stop-color=\"color\" stop-opacity=\"0\" />\n </template>\n <template v-else>\n <stop offset=\"0%\" :stop-color=\"color\" />\n </template>\n </linearGradient>\n </defs>\n </svg>\n\n <ChartCrosshair v-if=\"showTooltip\" :colors=\"colors\" :items=\"legendItems\" :index=\"index\" :custom-tooltip=\"customTooltip\" />\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisArea\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n color=\"auto\"\n :curve-type=\"curveType\"\n :attributes=\"{\n [Area.selectors.area]: {\n fill: `url(#color-${i})`,\n },\n }\"\n :opacity=\"legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1\"\n />\n </template>\n\n <template v-for=\"(category, i) in categories\" :key=\"category\">\n <VisLine\n :x=\"(d: Data, i: number) => i\"\n :y=\"(d: Data) => d[category]\"\n :color=\"colors[i]\"\n :curve-type=\"curveType\"\n :attributes=\"{\n [Line.selectors.line]: {\n opacity: legendItems.find(item => item.name === category)?.inactive ? filterOpacity : 1,\n },\n }\"\n />\n </template>\n\n <VisAxis\n v-if=\"showXAxis\"\n type=\"x\"\n :tick-format=\"xFormatter ?? ((v: number) => data[v]?.[index])\"\n :grid-line=\"false\"\n :tick-line=\"false\"\n tick-text-color=\"hsl(var(--vis-text-color))\"\n />\n <VisAxis\n v-if=\"showYAxis\"\n type=\"y\"\n :tick-line=\"false\"\n :tick-format=\"yFormatter\"\n :domain-line=\"false\"\n :grid-line=\"showGridLine\"\n :attributes=\"{\n [Axis.selectors.grid]: {\n class: 'text-muted',\n },\n }\"\n tick-text-color=\"hsl(var(--vis-text-color))\"\n />\n\n <slot />\n </VisXYContainer>\n </div>\n</template>\n"
},
{
"name": "index.ts",
"content": "export { default as AreaChart } from './AreaChart.vue'\n"
}
],
"type": "components:ui"
}

Some files were not shown because too many files have changed in this diff Show More