RORK LABEN
BUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要ですBUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要です
記事一覧/アプリ開発
アプリ開発/2026-08-16中級

グラフが壊れて見えるのは、データが多いときではなく最初の数日でした

記録アプリの折れ線グラフが、データの少ない最初の数日だけ線が消える。原因はY軸の幅が0になり座標がNaNになることでした。実測した挙動と、描画前に置く小さな正規化層を紹介します。

Rork539Expo175React Native227データ可視化4個人開発198

記録の推移を見せる画面を追加して、手元では問題なく動いていました。ところが公開してすぐ、「グラフのところが真っ白です」という連絡が届きます。

自分の端末では再現しません。私のデータは何週間分も溜まっていたからです。相手はインストールしたその日の方でした。個人開発では、いちばん検証に使う端末がいちばん実態から遠い、ということがよく起こります。

データが1件しかない状態を作って開いた瞬間、線が消えました。原因はグラフライブラリではなく、その手前で私が書いていた目盛りの計算にありました。

値が動かないとき、目盛りの幅は 0 になります

折れ線グラフを描くとき、たいていは最小値と最大値から縦軸の幅を決めます。私も素直にそう書いていました。

const H = 200; // 描画領域の高さ(px)
 
const naive = (values) => {
  const min = Math.min(...values);
  const max = Math.max(...values);
  return { min, max, span: max - min, pxPerUnit: H / (max - min) };
};

この関数に、実際に起こりうる入力を通してみます。手元の Node で実行した結果がこちらです。

1点        span= 0 pxPerUnit= Infinity isFinite= false
同値3日    span= 0 pxPerUnit= Infinity isFinite= false
全0        span= 0 pxPerUnit= Infinity isFinite= false
通常7日分  span= 6 pxPerUnit= 33.333333333333336

値が7日分ばらついている通常のケースでは、1単位あたり 33.3px という妥当な倍率が出ます。

一方で、データが1点しかない場合、同じ値が続く場合、そして全部が 0 の場合は、いずれも幅が 0 になります。JavaScript では 0 での除算は例外を投げず、Infinity を返します。

つまりエラーは出ません。計算は静かに続きます。

0 で割った結果が、そのまま座標に流れます

Infinity を含んだ倍率は、次の掛け算でさらに厄介な値に変わります。同じく実行した結果です。

4 * Infinity = Infinity
0 * Infinity = NaN

値が 0 のとき、y = (value - min) * pxPerUnitNaN になります。

NaN が入った座標は、SVG でも Canvas でも「描かない」という扱いになります。線は引かれず、警告も出ません。コンポーネントは正常に返り、画面には枠だけが残ります。

私が最初に疑ったのはライブラリのバージョンでした。実際には自分で書いた4行の関数が原因で、切り分けに半日ほど使っています。数値計算が黙って通ってしまう箇所は、ログを仕込まないと見つかりません。

入力の状態span倍率画面に起きること
7日分の記録あり633.3px/単位期待どおりに描画される
記録が1件だけ0Infinity点も線も出ない
同じ値が3日続く0Infinity横一直線すら出ない
すべて 0 で補完0Infinity座標が NaN になり空白

欠測を 0 で埋めると、グラフが別のことを語り始めます

幅が 0 になる原因のひとつは、私自身が作っていました。記録のない日を 0 で埋めていたのです。

配列の長さを7日に揃えたいという実装上の都合でしたが、これは表示の意味を変えてしまいます。「その日は記録しなかった」と「その日は 0 だった」は、読む人にとって別の事実です。

体重や睡眠時間のように 0 があり得ない指標では、谷が突き刺さったグラフになります。回数を数える指標なら 0 は正しいこともあります。

私はこの判断を、指標ごとに1行のメモとして残すようにしました。あとから自分が迷わないためです。

指標の種類記録のない日の扱い理由
体重・体温・睡眠時間線を途切れさせる0 は実在しない値のため
歩数・回数・保存件数0 として扱う0 回という事実が成立するため
気分・評価などの主観値線を途切れさせる未入力を中間値と誤読させないため

描く前に整える小さな層をひとつ置きます

対処はグラフライブラリの設定ではなく、その手前に置いた関数で行っています。ライブラリを乗り換えても残る層にしておきたかったからです。

やっていることは2つだけです。幅に下限を与えることと、描くに足りない入力を弾くことです。

type Point = { x: number; y: number | null };
 
type Axis = { lo: number; hi: number; span: number; pxPerUnit: number };
 
export const buildAxis = (
  values: number[],
  height = 200,
  minSpan = 1,
): Axis | null => {
  const finite = values.filter((v) => Number.isFinite(v));
  if (finite.length === 0) return null;
 
  let lo = Math.min(...finite);
  let hi = Math.max(...finite);
 
  if (hi - lo < minSpan) {
    const center = (hi + lo) / 2;
    lo = center - minSpan / 2;
    hi = center + minSpan / 2;
  }
 
  return { lo, hi, span: hi - lo, pxPerUnit: height / (hi - lo) };
};

minSpan を通したあとの実測値です。幅 0 だった3ケースが、すべて有限の倍率に変わりました。

padded 1点     {"lo":3.5,"hi":4.5,"span":1,"pxPerUnit":200}
padded 同値3日 {"lo":4.5,"hi":5.5,"span":1,"pxPerUnit":200}
padded 全0     {"lo":-0.5,"hi":0.5,"span":1,"pxPerUnit":200}
padded 通常    {"lo":3,"hi":9,"span":6,"pxPerUnit":33.333333333333336}

値が1つでも、中心に置かれた線が描かれます。全部が 0 でも、下に少し余白を取った状態で 0 の線が引かれます。

minSpan の値は指標の単位に合わせます。歩数なら 100、睡眠時間なら 1 といった具合です。私は手元の複数のアプリでこの関数を使い回していますが、単位だけはどうしても固定できませんでした。ここを固定値にすると、単位の違う画面で目盛りが不自然になります。

null を返しているのは、有限の値が1つもないときです。この場合はグラフを描かず、呼び出し側で別の表示に切り替えます。

この関数は描画を一切 import しない素の module に置いています。コンポーネントを組み立てなくても検証できるからです。1点・同値・空配列の3つを確かめる assertion なら1分で書けますし、あとから下限の規則を触ったときに気づけます。

もうひとつ効いたのは、リリースビルドで buildAxisnull を返したときに警告を残すことでした。静かに壊れる数値計算はクラッシュレポートに出てきません。自分で信号を出しておかないと、空白のグラフはどなたかがわざわざ連絡をくださるまで気づけないままになります。

数が足りないときは、グラフを出さない判断もします

技術的に描けることと、見せる価値があることは別でした。

記録が1件だけの折れ線は、線がなく点が1つあるだけです。これを見せられた方は、自分の記録がどうなっているのか何も分かりません。むしろ不具合に見えます。

私は下限を3件に置いています。それ未満のときは、グラフの代わりに「あと2日分でグラフが出ます」という案内を出しています。次に開く理由になるからです。

const MIN_POINTS = 3;
 
export const TrendSection = ({ points }: { points: Point[] }) => {
  const values = points.map((p) => p.y).filter((v): v is number => v !== null);
  const axis = buildAxis(values, 200, 1);
 
  if (values.length < MIN_POINTS || axis === null) {
    return <TrendPlaceholder remaining={MIN_POINTS - values.length} />;
  }
 
  return <TrendChart points={points} axis={axis} />;
};

この分岐は、空の状態をどう見せるかという設計と地続きです。読み込み中・記録なし・件数不足は見た目が似ていて、まとめて扱うと読者が混乱します。それぞれの出し分けはRork アプリの空状態を設計しきるで扱っています。棒グラフ側の実装で複数系列を並べたい場合は、react-native-chart-kit の BarChart で複数データセットを横並び表示する実装パターンが参考になります。

次に手を動かすなら、テストデータを1件・同値3件・全0の3パターンだけ用意して、自分のグラフ画面を開いてみてください。半日を溶かす前に、その3回で分かります。

記録そのものをどう型で守るかまで踏み込みたい場合は、複数アプリで共有するイベント層の設計をRork製6アプリで分析イベントを型で守るにまとめてあります。

最後までお読みいただきありがとうございました。同じ空白のグラフで立ち止まっている方の手がかりになれば嬉しいです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-06-27
「本当に削除しますか?」を出す前に — 取り消せる削除という選択肢
リストの項目を消すたびに確認ダイアログを出すと、ユーザーは反射的にOKを押すようになります。Rork(Expo)アプリで「取り消せる削除」を実装し、確認を出すべき場面と出さない場面を切り分ける設計ノートです。
アプリ開発2026-08-06
壁紙の上の文字が読めるかを、端末ではなく取り込み時に決める — 判定指標を4つ測り比べた記録
壁紙の上に置くステータスバーや見出しが読めるかを、端末での画像解析ではなく取り込み時のメタデータで決める設計です。240枚のコーパスで判定指標を4つ実測し、縮小画像での判定が何と一致するのかまで記録しました。
アプリ開発2026-07-14
壁紙を長押しして、プレビューと保存メニューを出す — Rork の Expo アプリで context menu を組む
壁紙ギャラリーのカードを長押ししても何も起きない。iOS利用者はプレビュー付きのメニューを期待します。Pressableだけでは届かない理由から、zeegoでのネイティブcontext menu実装、スクロールとの競合解消、保存・共有アクション、Android向けの自前フォールバックまで、動くコードでまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →