Patterns

Patterns are a way to add visual interest to your charts.

Installation

Copy-paste the component code in a file named Patterns.tsx in your tailus-ui/visualizations folder.

export const Stripes = ({ intent = 'primary', id }: { intent?: string; id: string }) => {
    return (
        <pattern id={id} patternUnits="userSpaceOnUse" width="4" height="4">
            <rect width="4" height="4" fill={`var(--dv-${intent})`} opacity={0.3} />
            <path d="M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2" stroke={`var(--dv-${intent})`} opacity={0.6} strokeWidth="1" />
        </pattern>
    );
};

export const Dots = ({ intent = 'primary', id }: { intent?: string; id: string }) => {
    return (
        <pattern id={id} patternUnits="userSpaceOnUse" width="8" height="8">
            <circle cx="2" cy="2" r="2" fill={`var(--dv-${intent})`} />
        </pattern>
    );
};

export const LinearGradient = ({ intent = 'primary', id }: { intent?: string; id: string }) => {
    return (
        <linearGradient x1={0} y1={0} x2={0} y2={1} id={id}>
            <stop offset="5%" stopColor={`var(--dv-${intent})`} stopOpacity={0.3} />
            <stop className="text-white dark:text-gray-950" offset="95%" stopColor="currentColor" stopOpacity={0} />
        </linearGradient>
    );
};

export const DotCartesianGrid = ({ id, opacity = 0.5 }: { id: string; opacity: number }) => {
    return (
        <pattern id={id} patternUnits="userSpaceOnUse" width="8" height="8">
            <circle cx="2" cy="2" r="1" opacity={opacity} fill="var(--ui-border-color)" />
        </pattern>
    );
};

Usage

Import the Pattern of your choice from your local files and use it inside the <defs> tag in your Chart component.

import { LinearGradient, Dot, Stripes, DotCartesianGrid } from '@tailus-ui/visualizations';
<defs>
    <LinearGradient intent="accent" id="Accent" />
    <LinearGradient intent="primary" id="Primary" />

    <Dot intent="primary-600" id="dP600"/>

    <Stripes intent="accent-500" id="sA500"/>

    <DotCartesianGrid id="dCart" opacity={0.6}/>
</defs>

Assign a unique identifier to the Pattern component using the id prop, for example id="Gray". Then, use this id as the value for the fill prop of the Chart componen, like so: fill="url(#Gray)". This links the pattern with the id="Gray" to the Chart component it should be applied to.

<Area stroke="var(--dv-accent)" fill="url(#Accent)" dataKey="Accent" />
<Bar fill="url(#sA500)" dataKey="Accent" />
<CartesianGrid fill="url(#grid)" vertical={false} horizontal={false} />

Reference

Dots, Stripes, and Linear Gradients

These patterns has the following props

Prop
Type
Default
intent
string
primary
id*
string
opacity
number

The intent prop accepts a string, so primary and primary-600 are valid values

DotCartesianGrid

The DotCartesianGrid component has the following props

Prop
Type
Default
opacity
number
0.5
id*
string

Stripes

Visitors and Revenue

New users by First user primary channel group (Default Channel Group)

import { ResponsiveContainer, BarChart, Bar, Tooltip, YAxis, XAxis, CartesianGrid } from 'recharts';
import { CustomTooltip, Stripes, data } from "@tailus-ui/visualizations";
import { Text, Title } from '@tailus-ui/typography';


export const MyChart = () => {
    return (
        <div className="w-full">
            <Title as="h2" size="lg" weight="medium">
                Visitors and Revenue
            </Title>
            <Text className="mb-8 mt-2" size="sm">
                New users by First user primary channel group (Default Channel Group)
            </Text>

            <div className="h-56 w-full sm:h-72 sm:max-w-xl">
                <ResponsiveContainer width="100%" height="100%">
                    <BarChart data={data}>
                        <YAxis className="text-caption" width={40} fontSize={12} unit="$" width={42} tickLine={false} axisLine={false} />
                        <XAxis className="text-caption" dataKey="name" fontSize={12} tickLine={false} axisLine={false} />

                        <CartesianGrid vertical={false} stroke="var(--ui-border-color)" strokeDasharray={3} />
                        <Tooltip cursor={{ fill: 'var(--ui-border-color)' }} content={<CustomTooltip payload={[]} active mixed label={''} />} />

                        <Bar fill="var(--dv-primary)" stackId="a" barSize={20} dataKey="Primary" />
                        <Bar fill="var(--dv-accent)" stackId="a" dataKey="Accent" barSize={20} />
                        <Bar radius={[4, 4, 0, 0]} fill="url(#strbar)" stackId="a" dataKey="Neutral" barSize={20} />

                        <defs>
                            <Stripes id="strbar" intent="primary-500" />
                        </defs>
                    </BarChart>
                </ResponsiveContainer>
            </div>
        </div>
    )
}

Linear Gradient

import { ResponsiveContainer, BarChart, Bar, Tooltip, YAxis, XAxis } from 'recharts';
import { CustomTooltip, LinearGradient, barData } from "@tailus-ui/visualizations";
import { Text, Title } from '@tailus-ui/typography';


export const MyChart = () => {
    return (
        <div className="h-56 w-full pt-4 sm:h-72 sm:pt-0" data-shade="900">
            <ResponsiveContainer width="100%" height="100%">
                <AreaChart data={barData}>
                    <defs>
                        <LinearGradient intent="accent" id="Accent" />
                        <LinearGradient intent="primary" id="Primary" />
                    </defs>

                    <Area type="natural" stroke="var(--dv-accent)" fill="url(#Accent)" dataKey="Neutral" activeDot={{ color: 'var(--dv-accent)', r: 4, stroke: 'none' }} />
                    <Area type="natural" stroke="var(--dv-primary)" fill="url(#Primary)" dataKey="Primary" activeDot={{ color: 'var(--dv-primary)', r: 4, stroke: 'none' }} />
                </AreaChart>
            </ResponsiveContainer>
        </div>
    )
}

Dot Cartesian Grid

Overview

Visualize your main activities data

185,267,931.00 CDF36%
import { useEffect, useState } from 'react';
import { ResponsiveContainer, LineChart, Tooltip, YAxis, XAxis, Line, CartesianGrid } from 'recharts';
import { CustomTooltip } from "@tailus-ui/visualizations";
import { Text, Title } from '@tailus-ui/typography';
import Badge from '@tailus-ui/Badge';
import { TrendingDown, TrendingUp } from 'lucide-react';

export const CryptoLineChart = () => {
    const [bitcoinData, setBitcoinData] = useState([]);

    useEffect(() => {
        const endTimestamp = Date.now();
        const startTimestamp = endTimestamp - 6 * 30 * 24 * 60 * 60 * 1000;

        fetch(`https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range?vs_currency=usd&from=${startTimestamp / 1000}&to=${endTimestamp / 1000}`)
            .then((response) => response.json())
            .then((data) => {
                const prices = data.prices.map((price) => ({
                    date: new Date(price[0]).toISOString().slice(0, 10),
                    price: price[1]
                }));
                setBitcoinData(prices);
            })
            .catch((error) => console.error(error));
    }, []);

    return (
        <div className="w-full">
            <Title as="h2" size="lg" weight="medium" className="mb-1">
                Overview
            </Title>
            <Text className="mb-0 mt-1" size="sm">
                Visualize your main activities data
            </Text>
            <Title className="mt-2 flex items-center gap-3" as="span" weight="medium" size="xl">
                185,267,931.00 <span className="text-caption -ml-2 mb-1 mt-auto h-fit text-xs">CDF</span>
                <Badge intent="success" size="xs" className="flex h-fit items-center gap-1.5">
                    <TrendingUp className="size-3.5" />
                    36%
                </Badge>
            </Title>
            <div className="mt-8 h-56 w-full sm:h-72" data-shade="900">
                <ResponsiveContainer width="100%" height="100%">
                    <LineChart data={bitcoinData}>
                        <YAxis className="text-caption" width={42} fontSize={11} tickLine={false} axisLine={false} />
                        <XAxis className="text-caption" dataKey="date" fontSize={12} tickLine={false} axisLine={false} />
                        <Tooltip active coordinate={{ x: 100 }} cursor={{ stroke: 'var(--ui-border-color)', strokeWidth: 1 }} position={{ y: 0 }} offset={20} content={<Custom payload={[]} active fancy label={''} />} />

                        <CartesianGrid vertical={false} horizontal={false} fill="url(#grid)" stroke="var(--ui-border-color)" />

                        <Line
                            type="natural"
                            stroke="var(--dv-primary)"
                            strokeWidth={1.5}
                            dataKey="price"
                            dot={false}
                            unit="CDF"
                            activeDot={{
                                color: 'var(--dv-primary)',
                                r: 3,
                                stroke: 'currentColor'
                            }}
                        />

                        <defs>
                            <pattern id="grid" patternUnits="userSpaceOnUse" width="8" height="8">
                                <circle cx="2" cy="2" r="1" opacity={0.4} fill="var(--ui-border-color)" />
                            </pattern>
                        </defs>
                    </LineChart>
                </ResponsiveContainer>
            </div>
            <div></div>
        </div>
    );
};

Dots

import { ResponsiveContainer, Legend, PieChart, Pie, Cell } from 'recharts';
import { Dots, legendText } from '@tailus-ui/visualizations';

const data = [
    {
        name: 'Running',
        uv: 31.47,
        pv: 2400,
        fill: 'url(#dotP)'
    },
    {
        name: 'Swimming',
        uv: 26.69,
        pv: 4567,
        fill: 'url(#dotA)'
    },
    {
        name: 'Cycling',
        uv: 15.69,
        pv: 1398,
        fill: 'url(#dotS)'
    },
    {
        name: 'Weightlifting',
        uv: 8.22,
        pv: 9800,
        fill: 'url(#dotS)'
    },
    {
        name: 'Yoga',
        uv: 8.63,
        pv: 3908,
        fill: 'url(#dotI)'
    },
    {
        name: 'Pilates',
        uv: 2.63,
        pv: 4800,
        fill: 'url(#dotW)'
    },
    {
        name: 'Boxing',
        uv: 6.67,
        pv: 4800,
        fill: 'url(#dotD)'
    }
];

export const CategoryBar = () => {
    return (
        <div className="mx-auto w-96">
            <ResponsiveContainer aspect={5 / 4}>
                <PieChart>
                    <Pie label={{ fontSize: 12, fill: 'var(--title-text-color)' }} startAngle={180} endAngle={0} data={data} cx={180} cy={200} innerRadius={70} outerRadius={140} fill="var(--area-primary-stroke)" dataKey="uv">
                        {data.map((entry, index) => (
                            <Cell key={`cell-${index}`} fill={entry.fill} stroke="none" />
                        ))}
                    </Pie>

                    <Legend formatter={legendText} iconType="circle" iconSize={9} />

                    <defs>
                        <Dots id="dotP" />
                        <Dots intent="accent" id="dotA" />
                        <Dots intent="secondary" id="dotS" />
                        <Dots intent="warning" id="dotW" />
                        <Dots intent="danger" id="dotD" />
                        <Dots intent="info" id="dotI" />
                    </defs>
                </PieChart>
            </ResponsiveContainer>
        </div>
    );
};