home / skills / dennisadriaans / vue-chrts / dual-chart

dual-chart skill

/.claude/skills/dual-chart

This skill helps you visualize two related metrics simultaneously by combining bar and line charts for clear insights.

npx playbooks add skill dennisadriaans/vue-chrts --skill dual-chart

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.4 KB
---
name: dual-chart
description: Build DualChart components combining bars and lines. Use for showing relationships between two different metrics on the same visualization.
---

# DualChart

DualChart combines BarChart and LineChart in a single visualization, perfect for comparing two related but different metrics (e.g., revenue bars with profit margin line).

## Mental Model

```
┌─────────────────────────────────────────────────────────────┐
│  DUAL CHART = BARS + LINES                                  │
│                                                             │
│  profit (line)                                              │
│     ─●─────●─────●─────●                                    │
│       ╲   ╱ ╲         ╱                                     │
│        ●     ●───────●                                      │
│     ┌──┐  ┌──┐  ┌──┐  ┌──┐  ← revenue, costs (bars)        │
│     │  │  │  │  │  │  │  │                                  │
│     └──┘  └──┘  └──┘  └──┘                                  │
│      Q1    Q2    Q3    Q4                                   │
│                                                             │
│  Two separate category configs:                             │
│  • barCategories → which data keys become bars              │
│  • lineCategories → which data keys become lines            │
└─────────────────────────────────────────────────────────────┘
```

## Complete Example

```vue
<script setup lang="ts">
import { DualChart, CurveType, LegendPosition } from 'vue-chrts'

interface MonthlyData {
  month: string
  revenue: number
  costs: number
  profit: number
}

const data: MonthlyData[] = [
  { month: 'January', revenue: 45000, costs: 30000, profit: 15000 },
  { month: 'February', revenue: 52000, costs: 35000, profit: 17000 },
  { month: 'March', revenue: 48000, costs: 32000, profit: 16000 },
  { month: 'April', revenue: 61000, costs: 38000, profit: 23000 },
  { month: 'May', revenue: 55000, costs: 33000, profit: 22000 },
  { month: 'June', revenue: 67000, costs: 40000, profit: 27000 },
]

// Bars configuration
const barCategories = {
  revenue: { name: 'Revenue', color: '#3b82f6' },
  costs: { name: 'Costs', color: '#ef4444' }
}
const barYAxis: (keyof MonthlyData)[] = ['revenue', 'costs']

// Lines configuration
const lineCategories = {
  profit: { name: 'Profit Trend', color: '#10b981' }
}
const lineYAxis: (keyof MonthlyData)[] = ['profit']

const xFormatter = (tick: number) => data[tick].month.substring(0, 3)
const yFormatter = (tick: number) => `$${(tick / 1000).toFixed(0)}k`
</script>

<template>
  <DualChart
    :data="data"
    :barCategories="barCategories"
    :barYAxis="barYAxis"
    :lineCategories="lineCategories"
    :lineYAxis="lineYAxis"
    :height="350"
    :xFormatter="xFormatter"
    :yFormatter="yFormatter"
    :curveType="CurveType.MonotoneX"
    :legendPosition="LegendPosition.TopRight"
    xLabel="Month"
    yLabel="Amount ($)"
    :yGridLine="true"
    :lineWidth="3"
  />
</template>
```

## Key Props Reference

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `data` | `T[]` | required | Array of data objects |
| `barCategories` | `Record<string, BulletLegendItem>` | required | Bar series config |
| `barYAxis` | `(keyof T)[]` | required | Data keys for bars |
| `lineCategories` | `Record<string, BulletLegendItem>` | required | Line series config |
| `lineYAxis` | `(keyof T)[]` | required | Data keys for lines |
| `height` | `number` | required | Chart height in pixels |
| `curveType` | `CurveType` | `Linear` | Line interpolation style |
| `lineWidth` | `number` | `2` | Stroke width of lines |
| `xFormatter` | `(tick, i?, ticks?) => string` | - | X-axis label formatter |
| `yFormatter` | `(tick, i?, ticks?) => string` | - | Y-axis label formatter |
| `yLabelSecondary` | `string` | - | Secondary y-axis label |
| `xNumTicks` | `number` | auto | Desired x-axis tick count |
| `yNumTicks` | `number` | auto | Desired y-axis tick count |
| `xGridLine` | `boolean` | `false` | Show vertical grid lines |
| `yGridLine` | `boolean` | `false` | Show horizontal grid lines |
| `hideLegend` | `boolean` | `false` | Hide the legend |
| `hideTooltip` | `boolean` | `false` | Hide tooltips |

## Common Patterns

### Sales vs Target

```vue
<script setup>
const data = [
  { period: 'Week 1', actual: 12500, target: 10000 },
  { period: 'Week 2', actual: 14200, target: 12000 },
  { period: 'Week 3', actual: 11800, target: 13000 },
  { period: 'Week 4', actual: 15600, target: 14000 },
]

const barCategories = {
  actual: { name: 'Actual Sales', color: '#3b82f6' }
}

const lineCategories = {
  target: { name: 'Target', color: '#f59e0b' }
}
</script>

<template>
  <DualChart
    :data="data"
    :barCategories="barCategories"
    :barYAxis="['actual']"
    :lineCategories="lineCategories"
    :lineYAxis="['target']"
    :height="300"
    :xFormatter="(i) => data[i].period"
  />
</template>
```

### Temperature with Precipitation

```vue
<script setup>
const weatherData = [
  { day: 'Mon', temp: 22, rain: 5 },
  { day: 'Tue', temp: 25, rain: 0 },
  { day: 'Wed', temp: 19, rain: 12 },
  { day: 'Thu', temp: 21, rain: 8 },
  { day: 'Fri', temp: 28, rain: 0 },
]

// Bars for rain
const barCategories = {
  rain: { name: 'Rainfall (mm)', color: '#3b82f6' }
}

// Line for temperature
const lineCategories = {
  temp: { name: 'Temperature (°C)', color: '#ef4444' }
}
</script>

<template>
  <DualChart
    :data="weatherData"
    :barCategories="barCategories"
    :barYAxis="['rain']"
    :lineCategories="lineCategories"
    :lineYAxis="['temp']"
    :height="300"
    :xFormatter="(i) => weatherData[i].day"
    :yFormatter="(tick) => `${tick}`"
  />
</template>
```

## Gotchas

1. **Need FOUR category configs**: barCategories + barYAxis + lineCategories + lineYAxis
2. **Keys must match across configs**: barYAxis keys must exist in barCategories
3. **Different scales?**: Both use the same y-axis scale by default. Consider if your data ranges differ significantly.
4. **Legend combines both**: Bar and line categories appear together in legend

Overview

This skill builds DualChart components that combine bar and line series into a single visualization. It’s designed to compare two different metrics—like revenue as bars and profit margin as a trend line—while keeping configuration simple and declarative. The component is Vue 3–native and optimized for clear comparisons between related data types.

How this skill works

The DualChart accepts a data array and two parallel sets of category configs: barCategories + barYAxis for bar series, and lineCategories + lineYAxis for line series. It renders bars and overlaid lines on a shared x-axis, with configurable axis formatters, curve type, grid lines, legend placement, and line stroke width. Categories control labels and colors; axis keys must match their category records.

When to use it

  • Compare absolute values (bars) against a trend or ratio (line) for the same periods.
  • Show operational metrics where two different scales or units are conceptually related (sales vs margin).
  • Highlight targets or thresholds (line) against actual totals (bars).
  • Visualize dual aspects of time series data like rainfall (bars) and temperature (line).

Best practices

  • Always provide all four required category props: barCategories, barYAxis, lineCategories, lineYAxis to avoid runtime mismatches.
  • Keep category keys consistent across category objects and Y-axis arrays so series map correctly.
  • If ranges differ greatly, normalize data or add notes—DualChart uses a single y-scale by default.
  • Use xFormatter and yFormatter for readable axis labels (short month names, currency, units).
  • Adjust curveType and lineWidth to match the visual emphasis between bars and lines.

Example use cases

  • Monthly finance dashboard: revenue and costs as bars, profit as a line to show margin trends.
  • Sales vs target: actual sales as bars with target line for quick performance checks.
  • Weather summary: rainfall in bars and temperature as a line for daily summaries.
  • Product analytics: number of purchases (bars) vs conversion rate (line) across campaigns.

FAQ

What props are mandatory?

You must supply data, barCategories, barYAxis, lineCategories, lineYAxis, and height.

Can bars and lines use separate y-axes?

Not by default—the DualChart shares one y-scale. If your ranges differ, normalize values or annotate differences; consider a chart variant that supports dual y-axes.