• #Vue3
  • #コンポーネント設計
  • #財務チャート

インラインデータ形式のチャートコンポーネント設計

背景・目的

課題

従来の実装では、データがスクリプト内の変数(epsDataMap, sgaDataMapなど)に定義され、テンプレートのコンポーネントは変数名を参照していた。

<script setup>
// データはここに定義(テンプレートから物理的に離れている)
const microsoftExpenseRatioData = computed(() => { ... })
</script>

<template>
  <!-- 変数名を参照 -->
  <ExpenseRatioChart
    :ratio-data="microsoftExpenseRatioData"
    :current-period-index="microsoftPeriodIndex"
  />
</template>

問題点:

  • データと表示が物理的に離れている
  • 何を渡したら何が表示されるか分かりにくい
  • 別の会社のデータを追加するとき、コピペしづらい

目標

AIにデータを渡して「このフォーマットに変換して」と言えば、そのままコピペで動くコードが生成される形式にしたい。

<!-- AIが生成したコードをそのままペーストできる -->
<ExpenseRatioChart
  :config="{
    financials: [
      { label: '2015', revenue: 93580, sga: 20324, rd: 12046 },
      { label: '2016', revenue: 91154, sga: 19198, rd: 11988 },
      ...
    ],
    currentPeriodIndex: microsoftPeriodIndex
  }"
/>

設計方針

1. 元データを渡し、比率はコンポーネント内で計算

理由:

  • Koyfinなどから取得した元データをそのまま渡せる
  • AIに「比率を計算して」という追加の指示が不要
  • 計算ロジックがコンポーネントに集約される
// コンポーネント内で比率を計算
const ratioData = computed(() =>
  props.config.financials.map(d => ({
    label: d.label,
    sgaRatio: (d.sga / d.revenue) * 100,
    rdRatio: (d.rd / d.revenue) * 100
  }))
)

2. currentPeriodIndex は変数参照を許容

アニメーションで動的に変わる値なので、これだけは変数参照が必要。

<ExpenseRatioChart
  :config="{
    financials: [...],  // リテラルデータ
    currentPeriodIndex: microsoftPeriodIndex  // 変数参照(動的)
  }"
/>

3. 将来的にはラッパーコンポーネントで統合

最終形は、全チャートをラップするコンポーネント:

<CompanyFinancialDashboard
  :config="{
    companyName: 'Microsoft',
    financials: [
      {
        label: '2015',
        revenue: 93580,
        grossProfit: 60542,
        operatingIncome: 28172,
        netIncome: 12193,
        eps: 1.48,
        sga: 20324,
        rd: 12046,
        operatingCF: 29668,
        capex: -5944,
        fcf: 23724
      },
      ...
    ]
  }"
/>

ラッパー内部で:

  • currentPeriodIndexを管理
  • 各チャートに適切なデータを渡す
  • 比率を計算して渡す

実装手順

Step 1: ExpenseRatioChart を変更(完了)

Before:

interface Props {
  ratioData: { label: string; sgaRatio: number; rdRatio: number }[]
  currentPeriodIndex: number
}

After:

interface FinancialDataItem {
  label: string
  revenue: number
  sga: number
  rd: number
}

interface ChartConfig {
  financials: FinancialDataItem[]
  currentPeriodIndex: number
}

interface Props {
  config: ChartConfig
}

// 比率はコンポーネント内で計算
const ratioData = computed(() =>
  props.config.financials.map(d => ({
    label: d.label,
    sgaRatio: (d.sga / d.revenue) * 100,
    rdRatio: (d.rd / d.revenue) * 100
  }))
)

Step 2: 呼び出し側をインラインデータに変更

<ExpenseRatioChart
  :config="{
    financials: [
      { label: '2015', revenue: 93580, sga: 20324, rd: 12046 },
      { label: '2016', revenue: 91154, sga: 19198, rd: 11988 },
      { label: '2017', revenue: 96571, sga: 19942, rd: 13037 },
      { label: '2018', revenue: 110360, sga: 22223, rd: 14726 },
      { label: '2019', revenue: 125843, sga: 23098, rd: 16876 },
      { label: '2020', revenue: 143015, sga: 24709, rd: 19269 },
      { label: '2021', revenue: 168088, sga: 25224, rd: 20716 },
      { label: '2022', revenue: 198270, sga: 27725, rd: 24512 },
      { label: '2023', revenue: 211915, sga: 30334, rd: 27195 },
      { label: '2024', revenue: 245122, sga: 32065, rd: 29510 },
      { label: '2025', revenue: 277985, sga: 32877, rd: 32488 },
      { label: 'LTM', revenue: 289054, sga: 33010, rd: 33090 }
    ],
    currentPeriodIndex: microsoftPeriodIndex
  }"
/>

Step 3: 他のチャートも同様に変更

  • ProfitMarginChart
  • EPSChart
  • FCFChart

Step 4: ラッパーコンポーネント作成(オプション)

全チャートを1つのconfigでまとめて渡せるようにする。

AIへの指示例

Koyfinなどからデータをコピーして、以下のように依頼:

以下のMicrosoftの財務データを、このフォーマットに変換してください:

{ label: '年度', revenue: 売上, sga: SG&A, rd: R&D }

データ:
[ここにKoyfinのデータをペースト]

懸念点と対策

懸念対策
Vueファイルが長くなるデータと表示が近いのでむしろ見やすい
同じデータを別ページで使いたいコピペ、または必要時にcomposableに切り出す
型安全性TypeScriptのprops定義で検出

まとめ

  • 目的: AIでコード生成 → コピペで動く
  • 方針: 元データを渡し、比率はコンポーネント内で計算
  • 手順: まずExpenseRatioChartで試し、うまくいったら他に展開