NextJSでprismaを使用してDBを操作した際にTypeError: The "payload" argument must be of type object. Received null`

背景

  • NextJS製のアプリケーションを作っていて、prismaでDBを操作した際にタイトルのエラーが発生した
  • NextJS 15で発生するものらしい

対応方法

GitHubのissueを参照

  • 下記のissueが作成されている

github.com

  • issue作成者の方は prisma.someTable.createで発生したと言っているが、私は prisma.someTable.createManyで発生した
    • もしかしたら他のメソッドでも発生するのかも

解決方法

  • 参考情報

To temporarily work around this issue, you need to remove the line //# sourceMappingURL=library.js.map from node_modules/@prisma/client/runtime/library.js.

  • node_modulesディレクトリの中にある依存ライブラリのスクリプトを直接編集することで解消できた
    • node_modules/@prisma/client/runtime/library.js.の以下の部分を削除する
//# sourceMappingURL=library.js.map // これを削除

patch-packageの実施

  • node_modulesを削除して再度 npm ciなどをした場合に自動的に上記の変更が適用されるようにしておきべき
    • これについても issue のコメントに記述がある
    • ただしコメントのとおり npm i -D patch-packageとすると、production環境向け環境で npm ciしたときに patch-packageがインストールされない
      • -D (--save-dev)は開発環境向け( NODE_ENV=development )のときにインストールされる
      • npm i --save patch-packageでインストールしたほうがいいのかな(または NODE_ENV=development npm ciとしてインストールするとか?)

参考情報

関連issue

github.com

【自宅kubernetes】Argo CDでGiteaをデプロイする

目次

概要

今回やること

今回はやらないこと

Argo CDのデプロイ

helm を用いてデプロイする

  • 手順は 公式 を参考に若干パラメータをいじる
  • 私の場合は既にクラスタingress-nginxやmetallbを導入済みのため、クラスタ外からのアクセスはそれを使うことにしている
# values.yaml
server:
  ingress:
    enabled: true
    hostname: argocd.nkiri.internal
    ingressClassName: nginx
    path: /
    pathType: Prefix
configs:
  params:
    "server.insecure": true # 後述
$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm install argocd argo/argo-cd -n argocd --create-namespace -f values.yaml

Podの状態を確認

  • STATUSRunningになることを確認
  • 別のPodの起動を待つこともあるのでしばらくは再起動を繰り返すこともある
$ kubectl get pods -n argocd
NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          6h35m
argocd-applicationset-controller-766769bf76-sntnb   1/1     Running   0          6h35m
argocd-dex-server-6df46f4b79-fmtw6                  1/1     Running   0          6h35m
argocd-notifications-controller-54dd575496-gc2tp    1/1     Running   0          7h32m
argocd-redis-6ddc76cf75-4c59l                       1/1     Running   0          7h32m
argocd-repo-server-79f78f6844-mw9ns                 1/1     Running   0          6h35m
argocd-server-5cc5569c8-9c8ws                       1/1     Running   0          6h35m

ブラウザからの確認

  • ingress-nginxのLoadBalancerに割り当てられたExternalIPと、 values.yamlserver.ingress.hostnameで指定したドメインの紐付けを /etc/hostsに追記
  • https://<values.yamlで指定したホスト名>でアクセス
    • ユーザ名: admin
    • パスワード: 以下のコマンドで確認
$ kubectl -n argocd get secret/argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

【おまけ】ERR_TOO_MANY_REDIRECTSが出た場合

  • ERR_TOO_MANY_REDIRECTSとなってArgo CDのWeb UIにアクセスできないことがある
    • その場合は values.yamlにて config.params."server.insecure"trueにすると解消するらしい
    • 詳細は StackOverflow を参照

Giteaのデプロイ

Argo CDの画面からGiteaのHelmChartを登録

  • Argo CDにログインし、トップ画面 > Applications > NEW APP と遷移し、以下を入力(言及してない部分はデフォルトのまま)
    • Application Name: gitea
    • Project Name: Default
    • SYNC POLICY: Automatic
    • SYNC OPTIONS: AUTO-CREATE NAMESPACEのみチェック
    • Repository URL: https://dl.gitea.io/charts/ (右側のプルダウンは HELM を選択)
    • Chart: gitea (右側のプルダウンは 10.4.0(記事作成時点での最新)を選択)
    • Cluster URL: https://kubernetes.default.svc
    • Namespace: gitea
  • 画面上部の CREATEをクリックするとクラスタ上にGiteaがデプロイされる

確認

  • デプロイしたアプリケーションの状態

  • 各リソースの状態

このあとやりたいこと

  • Giteaでアプリケーションを管理し、リポジトリの更新をトリガーとして自動的にクラスタにデプロイするみたいなことをやりたい
    • アプリケーションの登録手順とかは多分今回のGiteaのやり方と同じだと思っているので、設定の更新などが自動で動く様子を確認したい

【追記】Argo CDのデータ永続化について

  • ArgoCDデプロイ時の設定項目に persistenceに関する情報がなかったので気になって調べた
    • こちらのIssueによると、CRDやConfigMap/Secretsにデータを保存する仕組みになっているらしく、PVCを作成する必要はないらしい

自宅kubernetesクラスタをとりあえず動くようにする

やりたいこと

構成

  • クラスタ:
    • VM2台(ControlPlane1台、Worker1台)
      • OS: Ubuntu 22.04
        • 記事作成時点で最新は24.04だけど理由あって22.04を使用している
    • kubernetes: v1.30.3
  • NW
    • 自宅NW: 192.168.11.0/24
      • ControlPlaneノード: 192.168.11.110
      • Workerノード: 192.168.11.115
    • PodNetworkCidr: 172.244.0.0/16
      • kubeadm init実行時に指定

クラスタ環境のセットアップ

CRIとしてCRI-Oをインストール

  • 公式手順の手順でやろうとしたけど上手くいかなかったのでこちらの記事を参考にインストール

    • リポジトリにUbuntu24.04がなかったので、記事を書いている時点ではまだ対応してないのかも
      • これがVMをUbuntu22.04で作った理由
    • 公式手順でうまくいかなかったのは crio.confの編集とかその辺りが足りなかったから?
  • インストール後はこちらの手順も忘れ時に実施

kubernetes関連の諸々をインストール

クラスタの起動

  • ControlPlaneノードでのみ以下を実行

  • kubeadm init <args>をする

    • kubeadm initのオプションは以下の通り(不要なものもあるかもしれないがとりあえず動いたのでヨシ)
$ sudo kubeadm init --apiserver-advertise-address=192.168.11.110 --control-plane-endpoint=192.168.11.110 --pod-network-cidr=172.244.0.0/16

Workerのjoin

  • Workerノードでのみ実行
  • 実行するコマンドはControlPlaneで kubeadm initしたときに出力されるのでそれをコピペ

動作確認

$ kubectl get nodes

を実行してControlPlaneとWorkerがそれぞれReadyになっていればクラスタの起動はOK

Pod / ReplicaSet / Deployment を動かす

  • こちらの記事に書かれてる設定ファイルでとりあえず一通り確認する
    • このときPodのIPが --pod-network-cidrで指定したセグメント内のものにならなくて困った気がする
    • CNI(ContainerNetworkInterface)としてCalicoをインストールしたら想定していたIPが割り当てられた
      • その間いろいろ試していたので別要因かもしれない
      • Calicoのインストールは Helm を使用した

Ubuntu24.04でKVMの環境を作る

概要

目的

  • 手元で自由に遊べる環境が欲しくて久しぶりにKVMVMを適当に作りたくなったけど、以前やったのがだいぶ昔だったし全然思い出せなかったので再度脳内を整理する

やること

  • Ubuntu Server(GUIなしのやつ) にKVMをセットアップする

触れないこと

  • LVM周り
    • とりあえず virt-installVMを起動するところまで

前提情報

HW構成

  • CPU: Ryzen9 7900
  • OS: UbuntuServer24.04
    • ホストもゲストも同じ
  • メモリ: 32GB
  • ストレージ: SSD 1TB

NW構成

  • サブネット: 192.168.11.0/24
  • ホストOS: 192.168.11.100/24
    • インストール時にManual設定

手順

パッケージインストール

sudo apt update
sudo apt upgrade
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst
  • ここでインストールしたパッケージが何のためのものなのかは以下を参考にさせていただいた

qiita.com

ユーザをlibvert/kvmグループに追加

sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER

# 反映のために一度 logoutし、再度loginする

libvertdの起動と自動起動設定

sudo systemctl start libvertd
sudo systemctl enable libvertd

ネットワーク設定

  • 今回はブリッジ接続を行うため、それ用のI/Fを作成する
  • ホストOSインストール時にNW設定をしたためか、デフォルトで /etc/netplan/50-cloud-init.yamlが作成される

    • 本来このファイルは手動で編集するべきではないらしいが、筆者はエラーを解消することができなくてイライラしたのでこのファイルを編集した
    • たぶん本当は 99-manual-config.yamlとか作って設定するのが正しいんだけど、どう書けばよかったんすかね。誰か教えて。
  • 以下の通り編集(冒頭のコメントを消したのは「自動生成したもの」という情報が嘘になるから)

$ sudo diff -u /etc/netplan/50-cloud-init.yaml.orig /etc/netplan/50-cloud-init.yaml
--- /etc/netplan/50-cloud-init.yaml.orig        2024-08-01 12:42:21.662094055 +0000
+++ /etc/netplan/50-cloud-init.yaml     2024-08-01 12:44:23.154575704 +0000
@@ -1,11 +1,11 @@
-# This file is generated from information provided by the datasource.  Changes
-# to it will not persist across an instance reboot.  To disable cloud-init's
-# network configuration capabilities, write a file
-# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
-# network: {config: disabled}
 network:
     ethernets:
         enp3s0:
+            dhcp4: false
+    bridges:
+        br0:
+            interfaces:
+                - enp3s0
             addresses:
             - 192.168.11.100/24
             nameservers:
  • 反映
sudo netplan apply

VMの起動

前提

  • isoファイルは /tmpに置いておく

コマンド

sudo virt-install \
        --name ubuntu-vm \
        --ram 4096 \
        --disk path=/var/lib/libvirt/images/ubuntu-vm.qcow2,size=20 \
        --vcpus 2 \
        --os-variant ubuntu24.04 \
        --network bridge=br0 \
        --graphics none \
        --console pty,target_type=serial \
        --location '/tmp/ubuntu-24.04-live-server-amd64.iso,initrd=casper/initrd,kernel=casper/vmlinuz' \
        --extra-args 'console=ttyS0,115200n8 serial'

トラブルシュート

ERROR Couldn't find kernel for install tree.

  • こんな感じのやつ
ERROR    Couldn't find kernel for install tree.
Domain installation does not appear to have been successful.
  • 解決策
    • --locationオプションで指定している値にあるとおり `initrd=casper/initrd,kernel=casper/vmlinuz'を追記すると発生しなくなった
    • これが何のための設定なのかは未確認

ゲストOSのセットアップ

  • これは通常のセットアップと同じなので割愛

まとめ

  • 昔と色々状況が変わっているのか、「こんなんだっけ…?」となる部分が多かった
  • virt-installのオプションなど、よくわかっていないで使っているものもあるのでこれから学びながら遊んでいく

ヒアドキュメントで入力した文字列がコマンドとして実行されてしまう件を調査してみた

背景

業務でbashのヒアドキュメントを使う場面があったが、知らない挙動があったのでマニュアルを読んで理解する

知りたいこと

  • ヒアドキュメントの入力として書いたコマンドが文字列として解釈される場合とコマンドとして解釈される場合の違いについて
# 1パターン目
$ cat <<EOS
...
EOS

# 2パターン目
$ cat <<'EOS' # 終端語がquoted
...
EOS

bashのマニュアルを読む

原文

Bash Reference Manual

意訳

  • 以下の形式で書く
[n]<<[-]word
        here-document
delimiter
  • wordには変数、コマンド、算術計算、ファイル名展開は使えない
  • wordのいずれか部分がquotedの場合
    • delimiterはquoteを除外したwordとなる
    • here-documentの行は展開されない
  • wordがunquotedの場合
    • here-documentはパラメータの展開、コマンド、算術計算などが行われる
    • 改行のための\newlineは無視される
    • 「\」、「$」、「`」の文字を引用するには「\」を使用する必要がある

試す

wordがquoted

$ WORD=hoge cat <<"EOS"
> echo $WORD
> EOS # -> echo $WORD ( 変数が展開されずに出力される

wordがunquoted

$ WORD=hoge cat <<EOS
> echo $WORD
> EOS # -> echo hoge ( 変数が展開されて出力される

レガシーシステムと向き合う

この記事で扱うこと

  • 自身が所属しているチームが開発・運用しているレガシーシステムに我慢ができなくなり、作り直しはできずともちょっとだけマシにするためにやったことのメモ
  • 既に対応実施から数年たったが「以前のほうがよかった」という声は一切ないので、取り組みは成功だったと思っている

実施前の状態について

システムについて

開発するための環境

コーディング環境

秘伝のVM運用

  • 会社から支給されたPC上にチームで管理してるVMイメージからVMを作成する

  • DBのインスタンスVM上に構築済みの状態

    • テーブルは作成済みで、データも投入済み(過去に使用したゴミデータも含まれる)
      • DBに更新があった場合は誰かが更新を適用したVMイメージを作成してチーム内に配布

コーディング規約

  • 口伝のルールがあったらしいが、実質ないようなものだった

エディタ

  • 支給されたPC上に好きなエディタを入れてそれを使う人
    • ローカルのディレクトリをVM上にマウントして動かしたりしてた
  • VM上にインストールされたエディタを使う人

コーディング環境の問題点

  • VMの運用がめんどくさい
  • コードを書く人やレビューする人によって書きっぷりが少しずつ異なっていた
  • VMイメージを作った人が .vimrcをめっちゃいじる人だったので死ぬほど使いにくかった

テスト環境

動作確認

  • 画面の表示はVM上で確認
  • VMで確認できない部分を共用の開発環境サーバにデプロイして動かす
    • 外部システムとの連携とか

自動テスト

  • なし

テスト環境の問題点

  • ローカルのVM環境でのテストが信頼できない
    • その人の環境でだけ動くコードなどが発生する
  • 共用開発環境は他の誰かが使っているときは使えない
  • VM上に過去の作業によって生じたゴミデータが永遠に蓄積される
    • DBにゴミを貯めたままVMイメージが配布される
  • 簡単な関数処理の動作確認ですら共用開発環境で動かさないとミスに気付けない
  • 想定していない箇所への影響が検知できない

運用

デプロイ

作業の流れ

  1. 更新対象のファイルを手動でリストアップ
  2. リストアップしたファイルについて、既存ファイルをサーバ上でバックアップ
  3. 更新対象ファイルを置き換え
  4. 切り戻しの際はリストアップしたファイルをバックアップファイルで置き換え

デプロイの問題点

  • リストアップした一覧に漏れが生じる
  • 作業を全て手作業で実施するため、ミスや実施漏れが生じる
  • 前述の共用の開発環境へのデプロイや戻し作業も同様の方法で行うため、戻し作業にも漏れが生じる
    • 後続作業で想定外の事象が起きた時の切り分けが困難
  • ミス防止のため(?)、チームメンバー全員で作業の見守りをしていた
    • 5人で60~90分くらい作業してたので工数的も問題あり
    • それでもミスは頻発していたのでマジで無意味な時間だった

目的意識

やりたかったこと

  • デプロイ作業の簡易化

    • 作業工数の削減
    • ミスを削減したかった
  • 自動テスト

    • 簡単な処理の動作保証
  • コードフォーマットの自動化

  • 静的解析

    • 文法チェック
    • linterの導入

やらないようにしたかったこと

  • VMの配布
    • 自分が配布するのはめんどくさい
    • 他人が配布したものに置き換えるのもめんどくさい
  • 他人がカスタマイズしたvimでの作業
    • 単に使いにくくて不快だった

取り組みについて

前提

  • システム全ての作り直しはしない

    • そんな時間も金もない
    • 最低限として「数年以内に破綻しないための施策」を検討
  • 今までの開発と並行して実施

    • 「内部改善をしてるのでエンハンスできません」はダメ

ゴール

開発のための環境的な話

  • VM配布を廃止し、dockerを利用できるようにする
    • 共用dockerイメージの作成
      • 動作環境としての仕組みだけを持たせて、DBの変更による更新は発生させない
    • 共用dockerイメージを使用した最低限の動作確認方法の確立

運用的な話

  • CIによるチェックで最低限の品質を担保する

  • デプロイスクリプトを作成する

  • デッドコードの削除

    • どこからも呼び出されてない処理の削除

やったこと

dockerイメージの作成

perl用イメージ

  • perlを実行するための環境をもつイメージ
    • plenvで特定のバージョンのperlをインストール
    • 共用開発環境にインストールされているモジュールのインストール

DB用イメージ

docker-compose.yml

  • perlイメージのDBイメージからコンテナを起動し、お互いに疎通できる状態を作る

メリット

  • 全員が同じ環境で動作確認を行うことになるので、実施者依存の変なことが起きない
    • 一度イメージを作成しておけば、よっぽどのことがないと更新作業は発生しない
    • VM配布廃止、不快なvimも使わずに済む

デメリット

  • レガシーシステムをやってる人だとdockerに触れたことがない人が多いかも
    • 「dockerよくわからん」って人でも容易に使えるように手順を固める必要はありそう

CI環境の構築

内容の定義

  1. 変更されたファイルに対して、 perl -cwで文法チェックを実施
    • そもそも実行できないものをここで弾く
  2. 変更されたファイルに対して、 perlcriticでlint
    • 推奨されない記述はここで弾く
  3. 変更されたファイルに対して、 perltidyフォーマットチェック
    • 動かすことはできるけど書き方に問題があるものはここで弾く
  4. リポジトリ全体のテストコードの実行
    • 既存処理に影響を与える変更をした場合はここで検知する

"変更されたファイルに対して" について

  • リポジトリ全体にperl -cwperlcriticの処理を実行すると大量のファイルで問題が検知される
    • 一つ一つ直すとエンハンスが止まるため、前提に反する
    • →何らかの変更を加えた際、その人の書いたコードに問題がなくても既存コードのせいでエラーになることがある
      • 運が悪かったと思ってその人が修正を実施するというルールを定めて運用
  • perltidyだけは最初に全体に適用した
    • 同様に大量のファイルで問題が検知されるが比較的影響は少なくすぐに対応が可能だったため

perltidyについて

  • perltidyは自動的にフォーマットを整えてくれるツールだと思っている
    • a.pl を対象に実行すると元ファイルは a.pl.bakみたいな名前に置き換えられ、 a.plは整形された状態になる
      • a.plが整形された状態であれば、 a.pl.bakは生成されない
    • なのでCI上では *.bakが存在する場合にエラーとしている
    • → CIで整形したものをcommitすればいいのでは?という声もあった
      • 同チーム内で 「perltidyで整形したものが信頼できないから一通り目で確認したい」という声もあった
      • 「信頼できない」と言っているものを自動commitさせる意味がわからなかったため却下とした
        • コードを書いた人間が責任を持って整形した状態をcommitすることとした

テストについて

  • 今までテストコードなんて書いたことがないという人ばかりだったので、実際にサンプルとしてテストコードを作成して動かした
  • 全ての既存コードのテストをいきなり書くことは無理なので、「今日以降変更したところはテストを書きましょう」というルールを定めて運用
    • ただしどうしてもテストコードを書く手間が大きい部分(画面操作のテストとか)は無理してテストコードは書かず、共用環境などで打鍵テストを行うこととした

メリット

  • 共用開発環境を使う前にある程度確認ができるため、共用環境が長時間占有されることがなくなった
  • → 品質担保、共用環境の待ち行列の解消

デメリット

  • 既存コードのせいでエンハンス時に余計な手間がかかる
    • 最初に perl -cw/perlcriticを実行し、エラーを解消してから実装に着手すると比較的マシ

CI環境のDBセットアップについて

  • セットアップ用のスクリプトを作成した
    • DDLが格納されたディレクトリ配下から findコマンドで .sqlなファイルを探して順に実行する
      • 実行順序の制御はファイル名で縛る( 001_hogehoge.sql, 002_fugafuga.sql, ... みたいに)

メリット

  • DB更新があってもdockerイメージ自体の更新は不要

デメリット

  • CIを実行するたびにテーブルから作り直すので若干時間がかかる

デプロイスクリプトの作成

スクリプトの処理内容

  • gitリポジトリの特定のブランチの状態を常に正とし、そのブランチに含まれるコードで全てを置き換える

置き換え処理について

メリット

  • デプロイ/切り戻しの作業工数の削減
  • 共用開発環境に蓄積されたゴミファイルによる影響を排除

デメリット

  • とくになし

デッドコードの削除

不要コード候補の抽出

  • 全ての関数定義と関数呼び出し処理の抽出を行い、愚直に調査を行った。
    • ひたすら grepを駆使
  • どこからも呼び出されてなさそうな処理を不要コード候補としてリストアップ

不要コード候補の監視

  • 不要コード候補にログ出力処理を追加する
    • 「ここの処理は不要コード候補だったけど、○○から呼び出されたぜ」みたいな
  • この状態で一定期間運用を続けて、仕込んだログが出力されなければ不要コードと判断する

不要コードの削除

  • 不要コードが確定したらコードを削除する
    • もしかしたら監視期間中に偶々呼び出されなかっただけで、実際には不要ではないコードを消してしまう可能性もあるので、切り戻し準備はしておく
    • そういうリスクがあることを理解し、過剰に恐れず対応を進めるしかない

繰り返し

  • 不要コードを削除することにより、再度不要コード候補が発生する可能性がある
  • 定期的に削除と抽出を繰り返していく必要がある

まとめ

  • 対応を実施してから3年くらい経つが問題は発生していないし、むしろテストを書いてたおかげでトラブルを事前に防ぐことができたこともある
  • とはいえ「やって!」と言ってやってくれる人なんていないと思うので、最初のうちは自らが行動を起こしていくしかない。具体的には以下のようなことをする。
    • 取り組みを理解してくれる味方をつくる
    • dockerイメージやCIの設定の作成
    • テストコードのサンプル作成
      • 既存コードのめっっっちゃ簡単な処理のテストでOK
    • perl -cwperlcriticでエラー対応方法をまとめる
      • 他の担当者の対応ハードルを下げる
  • 継続してもらうためにはできるだけめんどくさい手順を省くとよいかも
    • それ用のスクリプトを作っておくとか、タスクランナー的なツールを使うとか
    • 我々はMakefileにいろんな処理を定義してそれを使っている
      • make formatとか make testとかで必要な処理を自動化
  • 全てを作り直すことはできなくても最低限守るべきところを守ることで、作り直しができるようになるまでの延命は可能になると思う

参考

metacpan.org

metacpan.org

nextjs + react-chartjs-2 でウォーターフォールチャートを描く

やりたいこと

環境

  • "next": "13.4.9",
  • "chart.js": "^4.3.0",
  • "react-chartjs-2": "^5.2.0",

今回触れないこと

  • react / nextjs の話
  • tailwindcss の話
  • chart.js の話
  • TypeScriptの話(勉強中なので許して…)

実装

WaterfallChartのコンポーネントを作成

方針

  • 普通にBarChartを描くコードを作り、dataをいじるだけ

準備

$ npx create-next-app # nextjsアプリケーションの雛形を作成
$ cd <dir> # プロジェクトのディレクトリに移動

$ npm install --save chart.js react-chartjs-2 # 必要なパッケージのインストール

$ npm run dev # 起動

WaterfallChart コンポーネントの作成

まずは普通の棒グラフ

  • src/app/WaterfallChart.tsxを作成
"use client";
import { Bar } from "react-chartjs-2";
import {
  Chart as ChartJS,
  BarElement,
  CategoryScale,
  LinearScale,
  Title,
  Tooltip,
  Legend,
} from "chart.js";

ChartJS.register(
  CategoryScale,
  LinearScale,
  BarElement,
  Title,
  Tooltip,
  Legend
);

const options = {
  plugins: {
    title: {
      display: false,
    },
  },
  responsive: true,
  interaction: {
    mode: "index" as const,
    intersect: false,
  },
  scales: {
    x: {
      stacked: true,
    },
    y: {
      stacked: true,
    },
  },
};

const data = {
  labels: ["期初", "1Q末", "2Q末", "3Q末", "期末"],
  datasets: [
    {
      label: "在庫数",
      data: [100, 300, 200, 50, 200], // 数値は適当です。「在庫数がそんな遷移するわけねーだろ」などの指摘はしない
    },
  ],
};

export const WaterfallChart = () => {
  return <Bar options={options} data={data} />;
};
  • src/app/page.tsxを変更
    • 自動生成されたもののmain要素内を削除
      • tailwindcss関連は今回は変更していない
    • 作成したWaterfallChartを埋め込む
import { WaterfallChart } from "./WaterfallChart";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <WaterfallChart />
    </main>
  );
}
  • 結果表示

棒グラフの表示確認

WaterfallChart形式にする

  • data.datasets[0].data を number[] から (number|number[])[]に変更する
--- a/src/app/WaterfallChart.tsx
+++ b/src/app/WaterfallChart.tsx
@@ -41,11 +41,18 @@ const options = {
 };
 
 const data = {
-  labels: ["期初", "1Q末", "2Q末", "3Q末", "期末"],
+  labels: ["期初", "1Q", "2Q", "3Q", "4Q", "期末"],
   datasets: [
     {
       label: "在庫数",
-      data: [100, 300, 200, 50, 200],
+      data: [
+        100, // number型の要素は通常の棒グラフとして描画
+        [100, 300], // number[]型の要素は0番目の要素~1番目の要素の区間のみをグラフに描画
+        [300, 200],
+        [200, 50],
+        [50, 200],
+        200,
+      ],
     },
   ],
 };
  • 結果表示

ウォーターフォールチャートの表示

増減による色の変更

  • 増加した期間は青、減少した期間は赤でグラフを表示する
diff --git a/src/app/WaterfallChart.tsx b/src/app/WaterfallChart.tsx
index df2f7e1..38f517e 100644
--- a/src/app/WaterfallChart.tsx
+++ b/src/app/WaterfallChart.tsx
@@ -53,10 +53,26 @@ const data = {
         [50, 200],
         200,
       ],
+      backgroundColor: backgroundColor(),
     },
   ],
 };
 
+function backgroundColor() { // datasets.dataの各要素の状態によって色を分岐
+  return (ctx: any) => {
+    if (!ctx.parsed._custom) { // number型要素の場合は黄色で表示
+      return "rgba(255, 255, 102, 0.5)";
+    }
+
+    const start = ctx.parsed._custom.start;
+    const end = ctx.parsed._custom.end;
+
+    return end >= start
+      ? "rgba(102, 178, 255, 0.5)" // 増加
+      : "rgba(255, 102, 178, 0.5)"; // 減少
+  };
+}
+
 export const WaterfallChart = () => {
   return <Bar options={options} data={data} />;
 };
  • 結果表示

色付きウォーターフォールチャート

参考

react-chartjs-2.js.org

www.youtube.com