@uoa-css-lab/duckscatter 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.github/dependabot.yml +42 -0
  2. package/.github/workflows/ci.yaml +111 -0
  3. package/.github/workflows/release.yml +55 -0
  4. package/.prettierrc +11 -0
  5. package/LICENSE +22 -0
  6. package/README.md +250 -0
  7. package/dist/data/data-layer.d.ts +169 -0
  8. package/dist/data/data-layer.js +402 -0
  9. package/dist/data/index.d.ts +2 -0
  10. package/dist/data/index.js +2 -0
  11. package/dist/data/repository.d.ts +48 -0
  12. package/dist/data/repository.js +109 -0
  13. package/dist/diagnostics.d.ts +27 -0
  14. package/dist/diagnostics.js +71 -0
  15. package/dist/errors.d.ts +22 -0
  16. package/dist/errors.js +58 -0
  17. package/dist/event-emitter.d.ts +62 -0
  18. package/dist/event-emitter.js +82 -0
  19. package/dist/index.d.ts +12 -0
  20. package/dist/index.js +13 -0
  21. package/dist/renderer/gpu-layer.d.ts +204 -0
  22. package/dist/renderer/gpu-layer.js +611 -0
  23. package/dist/renderer/index.d.ts +3 -0
  24. package/dist/renderer/index.js +3 -0
  25. package/dist/renderer/shaders.d.ts +13 -0
  26. package/dist/renderer/shaders.js +216 -0
  27. package/dist/renderer/webgpu-context.d.ts +20 -0
  28. package/dist/renderer/webgpu-context.js +88 -0
  29. package/dist/scatter-plot.d.ts +210 -0
  30. package/dist/scatter-plot.js +450 -0
  31. package/dist/types.d.ts +171 -0
  32. package/dist/types.js +1 -0
  33. package/dist/ui/index.d.ts +1 -0
  34. package/dist/ui/index.js +1 -0
  35. package/dist/ui/label-layer.d.ts +176 -0
  36. package/dist/ui/label-layer.js +488 -0
  37. package/docs/image.png +0 -0
  38. package/eslint.config.js +72 -0
  39. package/examples/next/README.md +36 -0
  40. package/examples/next/app/components/ColorExpressionInput.tsx +41 -0
  41. package/examples/next/app/components/ControlPanel.tsx +30 -0
  42. package/examples/next/app/components/HoverControlPanel.tsx +69 -0
  43. package/examples/next/app/components/HoverInfoDisplay.tsx +40 -0
  44. package/examples/next/app/components/LabelFilterInput.tsx +46 -0
  45. package/examples/next/app/components/LabelList.tsx +106 -0
  46. package/examples/next/app/components/PointAlphaSlider.tsx +21 -0
  47. package/examples/next/app/components/PointLimitSlider.tsx +23 -0
  48. package/examples/next/app/components/PointList.tsx +105 -0
  49. package/examples/next/app/components/PointSizeScaleSlider.tsx +22 -0
  50. package/examples/next/app/components/ScatterPlotCanvas.tsx +150 -0
  51. package/examples/next/app/components/SearchBox.tsx +46 -0
  52. package/examples/next/app/components/Slider.tsx +76 -0
  53. package/examples/next/app/components/StatsDisplay.tsx +15 -0
  54. package/examples/next/app/components/TimeFilterSlider.tsx +169 -0
  55. package/examples/next/app/context/ScatterPlotContext.tsx +402 -0
  56. package/examples/next/app/favicon.ico +0 -0
  57. package/examples/next/app/globals.css +23 -0
  58. package/examples/next/app/layout.tsx +35 -0
  59. package/examples/next/app/page.tsx +15 -0
  60. package/examples/next/eslint.config.mjs +18 -0
  61. package/examples/next/next.config.ts +7 -0
  62. package/examples/next/package-lock.json +6572 -0
  63. package/examples/next/package.json +27 -0
  64. package/examples/next/postcss.config.mjs +7 -0
  65. package/examples/next/scripts/generate_labels.py +167 -0
  66. package/examples/next/tsconfig.json +34 -0
  67. package/package.json +43 -0
  68. package/src/data/data-layer.ts +515 -0
  69. package/src/data/index.ts +2 -0
  70. package/src/data/repository.ts +146 -0
  71. package/src/diagnostics.ts +108 -0
  72. package/src/errors.ts +69 -0
  73. package/src/event-emitter.ts +88 -0
  74. package/src/index.ts +40 -0
  75. package/src/renderer/gpu-layer.ts +757 -0
  76. package/src/renderer/index.ts +3 -0
  77. package/src/renderer/shaders.ts +219 -0
  78. package/src/renderer/webgpu-context.ts +98 -0
  79. package/src/scatter-plot.ts +533 -0
  80. package/src/types.ts +218 -0
  81. package/src/ui/index.ts +1 -0
  82. package/src/ui/label-layer.ts +648 -0
  83. package/tsconfig.json +19 -0
package/dist/errors.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * エラーコードからカテゴリへのマッピング
3
+ */
4
+ const categoryMap = {
5
+ WEBGPU_NOT_SUPPORTED: 'webgpu',
6
+ GPU_ADAPTER_NOT_AVAILABLE: 'webgpu',
7
+ GPU_DEVICE_FAILED: 'webgpu',
8
+ WEBGPU_CONTEXT_FAILED: 'webgpu',
9
+ DATA_LAYER_NOT_INITIALIZED: 'data',
10
+ PARQUET_LOAD_FAILED: 'data',
11
+ QUERY_FAILED: 'query',
12
+ LABEL_FETCH_FAILED: 'label',
13
+ LABEL_PARSE_FAILED: 'label',
14
+ NETWORK_ERROR: 'network',
15
+ };
16
+ /**
17
+ * エラーコードから重大度レベルへのマッピング
18
+ */
19
+ const severityMap = {
20
+ WEBGPU_NOT_SUPPORTED: 'fatal',
21
+ GPU_ADAPTER_NOT_AVAILABLE: 'fatal',
22
+ GPU_DEVICE_FAILED: 'fatal',
23
+ WEBGPU_CONTEXT_FAILED: 'fatal',
24
+ DATA_LAYER_NOT_INITIALIZED: 'fatal',
25
+ PARQUET_LOAD_FAILED: 'fatal',
26
+ QUERY_FAILED: 'error',
27
+ NETWORK_ERROR: 'error',
28
+ LABEL_FETCH_FAILED: 'warning',
29
+ LABEL_PARSE_FAILED: 'warning',
30
+ };
31
+ /**
32
+ * 指定されたコードとメッセージでScatterPlotErrorオブジェクトを作成する
33
+ *
34
+ * @param code エラーコード
35
+ * @param message 人間が読めるエラーメッセージ
36
+ * @param options 追加オプション
37
+ * @returns ScatterPlotErrorオブジェクト
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const error = createError(
42
+ * 'WEBGPU_NOT_SUPPORTED',
43
+ * 'WebGPU is not supported in this browser',
44
+ * { context: { userAgent: navigator.userAgent } }
45
+ * );
46
+ * ```
47
+ */
48
+ export function createError(code, message, options) {
49
+ return {
50
+ code,
51
+ category: categoryMap[code],
52
+ severity: severityMap[code],
53
+ message,
54
+ cause: options?.cause,
55
+ context: options?.context,
56
+ timestamp: Date.now(),
57
+ };
58
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * イベントハンドラー関数の型
3
+ */
4
+ export type EventHandler<T = unknown> = (event: T) => void;
5
+ /**
6
+ * ブラウザ環境向けの軽量なEventEmitter実装
7
+ * 外部依存なしで型安全なイベントハンドリングを提供する
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * interface MyEvents {
12
+ * data: { value: number };
13
+ * error: Error;
14
+ * }
15
+ *
16
+ * class MyClass extends EventEmitter<MyEvents> {
17
+ * doSomething() {
18
+ * this.emit('data', { value: 42 });
19
+ * }
20
+ * }
21
+ *
22
+ * const instance = new MyClass();
23
+ * instance.on('data', (event) => console.log(event.value));
24
+ * ```
25
+ */
26
+ export declare class EventEmitter<T extends {
27
+ [K in keyof T]: unknown;
28
+ }> {
29
+ /** イベント名からハンドラーのセットへのマップ */
30
+ private listeners;
31
+ /**
32
+ * 指定されたイベントに対してハンドラーを登録する
33
+ *
34
+ * @param event リッスンするイベント名
35
+ * @param handler イベント発生時に呼び出されるハンドラー関数
36
+ * @returns メソッドチェーン用のthisインスタンス
37
+ */
38
+ on<K extends keyof T>(event: K, handler: EventHandler<T[K]>): this;
39
+ /**
40
+ * 指定されたイベントからハンドラーを削除する
41
+ *
42
+ * @param event イベント名
43
+ * @param handler 削除するハンドラー関数
44
+ * @returns メソッドチェーン用のthisインスタンス
45
+ */
46
+ off<K extends keyof T>(event: K, handler: EventHandler<T[K]>): this;
47
+ /**
48
+ * 登録されたすべてのハンドラーにイベントを発行する
49
+ *
50
+ * @param event 発行するイベント名
51
+ * @param data ハンドラーに渡すイベントデータ
52
+ * @returns リスナーがあればtrue、なければfalse
53
+ */
54
+ protected emit<K extends keyof T>(event: K, data: T[K]): boolean;
55
+ /**
56
+ * 指定されたイベント、またはすべてのイベントのリスナーを削除する
57
+ *
58
+ * @param event イベント名(省略時はすべてのリスナーを削除)
59
+ * @returns メソッドチェーン用のthisインスタンス
60
+ */
61
+ removeAllListeners(event?: keyof T): this;
62
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * ブラウザ環境向けの軽量なEventEmitter実装
3
+ * 外部依存なしで型安全なイベントハンドリングを提供する
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * interface MyEvents {
8
+ * data: { value: number };
9
+ * error: Error;
10
+ * }
11
+ *
12
+ * class MyClass extends EventEmitter<MyEvents> {
13
+ * doSomething() {
14
+ * this.emit('data', { value: 42 });
15
+ * }
16
+ * }
17
+ *
18
+ * const instance = new MyClass();
19
+ * instance.on('data', (event) => console.log(event.value));
20
+ * ```
21
+ */
22
+ export class EventEmitter {
23
+ constructor() {
24
+ /** イベント名からハンドラーのセットへのマップ */
25
+ this.listeners = new Map();
26
+ }
27
+ /**
28
+ * 指定されたイベントに対してハンドラーを登録する
29
+ *
30
+ * @param event リッスンするイベント名
31
+ * @param handler イベント発生時に呼び出されるハンドラー関数
32
+ * @returns メソッドチェーン用のthisインスタンス
33
+ */
34
+ on(event, handler) {
35
+ if (!this.listeners.has(event)) {
36
+ this.listeners.set(event, new Set());
37
+ }
38
+ this.listeners.get(event).add(handler);
39
+ return this;
40
+ }
41
+ /**
42
+ * 指定されたイベントからハンドラーを削除する
43
+ *
44
+ * @param event イベント名
45
+ * @param handler 削除するハンドラー関数
46
+ * @returns メソッドチェーン用のthisインスタンス
47
+ */
48
+ off(event, handler) {
49
+ this.listeners.get(event)?.delete(handler);
50
+ return this;
51
+ }
52
+ /**
53
+ * 登録されたすべてのハンドラーにイベントを発行する
54
+ *
55
+ * @param event 発行するイベント名
56
+ * @param data ハンドラーに渡すイベントデータ
57
+ * @returns リスナーがあればtrue、なければfalse
58
+ */
59
+ emit(event, data) {
60
+ const handlers = this.listeners.get(event);
61
+ if (!handlers || handlers.size === 0) {
62
+ return false;
63
+ }
64
+ handlers.forEach((handler) => handler(data));
65
+ return true;
66
+ }
67
+ /**
68
+ * 指定されたイベント、またはすべてのイベントのリスナーを削除する
69
+ *
70
+ * @param event イベント名(省略時はすべてのリスナーを削除)
71
+ * @returns メソッドチェーン用のthisインスタンス
72
+ */
73
+ removeAllListeners(event) {
74
+ if (event) {
75
+ this.listeners.delete(event);
76
+ }
77
+ else {
78
+ this.listeners.clear();
79
+ }
80
+ return this;
81
+ }
82
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * duckscatter - WebGPUを使用して散布図を描画するTypeScriptライブラリ
3
+ */
4
+ export { ScatterPlot } from './scatter-plot.js';
5
+ export type { ColorRGBA, ScatterPlotOptions, Label, WhereCondition, NumericFilter, StringFilter, RawSqlFilter, NumericOperator, StringOperator, GpuWhereCondition, ErrorSeverity, ErrorCategory, ErrorCode, ScatterPlotError, ScatterPlotEventMap, PointId, LabelIdentifier, LabelHoverCallback, } from './types.js';
6
+ export { diagnoseWebGPU } from './diagnostics.js';
7
+ export type { WebGPUDiagnostics } from './diagnostics.js';
8
+ /**
9
+ * 現在の環境でWebGPUがサポートされているかを確認する
10
+ * @returns WebGPUがサポートされている場合はtrue、そうでない場合はfalse
11
+ */
12
+ export declare function isWebGPUSupported(): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * duckscatter - WebGPUを使用して散布図を描画するTypeScriptライブラリ
3
+ */
4
+ export { ScatterPlot } from './scatter-plot.js';
5
+ export { diagnoseWebGPU } from './diagnostics.js';
6
+ /**
7
+ * 現在の環境でWebGPUがサポートされているかを確認する
8
+ * @returns WebGPUがサポートされている場合はtrue、そうでない場合はfalse
9
+ */
10
+ export function isWebGPUSupported() {
11
+ // navigatorオブジェクトにgpuプロパティが存在するかをチェック
12
+ return 'gpu' in navigator;
13
+ }
@@ -0,0 +1,204 @@
1
+ import type { ColorRGBA } from '../types.js';
2
+ /**
3
+ * GPU用に処理されたポイントデータ
4
+ */
5
+ export interface AllPointsData {
6
+ /** インスタンスデータ (x, y, color, size) の Float32Array */
7
+ instanceData: Float32Array;
8
+ /** 全ポイント数 */
9
+ totalCount: number;
10
+ }
11
+ /**
12
+ * GpuLayerの設定オプション
13
+ */
14
+ export interface GpuLayerOptions {
15
+ /** 描画対象のHTMLCanvasElement */
16
+ canvas: HTMLCanvasElement;
17
+ /** 背景色(デフォルト: 透明な黒) */
18
+ backgroundColor?: ColorRGBA;
19
+ /** 表示可能なポイントの最大数(デフォルト: 5000000) */
20
+ visiblePointLimit?: number;
21
+ }
22
+ /**
23
+ * WebGPUレンダリングを担当するレイヤー
24
+ * 責務:
25
+ * - WebGPUコンテキストとパイプラインの管理
26
+ * - GPUバッファの作成と管理
27
+ * - コンピュートシェーダーによるLODフィルタリング
28
+ * - Indirect Drawingによるレンダリング
29
+ */
30
+ export declare class GpuLayer {
31
+ /** WebGPUコンテキスト */
32
+ private context;
33
+ /** 描画対象のキャンバス */
34
+ private readonly canvas;
35
+ /** レンダーパイプライン */
36
+ private renderPipeline;
37
+ /** フィルタリング用コンピュートパイプライン */
38
+ private filterPipeline;
39
+ /** Indirect Buffer更新用コンピュートパイプライン */
40
+ private updateIndirectPipeline;
41
+ /** クワッド頂点バッファ(stepMode: 'vertex') */
42
+ private quadVertexBuffer;
43
+ /** 全ポイントデータバッファ (Storage) */
44
+ private allPointsBuffer;
45
+ /** 可視ポイントインデックスバッファ (Storage) */
46
+ private visibleIndicesBuffer;
47
+ /** アトミックカウンターバッファ (Storage) */
48
+ private atomicCounterBuffer;
49
+ /** Indirect Drawingパラメータバッファ */
50
+ private indirectBuffer;
51
+ /** インデックスバッファ */
52
+ private indexBuffer;
53
+ /** レンダリング用ユニフォームバッファ */
54
+ private renderUniformBuffer;
55
+ /** コンピュート用ユニフォームバッファ */
56
+ private computeUniformBuffer;
57
+ /** GPUフィルターカラムデータバッファ */
58
+ private filterColumnsBuffer;
59
+ /** GPUフィルター条件 */
60
+ private gpuFilterConditions;
61
+ /** GPUフィルターカラム数 */
62
+ private gpuFilterColumnCount;
63
+ /** レンダリング用バインドグループ */
64
+ private renderBindGroup;
65
+ /** フィルタリング用バインドグループ */
66
+ private filterBindGroup;
67
+ /** Indirect更新用バインドグループ */
68
+ private updateIndirectBindGroup;
69
+ /** 全ポイント数 */
70
+ private totalPointCount;
71
+ /** 背景色 */
72
+ private backgroundColor;
73
+ /** 表示可能なポイントの最大数 */
74
+ private visiblePointLimit;
75
+ /** フィルタリング結果が有効かどうか */
76
+ private filterResultValid;
77
+ /** 現在のズームレベル */
78
+ private zoom;
79
+ /** 現在のX方向パンオフセット */
80
+ private panX;
81
+ /** 現在のY方向パンオフセット */
82
+ private panY;
83
+ /** グローバル透明度 (0.0-1.0) */
84
+ private pointAlpha;
85
+ /** グローバルサイズスケール */
86
+ private pointSizeScale;
87
+ /**
88
+ * GpuLayerインスタンスを作成する
89
+ * @param options 設定オプション
90
+ */
91
+ constructor(options: GpuLayerOptions);
92
+ /**
93
+ * WebGPUを初期化し、レンダリングリソースを作成する
94
+ * @param initialData 初期データ
95
+ */
96
+ initialize(initialData: AllPointsData): Promise<void>;
97
+ /**
98
+ * パイプラインを作成する
99
+ */
100
+ private createPipelines;
101
+ /**
102
+ * バッファを作成する
103
+ * @param data 初期データ
104
+ */
105
+ private createBuffers;
106
+ /**
107
+ * バインドグループを作成する
108
+ */
109
+ private createBindGroups;
110
+ /**
111
+ * 全ポイントデータをアップロードする
112
+ * @param data 新しいポイントデータ
113
+ */
114
+ uploadAllPoints(data: AllPointsData): void;
115
+ /**
116
+ * ビュー行列を作成する
117
+ */
118
+ private createViewMatrix;
119
+ /**
120
+ * LOD閾値を計算する
121
+ * ズームレベルに応じて表示するポイント数を制限する
122
+ */
123
+ private calculateLodThreshold;
124
+ /**
125
+ * ユニフォームを更新する
126
+ */
127
+ updateUniforms(): void;
128
+ /**
129
+ * 散布図をレンダリングする
130
+ */
131
+ render(): void;
132
+ /**
133
+ * キャンバスをリサイズし、ビューポートを更新する
134
+ */
135
+ resize(width: number, height: number): void;
136
+ /**
137
+ * ズームレベルを設定する
138
+ */
139
+ setZoom(zoom: number): void;
140
+ /**
141
+ * 現在のズームレベルを取得する
142
+ */
143
+ getZoom(): number;
144
+ /**
145
+ * パンオフセットを設定する
146
+ */
147
+ setPan(x: number, y: number): void;
148
+ /**
149
+ * 現在のパンオフセットを取得する
150
+ */
151
+ getPan(): {
152
+ x: number;
153
+ y: number;
154
+ };
155
+ /**
156
+ * 指定した画面座標を中心にズームする
157
+ */
158
+ zoomToPoint(newZoom: number, screenX: number, screenY: number): void;
159
+ /**
160
+ * GPUレイヤーの設定オプションを更新する
161
+ */
162
+ updateOptions(options: Partial<GpuLayerOptions>): void;
163
+ /**
164
+ * GPUフィルターカラムデータをアップロードする
165
+ * @param data フィルターカラムデータ(各ポイントに4カラム分のf32、totalPoints * 4 floats)
166
+ * @param columnCount 有効なカラム数(0-4)
167
+ */
168
+ uploadFilterColumns(data: Float32Array, columnCount: number): void;
169
+ /**
170
+ * GPUフィルター条件を設定する
171
+ * @param conditions フィルター条件の配列
172
+ */
173
+ setGpuFilterConditions(conditions: {
174
+ columnIndex: number;
175
+ min: number;
176
+ max: number;
177
+ }[]): void;
178
+ /**
179
+ * GPUフィルター条件をクリアする
180
+ */
181
+ clearGpuFilterConditions(): void;
182
+ /**
183
+ * グローバル透明度を設定する
184
+ * @param alpha 透明度 (0.0-1.0)
185
+ */
186
+ setPointAlpha(alpha: number): void;
187
+ /**
188
+ * 現在のグローバル透明度を取得する
189
+ */
190
+ getPointAlpha(): number;
191
+ /**
192
+ * グローバルサイズスケールを設定する
193
+ * @param scale サイズスケール (0.01以上)
194
+ */
195
+ setPointSizeScale(scale: number): void;
196
+ /**
197
+ * 現在のグローバルサイズスケールを取得する
198
+ */
199
+ getPointSizeScale(): number;
200
+ /**
201
+ * リソースを破棄する
202
+ */
203
+ destroy(): void;
204
+ }