12k
All articles

ブラウザでGeometric.jsを使った本格的なジオメトリ処理

Geometric.jsは、ブラウザのグラフや地図で使うポリゴン交差、境界 बॉक्स、点の内外判定、経路補間をSVGとCanvas向けに提供します。

OpenReplay Team
OpenReplay Team
ブラウザでGeometric.jsを使った本格的なジオメトリ処理

Geometric.jsは2Dジオメトリライブラリで、すべてのプリミティブがプレーンなJavaScript配列として表現されます。点は[x, y]、直線は[[x, y], [x, y]]、ポリゴンは点の配列です。つまり、計算に使う値がそのままJSONにシリアライズでき、SVGやCanvasに描画できます。

インタラクティブなチャートやマップを構築した経験があれば、ちょっとしたジオメトリ演算が必要なたびに数学を一から実装し直さなければならない煩わしさを感じたことがあるはずです。D3チャートにレイキャスティングによるポイント・イン・ポリゴン判定や境界ボックスの計算をコピー&ペーストしてきたなら、Geometric.jsがそれらのコードスニペットを、すでに持っている配列に対して直接動作する、名前付きでテスト済みの関数に置き換えてくれます。本記事では、主要な3つの機能(ポリゴンの交差、境界ボックス、補間)を解説した後、自分でトリゴノメトリを実装するよりもこのライブラリを使うべき2つのユースケースを紹介します。

重要なポイント

  • Geometric.jsでは、点を[x, y]、直線を2点、ポリゴンを点の配列で表現します。インスタンス化するクラスはなく、SVGやCanvasに描画する前に変換する必要もありません。
  • pointInPolygon(point, polygon)はレイキャスティングを使ってbooleanを返します。外部のジオメトリ計算なしに、1回の呼び出しでCanvasのヒットテストが実現できます。
  • polygonBounds(polygon)[topLeft, bottomRight]を2つの[x, y]点として返します。頂点が3つ未満の場合はnullを返します。任意の図形の周囲にツールチップボックスを配置するのに十分な情報です。
  • ジオメトリを返すブール演算(polygonIntersectionpolygonUnionpolygonDifferencepolygonXor)はgeometric v3以降が必要です。polygonIntersectsPolygon述語はv2から利用可能です。
  • バージョン3.x以降、geometricには独自のTypeScript宣言が同梱されているため、geometricをインストールするだけで済み、@types/geometricは不要です。

プリミティブはプレーンな配列——それがすべての魅力

Geometric.jsが使いやすい理由は、独自のデータ構造を導入しないことにあります。点は[x, y]配列、直線は2点の配列、ポリゴンは頂点の配列です。インスタンス化するカスタムクラスも、習得すべき特殊なデータ構造もありません。つまり、渡す値がそのままシリアライズ、検査、描画に使える値です。出力が配列であるため、アダプタコードなしでD3、SVGのpoints属性、またはCanvasのctx.lineToループに直接渡せます。

import { polygonRegular } from "geometric";

const pentagon = polygonRegular(5, 10000, [150, 150]);
// pentagonは[[x, y], [x, y], ...] — そのまま描画できます:

// SVG
polygon.setAttribute("points", pentagon.map(p => p.join(",")).join(" "));

// Canvas
ctx.beginPath();
pentagon.forEach(([x, y], i) => (i ? ctx.lineTo(x, y) : ctx.moveTo(x, y)));
ctx.closePath();

この一連の流れ(計算、シリアライズ、レンダリングをすべて同じ配列で行う)こそが、READMEが「ジオメトリをシンプルに保つ」と表現している意味です。

インストールとインポート

geometricはnpm、pnpm、またはyarnからインストールできます。ESMファーストのパッケージとして提供されており、CommonJSサポートとTypeScript宣言が同梱されています。npmの最新バージョンは3.0.9(2026年7月)です。

npm i geometric
# または: pnpm add geometric
# または: yarn add geometric
import { pointInPolygon, polygonBounds } from "geometric"; // ESM
const geometric = require("geometric");                     // CommonJS

セットアップに関して2点注意があります。まず、TypeScript宣言はソースから生成されパッケージと共に公開されているため、@types/geometricインストールしないでください。そのDefinitelyTypedパッケージは2.5.3で更新が止まっており、v3では冗長です。次に、以下で説明するジオメトリを返すブール演算にはgeometric@^3が必要です。v2ではこれらはundefinedを返します。

ポリゴンの交差とブール演算

重なり判定には、polygonIntersection(a, b)が共有領域を新しいポリゴンとして返し、polygonIntersectsPolygon(a, b)がbooleanを返します。述語は軽量なチェックに使い、ジオメトリを返す演算は実際に描画できる図形が必要な場合に使います。

import { polygonIntersection, polygonIntersectsPolygon } from "geometric";

const a = [[0, 0], [4, 0], [4, 4], [0, 4]];
const b = [[2, 2], [6, 2], [6, 6], [2, 6]];

polygonIntersectsPolygon(a, b); // true — boolean述語 (v2以降)
polygonIntersection(a, b);      // 重複領域を新しいポリゴンとして返す (v3以降)

ブール演算のフルセットはpolygonIntersectionpolygonUnionpolygonDifferencepolygonXorで、それぞれ点の配列を返します。4つすべてがv3での追加機能です。重なりではなく包含関係を調べる場合は、polygonInPolygon(polygonA, polygonB)がbooleanを返します。最初のポリゴンが2番目のポリゴンの内側に完全に収まっているかどうかを判定し、境界上の点は内側として扱います。

ポリゴンの境界ボックスを取得するには?

polygonBounds(polygon)[topLeft, bottomRight]を2つの[x, y]点として返します。頂点が3つ未満の場合はnullを返します。1回の呼び出しで任意の図形の周囲にツールチップやラベルボックスを配置するのに十分です。無効な値(nullundefinedNaNInfinity)を持つ点は無視されるため、データに欠損があってもボックスが壊れることはありません。

import { polygonBounds } from "geometric";

const region = [[12, 8], [40, 20], [30, 44], [6, 30]];
const [topLeft, bottomRight] = polygonBounds(region);
// topLeft = [6, 8], bottomRight = [40, 44]

const width = bottomRight[0] - topLeft[0];   // 34
const height = bottomRight[1] - topLeft[1];  // 36
// この幅と高さでtopLeftに<rect>またはツールチップを配置する

これはデータビジュアライゼーションでよくあるオーバーレイ処理です。マップやチャート上の任意の領域に対して、すべての頂点のmin/maxを手動で再計算することなく、ラベルフレームを配置できます。

補間:パスや外周に沿ったアニメーション

lineInterpolate(line)polygonInterpolate(polygon)は、[0, 1]の範囲のtを引数に取る補間関数を返します。これはパスに沿ってまたは外周に沿ってマーカーをアニメーションさせるのにまさに必要な機能です。現在の3.xビルドではclampのデフォルトはtrueで、出力をセグメント内に制限します。意図を明確にするために明示的に渡すことを推奨します。

import { lineInterpolate } from "geometric";

const path = [[0, 0], [100, 50]];
const at = lineInterpolate(path, true); // clamp = true

at(0);   // [0, 0]
at(0.5); // [50, 25]
at(1);   // [100, 50]

trequestAnimationFrameまたはD3トランジションで駆動すれば、マーカーが直線に沿ってスムーズに移動します。polygonInterpolateは閉じた外周に沿って同じことを行い、輪郭をトレースしたり図形の縁に沿って点を動かしたりするのに便利です。

点がポリゴンの内側にあるかどうかを判定するには?

クリックが領域内に落ちたかどうかを判定するには、pointInPolygon(point, polygon)がレイキャスティングを使ってbooleanを返します。外部のジオメトリ計算は不要です。これはCanvasの古典的なヒットテストであり、手書きの辺交差ロジックを数十行書く代わりに、テスト済みの1回の呼び出しで済みます。

import { pointInPolygon, pointOnPolygon } from "geometric";

const region = [[0, 0], [100, 0], [100, 100], [0, 100]];

pointInPolygon([50, 50], region);  // true
pointInPolygon([150, 50], region); // false

内部判定には予測可能な弱点が1つあります。辺の真上にクリックが落ちた場合です。ドラッグ選択やマップ領域ピッカーのセッションリプレイでは、このような失敗パターンがよく見られます。「なぜ選択が反応しなかったのか?」というバグは、ユニットテストでは見逃されますが、インタラクションを実際に見ると明らかです。適切な対処法はpointOnPolygon(point, polygon, epsilon)を使うことです。これは境界をオプションのイプシロン許容値(例:1e-6)でテストし、内部チェックだけでは対応できない辺上の曖昧なケースを処理します。

機能関数戻り値最低バージョン
点の領域内判定pointInPolygonboolean(レイキャスティング)v2
境界判定pointOnPolygonboolean(イプシロン)v2
境界ボックスpolygonBounds[topLeft, bottomRight]またはnullv2
重複図形polygonIntersectionポリゴンv3
重複述語polygonIntersectsPolygonbooleanv2
パス補間lineInterpolate関数(t) => [x, y]v2

チャート、マップ、またはCanvas UIで複数のジオメトリ演算が必要になった瞬間に、Geometric.jsの出番です。配列入力・配列出力のモデルにより、すべての結果が次の呼び出しやレンダラーに直接渡せます。geometricをインストールし、必要な2〜3つの関数をインポートして、Stack Overflowから拾ってきたトリゴノメトリのコードを削除しましょう。4つ目の演算が必要になったときは、APIリファレンスで完全な関数一覧を確認できます。

よくある質問

Geometric.jsにおけるpolygonIntersectionとpolygonIntersectsPolygonの違いは何ですか?

polygonIntersectsPolygon(a, b)は2つのポリゴンが重なっているかどうかをbooleanで返します。一方、polygonIntersection(a, b)は共有領域そのものを描画可能な新しいポリゴン配列として返します。単純なyes/noのヒットチェックには述語を使い、実際の重複図形が必要な場合はジオメトリを返す演算を使ってください。述語はv2から利用可能ですが、polygonIntersectionはgeometric v3以降が必要です。

Geometric.jsをTypeScriptで使うために@types/geometricをインストールする必要がありますか?

いいえ。バージョン3.x以降、geometricにはソースから生成されたTypeScript宣言がパッケージと共に同梱されているため、geometricをインストールするだけでエディタの自動補完と型チェックが利用できます。別途DefinitelyTypedパッケージの@types/geometricは2023年11月の2.5.3で更新が止まっており、v3では冗長です。インストールすると、正確な同梱型が古い型で上書きされる可能性があります。

点がポリゴンの辺上に正確に落ちた場合、Geometric.jsは何を返しますか?

pointInPolygonはレイキャスティングで内部をテストするため、辺上に正確に落ちた点は曖昧として扱われ、falseを返す場合があります。これはドラッグ選択やマップ領域UIでクリックが反応しないというバグとして現れます。境界ケースにはpointOnPolygon(point, polygon, epsilon)を使ってください。オプションのイプシロン許容値(例:1e-6)で、辺上とみなす距離を制御できます。

Geometric.jsの出力は変換なしにSVGやCanvasに直接描画できますか?

はい。すべてのプリミティブがクラスインスタンスではなくプレーンなJavaScript配列であるため、polygonRegularやpolygonIntersectionなどの関数が返す配列は、アダプタコードなしでSVGのpoints属性やCanvasのctx.lineTo ループに直接渡せます。計算に使う配列がそのままJSONにシリアライズ、D3への受け渡し、レンダリングに使える値です。これがこのライブラリの核心的な設計思想です。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.