DESKTOP

Chart.js アノテーション完全ガイド - 実践的なデータ可視化

Chart.js アノテーション完全ガイド

Chart.jsのアノテーション機能を利用して、データの可視化を効果的に行うための包括的なガイドです。このドキュメントでは、基本概念から高度なテクニックまでを網羅し、実際のビジネスシーンでの活用例も紹介します。すべてのアノテーション例には、動作するサンプルグラフを掲載しています。

1. 基本概念

アノテーションとは、グラフ上に特定の情報を視覚的に示すための追加要素です。これにより、データの重要なポイントや傾向を強調できます。

必要なライブラリ


import Chart from 'chart.js/auto';
import annotationPlugin from 'chartjs-plugin-annotation';
Chart.register(annotationPlugin);
      

注:このガイドではCDNを使用しているため、HTMLの<script>タグでライブラリを読み込んでいます。

基本構造


const config = {
  type: 'line',
  data: { /* データ設定 */ },
  options: {
    plugins: {
      annotation: {
        annotations: {
          myAnnotation: {
            // アノテーションの設定
          }
        }
      }
    }
  }
};
      

2. 縦線アノテーション

特定の日付や時点を示すのに最適です。以下の例では、基本的な縦線、スタイル付き縦線、高度な設定の縦線を示します。


const verticalLineConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-03', '2020-06', '2020-09', '2020-12'],
    datasets: [{
      label: 'データ',
      data: [50, 75, 100, 125, 150],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          basicVerticalLine: {
            type: 'line',
            scaleID: 'x',
            value: '2020-03',
            borderColor: '#FF0000',
            borderWidth: 2
          },
          styledVerticalLine: {
            type: 'line',
            scaleID: 'x',
            value: '2020-06',
            borderColor: '#3366CC',
            borderWidth: 3,
            borderDash: [10, 5],
            borderDashOffset: 0,
            shadowColor: 'rgba(51, 102, 204, 0.3)',
            shadowBlur: 8,
            shadowOffsetX: 2,
            shadowOffsetY: 2,
            label: {
              enabled: true,
              content: 'イベント発生日',
              position: 'start',
              backgroundColor: '#3366CC',
              color: 'white',
              padding: 8,
              borderRadius: 4,
              font: {
                size: 14,
                weight: 'bold',
                family: 'Arial'
              }
            }
          },
          advancedVerticalLine: {
            type: 'line',
            scaleID: 'x',
            value: '2020-09',
            display: (ctx) => ctx.chart.data.datasets.length > 0,
            borderColor: (ctx) => {
              const chart = ctx.chart;
              const {ctx: canvasCtx, chartArea} = chart;
              if (!chartArea) return '#FF0000';
              const gradient = canvasCtx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
              gradient.addColorStop(0, '#FF0000');
              gradient.addColorStop(1, '#FF6666');
              return gradient;
            },
            borderWidth: 4,
            enter: ({element, chart}) => {
              element.options.borderWidth = 6;
              chart.update('none');
            },
            leave: ({element, chart}) => {
              element.options.borderWidth = 4;
              chart.update('none');
            }
          }
        }
      }
    }
  }
};
      

3. 横線アノテーション

目標値や閾値を示すのに便利です。以下の例では、基本的な横線、目標値ライン、警告ラインを示します。


const horizontalLineConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-02', '2020-03', '2020-04', '2020-05'],
    datasets: [{
      label: 'データ',
      data: [50, 100, 75, 150, 120],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          basicHorizontalLine: {
            type: 'line',
            scaleID: 'y',
            value: 100,
            borderColor: '#00AA00',
            borderWidth: 2
          },
          targetLine: {
            type: 'line',
            scaleID: 'y',
            value: 150,
            borderColor: '#FF9900',
            borderWidth: 3,
            borderDash: [5, 5],
            xMin: '2020-01',
            xMax: '2020-05',
            label: {
              enabled: true,
              content: '目標値: 150',
              position: 'end',
              xAdjust: -10,
              backgroundColor: '#FF9900',
              color: 'white',
              padding: 6,
              font: {
                size: 12,
                weight: 'bold'
              }
            }
          },
          warningLow: {
            type: 'line',
            scaleID: 'y',
            value: 30,
            borderColor: '#FF3333',
            borderWidth: 2,
            borderDash: [3, 3],
            label: {
              enabled: true,
              content: '⚠️ 警告レベル(低)',
              position: 'start',
              backgroundColor: 'rgba(255, 51, 51, 0.9)',
              color: 'white'
            }
          },
          warningHigh: {
            type: 'line',
            scaleID: 'y',
            value: 200,
            borderColor: '#FF3333',
            borderWidth: 2,
            borderDash: [3, 3],
            label: {
              enabled: true,
              content: '⚠️ 警告レベル(高)',
              position: 'start',
              backgroundColor: 'rgba(255, 51, 51, 0.9)',
              color: 'white'
            }
          }
        }
      }
    }
  }
};
      

4. 矩形エリア

期間や範囲を強調するのに最適です。以下の例では、基本的な矩形、スタイル豊富な矩形、インタラクティブな矩形を示します。


const boxAreaConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-03', '2020-06', '2020-09', '2020-12'],
    datasets: [{
      label: 'データ',
      data: [50, 75, 100, 125, 150],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          basicBox: {
            type: 'box',
            xMin: '2020-03',
            xMax: '2020-06',
            yMin: 50,
            yMax: 150,
            backgroundColor: 'rgba(255, 0, 0, 0.1)',
            borderColor: '#FF0000',
            borderWidth: 2
          },
          styledBox: {
            type: 'box',
            xMin: '2020-06',
            xMax: '2020-09',
            yMin: 0,
            yMax: 200,
            backgroundColor: (ctx) => {
              const chart = ctx.chart;
              const {ctx: canvasCtx, chartArea} = chart;
              if (!chartArea) return 'rgba(0, 123, 255, 0.1)';
              const gradient = canvasCtx.createLinearGradient(
                chartArea.left, 0, chartArea.right, 0
              );
              gradient.addColorStop(0, 'rgba(0, 123, 255, 0.05)');
              gradient.addColorStop(1, 'rgba(0, 123, 255, 0.2)');
              return gradient;
            },
            borderColor: '#007BFF',
            borderWidth: 3,
            borderDash: [8, 4],
            borderRadius: 8,
            borderSkipped: false,
            shadowColor: 'rgba(0, 123, 255, 0.3)',
            shadowBlur: 10,
            shadowOffsetX: 3,
            shadowOffsetY: 3,
            label: {
              enabled: true,
              content: ['夏季キャンペーン', '期間限定'],
              position: { x: 'center', y: 'center' },
              backgroundColor: '#007BFF',
              color: 'white',
              padding: 12,
              borderRadius: 6,
              font: { size: 14, weight: 'bold' },
              rotation: 0
            }
          },
          interactiveBox: {
            type: 'box',
            xMin: '2020-09',
            xMax: '2020-12',
            yMin: 75,
            yMax: 125,
            backgroundColor: 'rgba(40, 167, 69, 0.1)',
            borderColor: '#28A745',
            borderWidth: 2,
            click: ({element, chart}) => {
              alert('秋季プロモーション期間がクリックされました!');
            },
            enter: ({element, chart}) => {
              element.options.backgroundColor = 'rgba(40, 167, 69, 0.3)';
              element.options.borderWidth = 4;
              chart.update('none');
            },
            leave: ({element, chart}) => {
              element.options.backgroundColor = 'rgba(40, 167, 69, 0.1)';
              element.options.borderWidth = 2;
              chart.update('none');
            }
          }
        }
      }
    }
  }
};
      

5. 楕円・円

特定のポイントを目立たせるのに効果的です。以下の例では、基本的な楕円、完全な円、アニメーション付き楕円を示します。


const ellipseCircleConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-04', '2020-07', '2020-10', '2020-12'],
    datasets: [{
      label: 'データ',
      data: [50, 80, 100, 120, 150],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          basicEllipse: {
            type: 'ellipse',
            xMin: '2020-04',
            xMax: '2020-05',
            yMin: 80,
            yMax: 120,
            backgroundColor: 'rgba(255, 193, 7, 0.2)',
            borderColor: '#FFC107',
            borderWidth: 3
          },
          perfectCircle: {
            type: 'ellipse',
            xMin: '2020-07',
            xMax: '2020-08',
            yMin: 90,
            yMax: 120,
            backgroundColor: 'rgba(220, 53, 69, 0.15)',
            borderColor: '#DC3545',
            borderWidth: 4,
            borderDash: [6, 3],
            rotation: Math.PI / 4,
            label: {
              enabled: true,
              content: '注目!',
              color: '#DC3545',
              font: { size: 16, weight: 'bold' }
            }
          },
          animatedEllipse: {
            type: 'ellipse',
            xMin: '2020-10',
            xMax: '2020-11',
            yMin: 60,
            yMax: 140,
            backgroundColor: 'rgba(111, 66, 193, 0.1)',
            borderColor: '#6F42C1',
            borderWidth: 3
          }
        }
      }
    }
  }
};
      

6. ポイントマーカー

特定のデータポイントを強調します。以下の例では、基本ポイント、装飾付きポイント、カスタム形状ポイントを示します。


const pointMarkerConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-03', '2020-06', '2020-09', '2020-12'],
    datasets: [{
      label: 'データ',
      data: [50, 95, 130, 75, 150],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          basicPoint: {
            type: 'point',
            xValue: '2020-03',
            yValue: 95,
            backgroundColor: '#FF0000',
            borderColor: '#FFFFFF',
            borderWidth: 3,
            radius: 8
          },
          decoratedPoint: {
            type: 'point',
            xValue: '2020-06',
            yValue: 130,
            backgroundColor: '#28A745',
            borderColor: '#FFFFFF',
            borderWidth: 4,
            radius: 12,
            shadowColor: 'rgba(40, 167, 69, 0.5)',
            shadowBlur: 12,
            shadowOffsetX: 2,
            shadowOffsetY: 2,
            label: {
              enabled: true,
              content: '最高値達成!',
              position: 'top',
              yAdjust: -15,
              backgroundColor: '#28A745',
              color: 'white',
              padding: 8,
              borderRadius: 20,
              font: { size: 12, weight: 'bold' }
            }
          },
          customPoint: {
            type: 'point',
            xValue: '2020-09',
            yValue: 75,
            pointStyle: 'star',
            backgroundColor: '#FFC107',
            borderColor: '#212529',
            borderWidth: 2,
            radius: 15,
            rotation: Math.PI / 6
          }
        }
      }
    }
  }
};
      

7. ラベル設定詳細

ラベルは情報を伝える重要な要素です。以下の例では、ラベルの詳細な設定を示します。サンプルグラフを追加しました。


label: {
  enabled: true,
  content: 'ラベルテキスト',
  content: [
    '重要なお知らせ',
    '詳細は担当者まで',
    '期限: 2020/12/31'
  ],
  content: (ctx) => {
    const value = ctx.parsed?.y || 0;
    return `現在値: ${value.toFixed(1)}`;
  },
  position: 'center',
  xAdjust: 0,
  yAdjust: -20,
  position: { x: 'center', y: 'top' },
  color: '#FFFFFF',
  backgroundColor: '#007BFF',
  backgroundColor: (ctx) => {
    const chart = ctx.chart;
    const {ctx: canvasCtx} = chart;
    const gradient = canvasCtx.createLinearGradient(0, 0, 100, 0);
    gradient.addColorStop(0, '#007BFF');
    gradient.addColorStop(1, '#6610F2');
    return gradient;
  },
  borderColor: '#FFFFFF',
  borderWidth: 2,
  borderRadius: 8,
  borderDash: [5, 3],
  padding: 12,
  padding: { top: 8, right: 12, bottom: 8, left: 12 },
  font: {
    family: 'Arial, sans-serif',
    size: 14,
    weight: 'bold',
    style: 'normal',
    lineHeight: 1.2
  },
  rotation: Math.PI / 4,
  shadowColor: 'rgba(0, 0, 0, 0.3)',
  shadowBlur: 6,
  shadowOffsetX: 2,
  shadowOffsetY: 2,
  textAlign: 'center',
  enabled: (ctx) => ctx.element.options.borderWidth > 2
}
      

8. アニメーション設定

動きのある効果でユーザーの注意を引きます。以下の例では、アニメーション設定を示します。サンプルグラフを追加しました。


options: {
  animation: {
    duration: 2000,
    easing: 'easeInOutQuart',
    onProgress: (animation) => {
      const progress = animation.currentStep / animation.numSteps;
      console.log(`アニメーション進行度: ${(progress * 100).toFixed(1)}%`);
    },
    onComplete: () => {
      console.log('アニメーション完了');
    }
  },
  plugins: {
    annotation: {
      animations: {
        borderWidth: { duration: 1000, easing: 'easeInOutBounce' },
        backgroundColor: { duration: 1500, easing: 'easeInOutSine' }
      },
      annotations: {
        fadeInLine: {
          type: 'line',
          scaleID: 'x',
          value: '2020-01-01',
          borderColor: '#FF0000',
          borderWidth: 3,
          enabled: false,
          animation: {
            enabled: { duration: 0, delay: 1000 },
            borderWidth: { from: 0, to: 3, duration: 1000 }
          }
        },
        wavyLine: {
          type: 'line',
          scaleID: 'y',
          value: (ctx) => {
            const time = Date.now();
            return 100 + Math.sin(time / 1000) * 20;
          },
          borderColor: '#00AA00',
          borderWidth: 2
        },
        pulsingPoint: {
          type: 'point',
          xValue: '2020-06-01',
          yValue: 100,
          backgroundColor: '#FF6B00',
          radius: (ctx) => {
            const time = Date.now();
            return 8 + Math.sin(time / 500) * 4;
          }
        }
      }
    }
  }
}
      

9. イベント処理

ユーザーのインタラクションに応答します。以下の例では、インタラクティブなアノテーションを示します。


const eventHandlingConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-03', '2020-06', '2020-09', '2020-12'],
    datasets: [{
      label: 'データ',
      data: [50, 75, 100, 125, 150],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          interactiveAnnotation: {
            type: 'box',
            xMin: '2020-01',
            xMax: '2020-03',
            yMin: 50,
            yMax: 150,
            backgroundColor: 'rgba(0, 123, 255, 0.1)',
            borderColor: '#007BFF',
            borderWidth: 2,
            click: ({element, chart}) => {
              console.log('クリックされたアノテーション:', element.options);
              console.log('チャートデータ:', chart.data);
              alert('Q1期間がクリックされました!');
              element.options.backgroundColor = 'rgba(40, 167, 69, 0.3)';
              chart.update('none');
              setTimeout(() => {
                element.options.backgroundColor = 'rgba(0, 123, 255, 0.1)';
                chart.update('none');
              }, 1000);
            },
            enter: ({element, chart}) => {
              element.options.backgroundColor = 'rgba(0, 123, 255, 0.3)';
              element.options.borderWidth = 4;
              chart.canvas.style.cursor = 'pointer';
              chart.update('none');
            },
            leave: ({element, chart}) => {
              element.options.backgroundColor = 'rgba(0, 123, 255, 0.1)';
              element.options.borderWidth = 2;
              chart.canvas.style.cursor = 'default';
              chart.update('none');
            }
          }
        }
      }
    }
  }
};
      

10. 実用例

実際のビジネスシーンで使える具体的な例を以下に示します。各例には動作するサンプルグラフを掲載しています。

売上分析ダッシュボード


const salesAnalysisConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-02', '2020-03', '2020-04', '2020-05', '2020-06',
             '2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12'],
    datasets: [{
      label: '月次売上',
      data: [120, 135, 95, 85, 140, 160, 180, 155, 170, 145, 190, 210],
      borderColor: '#007BFF',
      backgroundColor: 'rgba(0, 123, 255, 0.1)',
      tension: 0.4
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          covidStart: {
            type: 'line',
            scaleID: 'x',
            value: '2020-03',
            borderColor: '#DC3545',
            borderWidth: 3,
            borderDash: [8, 4],
            label: {
              enabled: true,
              content: 'COVID-19影響開始',
              position: 'start',
              backgroundColor: '#DC3545',
              color: 'white',
              padding: 8,
              font: { size: 12, weight: 'bold' }
            }
          },
          lowPerformance: {
            type: 'box',
            xMin: '2020-03',
            xMax: '2020-05',
            yMin: 0,
            yMax: 250,
            backgroundColor: 'rgba(220, 53, 69, 0.05)',
            borderColor: 'rgba(220, 53, 69, 0.3)',
            borderWidth: 1,
            label: {
              enabled: true,
              content: '売上低迷期',
              position: { x: 'center', y: 'top' },
              yAdjust: 10,
              backgroundColor: 'rgba(220, 53, 69, 0.8)',
              color: 'white'
            }
          },
          recoveryStart: {
            type: 'line',
            scaleID: 'x',
            value: '2020-06',
            borderColor: '#28A745',
            borderWidth: 2,
            label: {
              enabled: true,
              content: '回復基調',
              position: 'start',
              backgroundColor: '#28A745',
              color: 'white'
            }
          },
          targetLine: {
            type: 'line',
            scaleID: 'y',
            value: 150,
            borderColor: '#FFC107',
            borderWidth: 2,
            borderDash: [5, 5],
            label: {
              enabled: true,
              content: '月次目標: 150',
              position: 'end',
              backgroundColor: '#FFC107',
              color: '#212529'
            }
          },
          bestRecord: {
            type: 'point',
            xValue: '2020-12',
            yValue: 210,
            backgroundColor: '#FFD700',
            borderColor: '#FF8C00',
            borderWidth: 3,
            radius: 12,
            label: {
              enabled: true,
              content: '過去最高!',
              position: 'top',
              yAdjust: -15,
              backgroundColor: '#FFD700',
              color: '#8B4513',
              font: { weight: 'bold' }
            }
          }
        }
      }
    }
  }
};
      

プロジェクト進捗管理


const projectConfig = {
  type: 'line',
  data: {
    labels: ['Week1', 'Week2', 'Week3', 'Week4', 'Week5', 'Week6', 'Week7', 'Week8'],
    datasets: [{
      label: '実績',
      data: [10, 25, 40, 35, 55, 70, 80, 95],
      borderColor: '#007BFF'
    }, {
      label: '計画',
      data: [12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100],
      borderColor: '#6C757D',
      borderDash: [5, 5]
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          milestone1: {
            type: 'line',
            scaleID: 'x',
            value: 'Week3',
            borderColor: '#17A2B8',
            borderWidth: 3,
            label: {
              enabled: true,
              content: 'MS1: 基本設計完了',
              position: 'start',
              rotation: Math.PI / 2,
              backgroundColor: '#17A2B8',
              color: 'white'
            }
          },
          milestone2: {
            type: 'line',
            scaleID: 'x',
            value: 'Week6',
            borderColor: '#17A2B8',
            borderWidth: 3,
            label: {
              enabled: true,
              content: 'MS2: 開発完了',
              position: 'start',
              rotation: Math.PI / 2,
              backgroundColor: '#17A2B8',
              color: 'white'
            }
          },
          delayWarning: {
            type: 'box',
            xMin: 'Week3',
            xMax: 'Week5',
            yMin: 0,
            yMax: 100,
            backgroundColor: 'rgba(255, 193, 7, 0.1)',
            borderColor: 'rgba(255, 193, 7, 0.5)',
            borderWidth: 1,
            label: {
              enabled: true,
              content: '⚠️ 進捗遅延期間',
              backgroundColor: '#FFC107',
              color: '#212529'
            },
            click: () => {
              alert('遅延要因:\n- リソース不足\n- 要件変更\n- 技術的課題');
            }
          },
          completionTarget: {
            type: 'point',
            xValue: 'Week8',
            yValue: 100,
            backgroundColor: '#28A745',
            borderColor: '#FFFFFF',
            borderWidth: 3,
            radius: 10,
            label: {
              enabled: true,
              content: '完了予定',
              position: 'top',
              backgroundColor: '#28A745',
              color: 'white'
            }
          }
        }
      }
    }
  }
};
      

株価チャート分析


const stockConfig = {
  type: 'line',
  data: {
    labels: ['2020-01', '2020-02', '2020-03', '2020-04', '2020-05', '2020-06'],
    datasets: [{
      label: '株価',
      data: [1000, 1150, 850, 750, 950, 1100],
      borderColor: '#007BFF'
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          upTrend: {
            type: 'line',
            scaleID: 'x',
            xMin: '2020-04',
            xMax: '2020-06',
            yMin: 750,
            yMax: 1100,
            borderColor: '#28A745',
            borderWidth: 2,
            label: {
              enabled: true,
              content: '上昇トレンド',
              position: 'end',
              backgroundColor: '#28A745',
              color: 'white'
            }
          },
          supportLine: {
            type: 'line',
            scaleID: 'y',
            value: 800,
            borderColor: '#007BFF',
            borderWidth: 2,
            borderDash: [10, 5],
            label: {
              enabled: true,
              content: 'サポート: ¥800',
              position: 'start',
              backgroundColor: '#007BFF',
              color: 'white'
            }
          },
          resistanceLine: {
            type: 'line',
            scaleID: 'y',
            value: 1200,
            borderColor: '#DC3545',
            borderWidth: 2,
            borderDash: [10, 5],
            label: {
              enabled: true,
              content: 'レジスタンス: ¥1,200',
              position: 'start',
              backgroundColor: '#DC3545',
              color: 'white'
            }
          },
          announcement: {
            type: 'line',
            scaleID: 'x',
            value: '2020-03',
            borderColor: '#6F42C1',
            borderWidth: 3,
            label: {
              enabled: true,
              content: ['重要発表', '決算説明会'],
              position: 'start',
              backgroundColor: '#6F42C1',
              color: 'white',
              padding: 10
            }
          },
          buySignal: {
            type: 'point',
            xValue: '2020-04',
            yValue: 750,
            backgroundColor: '#28A745',
            borderColor: '#FFFFFF',
            borderWidth: 3,
            radius: 15,
            label: {
              enabled: true,
              content: '💰 買いシグナル',
              position: 'bottom',
              yAdjust: 15,
              backgroundColor: '#28A745',
              color: 'white',
              font: { size: 14, weight: 'bold' }
            }
          }
        }
      }
    }
  }
};
      

医療データ可視化


const medicalConfig = {
  type: 'line',
  data: {
    labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
    datasets: [{
      label: '血圧(最高)',
      data: [120, 115, 130, 140, 135, 125, 118],
      borderColor: '#DC3545'
    }, {
      label: '血圧(最低)',
      data: [80, 75, 85, 90, 88, 82, 78],
      borderColor: '#007BFF'
    }]
  },
  options: {
    plugins: {
      annotation: {
        annotations: {
          highBPWarning: {
            type: 'line',
            scaleID: 'y',
            value: 140,
            borderColor: '#DC3545',
            borderWidth: 2,
            borderDash: [8, 4],
            label: {
              enabled: true,
              content: '⚠️ 高血圧レベル',
              position: 'end',
              backgroundColor: '#DC3545',
              color: 'white'
            }
          },
          medicationTime: {
            type: 'line',
            scaleID: 'x',
            value: '08:00',
            borderColor: '#28A745',
            borderWidth: 2,
            label: {
              enabled: true,
              content: '💊 服薬時間',
              position: 'top',
              backgroundColor: '#28A745',
              color: 'white'
            }
          },
          exerciseTime: {
            type: 'box',
            xMin: '16:00',
            xMax: '20:00',
            yMin: 0,
            yMax: 180,
            backgroundColor: 'rgba(255, 193, 7, 0.1)',
            borderColor: '#FFC107',
            borderWidth: 1,
            label: {
              enabled: true,
              content: '🏃 運動時間帯',
              backgroundColor: '#FFC107',
              color: '#212529'
            }
          },
          normalRange: {
            type: 'box',
            xMin: '00:00',
            xMax: '24:00',
            yMin: 90,
            yMax: 130,
            backgroundColor: 'rgba(40, 167, 69, 0.05)',
            borderColor: 'rgba(40, 167, 69, 0.3)',
            borderWidth: 1,
            label: {
              enabled: true,
              content: '正常範囲',
              position: { x: 'start', y: 'center' },
              backgroundColor: 'rgba(40, 167, 69, 0.8)',
              color: 'white'
            }
          }
        }
      }
    }
  }
};
      

🔧 高度なテクニック

動的アノテーション更新

以下のコードは、データの更新に応じてアノテーションを動的に変更する例です。サンプルグラフを追加しました。


function updateAnnotations(chart, newData) {
  const annotations = chart.options.plugins.annotation.annotations;
  const maxValue = Math.max(...newData);
  annotations.warningLine = {
    type: 'line',
    scaleID: 'y',
    value: maxValue * 0.9,
    borderColor: '#FF0000',
    borderWidth: 2
  };
  const latestIndex = newData.length - 1;
  annotations.latestPoint = {
    type: 'point',
    xValue: chart.data.labels[latestIndex],
    yValue: newData[latestIndex],
    backgroundColor: '#FF6B00',
    radius: 10
  };
  chart.update();
}
      

カスタムプラグイン作成

以下のコードは、カスタム矢印やアイコンを描画するプラグインの例です。サンプルグラフを追加しました。


const customAnnotationPlugin = {
  id: 'customAnnotation',
  afterDraw(chart, args, options) {
    const ctx = chart.ctx;
    const chartArea = chart.chartArea;
    if (options.arrows) {
      options.arrows.forEach(arrow => {
        drawArrow(ctx, arrow, chart.scales);
      });
    }
    if (options.icons) {
      options.icons.forEach(icon => {
        drawIcon(ctx, icon, chart.scales);
      });
    }
  }
};
function drawArrow(ctx, arrow, scales) {
  const x = scales.x.getPixelForValue(arrow.x);
  const y = scales.y.getPixelForValue(arrow.y);
  ctx.save();
  ctx.fillStyle = arrow.color || '#FF0000';
  ctx.translate(x, y);
  ctx.rotate(arrow.rotation || 0);
  ctx.beginPath();
  ctx.moveTo(-10, -5);
  ctx.lineTo(0, 0);
  ctx.lineTo(-10, 5);
  ctx.lineTo(-7, 0);
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}
function drawIcon(ctx, icon, scales) {
  const x = scales.x.getPixelForValue(icon.x);
  const y = scales.y.getPixelForValue(icon.y);
  ctx.save();
  ctx.font = `${icon.size || 20}px Arial`;
  ctx.fillStyle = icon.color || '#000000';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(icon.symbol || '★', x, y);
  ctx.restore();
}
      

パフォーマンス最適化

以下のコードは、大量のアノテーションを効率的に処理するための最適化例です。サンプルグラフを追加しました。


const optimizedAnnotations = {
  animation: false,
  clip: true,
  annotations: {
    conditionalLine: {
      type: 'line',
      scaleID: 'x',
      value: '2020-06-01',
      borderColor: '#FF0000',
      display: (ctx) => {
        const chart = ctx.chart;
        const xScale = chart.scales.x;
        const range = xScale.max - xScale.min;
        return range < 365 * 24 * 60 * 60 * 1000;
      }
    }
  }
};
const memoizedAnnotations = {};
function getMemoizedAnnotation(key, generator) {
  if (!memoizedAnnotations[key]) {
    memoizedAnnotations[key] = generator();
  }
  return memoizedAnnotations[key];
}
      

🎨 デザインのベストプラクティス

カラーパレット

統一感のあるカラーパレットを使用することで、視覚的な一貫性を保ちます。サンプルグラフを追加しました。


const colorPalette = {
  primary: '#007BFF',
  secondary: '#6C757D',
  success: '#28A745',
  warning: '#FFC107',
  danger: '#DC3545',
  info: '#17A2B8',
  primaryAlpha: 'rgba(0, 123, 255, 0.2)',
  successAlpha: 'rgba(40, 167, 69, 0.2)',
  warningAlpha: 'rgba(255, 193, 7, 0.2)',
  dangerAlpha: 'rgba(220, 53, 69, 0.2)'
};
      

一貫性のあるスタイル

共通のスタイルを定義して、すべてのアノテーションに適用することで一貫性を確保します。サンプルグラフを追加しました。


const commonStyles = {
  lineWidth: 2,
  pointRadius: 8,
  labelPadding: 8,
  labelBorderRadius: 4,
  fontFamily: 'system-ui, -apple-system, sans-serif',
  fontSize: 12
};
function applyCommonStyles(annotation) {
  return {
    ...annotation,
    borderWidth: commonStyles.lineWidth,
    label: {
      ...annotation.label,
      padding: commonStyles.labelPadding,
      borderRadius: commonStyles.labelBorderRadius,
      font: {
        family: commonStyles.fontFamily,
        size: commonStyles.fontSize,
        ...annotation.label?.font
      }
    }
  };
}
      

🚀 まとめ

Chart.jsのアノテーション機能を使うことで、データの可視化をより効果的に行えます。以下のポイントを押さえてください:

  • 目的に応じた適切なアノテーションタイプの選択
  • ユーザビリティを考慮したインタラクション設計
  • パフォーマンスを意識した実装
  • 一貫性のあるデザイン
  • アクセシビリティへの配慮

学習の進め方

  1. 基本的な縦線・横線から始める
  2. ラベル設定をマスターする
  3. インタラクション機能を追加する
  4. 実際のプロジェクトで活用する
  5. カスタムプラグインで機能拡張する

これらの知識を活用して、魅力的で実用的なデータ可視化を作成してください!

このエントリーをはてなブックマークに追加

コメント

コメントフォーム
記事の評価
  • リセット
  • リセット

↑このページのトップヘ