@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.
- package/.github/dependabot.yml +42 -0
- package/.github/workflows/ci.yaml +111 -0
- package/.github/workflows/release.yml +55 -0
- package/.prettierrc +11 -0
- package/LICENSE +22 -0
- package/README.md +250 -0
- package/dist/data/data-layer.d.ts +169 -0
- package/dist/data/data-layer.js +402 -0
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.js +2 -0
- package/dist/data/repository.d.ts +48 -0
- package/dist/data/repository.js +109 -0
- package/dist/diagnostics.d.ts +27 -0
- package/dist/diagnostics.js +71 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +58 -0
- package/dist/event-emitter.d.ts +62 -0
- package/dist/event-emitter.js +82 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13 -0
- package/dist/renderer/gpu-layer.d.ts +204 -0
- package/dist/renderer/gpu-layer.js +611 -0
- package/dist/renderer/index.d.ts +3 -0
- package/dist/renderer/index.js +3 -0
- package/dist/renderer/shaders.d.ts +13 -0
- package/dist/renderer/shaders.js +216 -0
- package/dist/renderer/webgpu-context.d.ts +20 -0
- package/dist/renderer/webgpu-context.js +88 -0
- package/dist/scatter-plot.d.ts +210 -0
- package/dist/scatter-plot.js +450 -0
- package/dist/types.d.ts +171 -0
- package/dist/types.js +1 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/label-layer.d.ts +176 -0
- package/dist/ui/label-layer.js +488 -0
- package/docs/image.png +0 -0
- package/eslint.config.js +72 -0
- package/examples/next/README.md +36 -0
- package/examples/next/app/components/ColorExpressionInput.tsx +41 -0
- package/examples/next/app/components/ControlPanel.tsx +30 -0
- package/examples/next/app/components/HoverControlPanel.tsx +69 -0
- package/examples/next/app/components/HoverInfoDisplay.tsx +40 -0
- package/examples/next/app/components/LabelFilterInput.tsx +46 -0
- package/examples/next/app/components/LabelList.tsx +106 -0
- package/examples/next/app/components/PointAlphaSlider.tsx +21 -0
- package/examples/next/app/components/PointLimitSlider.tsx +23 -0
- package/examples/next/app/components/PointList.tsx +105 -0
- package/examples/next/app/components/PointSizeScaleSlider.tsx +22 -0
- package/examples/next/app/components/ScatterPlotCanvas.tsx +150 -0
- package/examples/next/app/components/SearchBox.tsx +46 -0
- package/examples/next/app/components/Slider.tsx +76 -0
- package/examples/next/app/components/StatsDisplay.tsx +15 -0
- package/examples/next/app/components/TimeFilterSlider.tsx +169 -0
- package/examples/next/app/context/ScatterPlotContext.tsx +402 -0
- package/examples/next/app/favicon.ico +0 -0
- package/examples/next/app/globals.css +23 -0
- package/examples/next/app/layout.tsx +35 -0
- package/examples/next/app/page.tsx +15 -0
- package/examples/next/eslint.config.mjs +18 -0
- package/examples/next/next.config.ts +7 -0
- package/examples/next/package-lock.json +6572 -0
- package/examples/next/package.json +27 -0
- package/examples/next/postcss.config.mjs +7 -0
- package/examples/next/scripts/generate_labels.py +167 -0
- package/examples/next/tsconfig.json +34 -0
- package/package.json +43 -0
- package/src/data/data-layer.ts +515 -0
- package/src/data/index.ts +2 -0
- package/src/data/repository.ts +146 -0
- package/src/diagnostics.ts +108 -0
- package/src/errors.ts +69 -0
- package/src/event-emitter.ts +88 -0
- package/src/index.ts +40 -0
- package/src/renderer/gpu-layer.ts +757 -0
- package/src/renderer/index.ts +3 -0
- package/src/renderer/shaders.ts +219 -0
- package/src/renderer/webgpu-context.ts +98 -0
- package/src/scatter-plot.ts +533 -0
- package/src/types.ts +218 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/label-layer.ts +648 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGPU診断結果のインターフェース
|
|
3
|
+
*/
|
|
4
|
+
export interface WebGPUDiagnostics {
|
|
5
|
+
/** WebGPU APIがサポートされているか */
|
|
6
|
+
supported: boolean;
|
|
7
|
+
/** WebGPUが利用可能か */
|
|
8
|
+
available: boolean;
|
|
9
|
+
/** GPUアダプター情報 */
|
|
10
|
+
adapter: {
|
|
11
|
+
/** アダプターが利用可能か */
|
|
12
|
+
available: boolean;
|
|
13
|
+
/** サポートされている機能の配列 */
|
|
14
|
+
features?: string[];
|
|
15
|
+
/** GPUの制限値 */
|
|
16
|
+
limits?: Record<string, number>;
|
|
17
|
+
};
|
|
18
|
+
/** ブラウザ情報 */
|
|
19
|
+
browser: string;
|
|
20
|
+
/** エラーメッセージ(ある場合) */
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* WebGPUの利用可能性を診断する
|
|
26
|
+
* @returns 診断結果のWebGPUDiagnosticsオブジェクト
|
|
27
|
+
*/
|
|
28
|
+
export async function diagnoseWebGPU(): Promise<WebGPUDiagnostics> {
|
|
29
|
+
const result: WebGPUDiagnostics = {
|
|
30
|
+
supported: false,
|
|
31
|
+
available: false,
|
|
32
|
+
adapter: {
|
|
33
|
+
available: false,
|
|
34
|
+
},
|
|
35
|
+
browser: getBrowserInfo(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (!('gpu' in navigator)) {
|
|
39
|
+
result.error = 'WebGPU API not found in navigator';
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
result.supported = true;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
47
|
+
|
|
48
|
+
if (!adapter) {
|
|
49
|
+
result.error = 'GPU adapter is null - GPU may be blocklisted or WebGPU is disabled';
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
result.adapter.available = true;
|
|
54
|
+
result.adapter.features = Array.from(adapter.features);
|
|
55
|
+
result.adapter.limits = {};
|
|
56
|
+
|
|
57
|
+
const limitsToCheck = [
|
|
58
|
+
'maxTextureDimension1D',
|
|
59
|
+
'maxTextureDimension2D',
|
|
60
|
+
'maxBufferSize',
|
|
61
|
+
'maxVertexBuffers',
|
|
62
|
+
'maxVertexAttributes',
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const limit of limitsToCheck) {
|
|
66
|
+
if (limit in adapter.limits) {
|
|
67
|
+
result.adapter.limits[limit] = (adapter.limits as any)[limit];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
result.available = true;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
result.error = `Error requesting adapter: ${e}`;
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* ブラウザ情報を取得する
|
|
82
|
+
* @returns ブラウザ名とバージョンの文字列
|
|
83
|
+
*/
|
|
84
|
+
function getBrowserInfo(): string {
|
|
85
|
+
const ua = navigator.userAgent;
|
|
86
|
+
|
|
87
|
+
if (ua.includes('Chrome') && !ua.includes('Edg')) {
|
|
88
|
+
const match = ua.match(/Chrome\/(\d+)/);
|
|
89
|
+
return match ? `Chrome ${match[1]}` : 'Chrome (unknown version)';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (ua.includes('Edg')) {
|
|
93
|
+
const match = ua.match(/Edg\/(\d+)/);
|
|
94
|
+
return match ? `Edge ${match[1]}` : 'Edge (unknown version)';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ua.includes('Safari') && !ua.includes('Chrome')) {
|
|
98
|
+
const match = ua.match(/Version\/(\d+)/);
|
|
99
|
+
return match ? `Safari ${match[1]}` : 'Safari (unknown version)';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (ua.includes('Firefox')) {
|
|
103
|
+
const match = ua.match(/Firefox\/(\d+)/);
|
|
104
|
+
return match ? `Firefox ${match[1]}` : 'Firefox (unknown version)';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return 'Unknown browser';
|
|
108
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ScatterPlotError, ErrorCode, ErrorCategory, ErrorSeverity } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* エラーコードからカテゴリへのマッピング
|
|
5
|
+
*/
|
|
6
|
+
const categoryMap: Record<ErrorCode, ErrorCategory> = {
|
|
7
|
+
WEBGPU_NOT_SUPPORTED: 'webgpu',
|
|
8
|
+
GPU_ADAPTER_NOT_AVAILABLE: 'webgpu',
|
|
9
|
+
GPU_DEVICE_FAILED: 'webgpu',
|
|
10
|
+
WEBGPU_CONTEXT_FAILED: 'webgpu',
|
|
11
|
+
DATA_LAYER_NOT_INITIALIZED: 'data',
|
|
12
|
+
PARQUET_LOAD_FAILED: 'data',
|
|
13
|
+
QUERY_FAILED: 'query',
|
|
14
|
+
LABEL_FETCH_FAILED: 'label',
|
|
15
|
+
LABEL_PARSE_FAILED: 'label',
|
|
16
|
+
NETWORK_ERROR: 'network',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* エラーコードから重大度レベルへのマッピング
|
|
21
|
+
*/
|
|
22
|
+
const severityMap: Record<ErrorCode, ErrorSeverity> = {
|
|
23
|
+
WEBGPU_NOT_SUPPORTED: 'fatal',
|
|
24
|
+
GPU_ADAPTER_NOT_AVAILABLE: 'fatal',
|
|
25
|
+
GPU_DEVICE_FAILED: 'fatal',
|
|
26
|
+
WEBGPU_CONTEXT_FAILED: 'fatal',
|
|
27
|
+
DATA_LAYER_NOT_INITIALIZED: 'fatal',
|
|
28
|
+
PARQUET_LOAD_FAILED: 'fatal',
|
|
29
|
+
QUERY_FAILED: 'error',
|
|
30
|
+
NETWORK_ERROR: 'error',
|
|
31
|
+
LABEL_FETCH_FAILED: 'warning',
|
|
32
|
+
LABEL_PARSE_FAILED: 'warning',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 指定されたコードとメッセージでScatterPlotErrorオブジェクトを作成する
|
|
37
|
+
*
|
|
38
|
+
* @param code エラーコード
|
|
39
|
+
* @param message 人間が読めるエラーメッセージ
|
|
40
|
+
* @param options 追加オプション
|
|
41
|
+
* @returns ScatterPlotErrorオブジェクト
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const error = createError(
|
|
46
|
+
* 'WEBGPU_NOT_SUPPORTED',
|
|
47
|
+
* 'WebGPU is not supported in this browser',
|
|
48
|
+
* { context: { userAgent: navigator.userAgent } }
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function createError(
|
|
53
|
+
code: ErrorCode,
|
|
54
|
+
message: string,
|
|
55
|
+
options?: {
|
|
56
|
+
cause?: Error;
|
|
57
|
+
context?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
): ScatterPlotError {
|
|
60
|
+
return {
|
|
61
|
+
code,
|
|
62
|
+
category: categoryMap[code],
|
|
63
|
+
severity: severityMap[code],
|
|
64
|
+
message,
|
|
65
|
+
cause: options?.cause,
|
|
66
|
+
context: options?.context,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* イベントハンドラー関数の型
|
|
3
|
+
*/
|
|
4
|
+
export type EventHandler<T = unknown> = (event: T) => void;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ブラウザ環境向けの軽量なEventEmitter実装
|
|
8
|
+
* 外部依存なしで型安全なイベントハンドリングを提供する
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* interface MyEvents {
|
|
13
|
+
* data: { value: number };
|
|
14
|
+
* error: Error;
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* class MyClass extends EventEmitter<MyEvents> {
|
|
18
|
+
* doSomething() {
|
|
19
|
+
* this.emit('data', { value: 42 });
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* const instance = new MyClass();
|
|
24
|
+
* instance.on('data', (event) => console.log(event.value));
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class EventEmitter<T extends { [K in keyof T]: unknown }> {
|
|
28
|
+
/** イベント名からハンドラーのセットへのマップ */
|
|
29
|
+
private listeners = new Map<keyof T, Set<EventHandler<any>>>();
|
|
30
|
+
|
|
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
|
+
if (!this.listeners.has(event)) {
|
|
40
|
+
this.listeners.set(event, new Set());
|
|
41
|
+
}
|
|
42
|
+
this.listeners.get(event)!.add(handler);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 指定されたイベントからハンドラーを削除する
|
|
48
|
+
*
|
|
49
|
+
* @param event イベント名
|
|
50
|
+
* @param handler 削除するハンドラー関数
|
|
51
|
+
* @returns メソッドチェーン用のthisインスタンス
|
|
52
|
+
*/
|
|
53
|
+
off<K extends keyof T>(event: K, handler: EventHandler<T[K]>): this {
|
|
54
|
+
this.listeners.get(event)?.delete(handler);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 登録されたすべてのハンドラーにイベントを発行する
|
|
60
|
+
*
|
|
61
|
+
* @param event 発行するイベント名
|
|
62
|
+
* @param data ハンドラーに渡すイベントデータ
|
|
63
|
+
* @returns リスナーがあればtrue、なければfalse
|
|
64
|
+
*/
|
|
65
|
+
protected emit<K extends keyof T>(event: K, data: T[K]): boolean {
|
|
66
|
+
const handlers = this.listeners.get(event);
|
|
67
|
+
if (!handlers || handlers.size === 0) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
handlers.forEach((handler) => handler(data));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 指定されたイベント、またはすべてのイベントのリスナーを削除する
|
|
76
|
+
*
|
|
77
|
+
* @param event イベント名(省略時はすべてのリスナーを削除)
|
|
78
|
+
* @returns メソッドチェーン用のthisインスタンス
|
|
79
|
+
*/
|
|
80
|
+
removeAllListeners(event?: keyof T): this {
|
|
81
|
+
if (event) {
|
|
82
|
+
this.listeners.delete(event);
|
|
83
|
+
} else {
|
|
84
|
+
this.listeners.clear();
|
|
85
|
+
}
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* duckscatter - WebGPUを使用して散布図を描画するTypeScriptライブラリ
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { ScatterPlot } from './scatter-plot.js';
|
|
6
|
+
export type {
|
|
7
|
+
ColorRGBA,
|
|
8
|
+
ScatterPlotOptions,
|
|
9
|
+
Label,
|
|
10
|
+
WhereCondition,
|
|
11
|
+
NumericFilter,
|
|
12
|
+
StringFilter,
|
|
13
|
+
RawSqlFilter,
|
|
14
|
+
NumericOperator,
|
|
15
|
+
StringOperator,
|
|
16
|
+
// GPU filter types
|
|
17
|
+
GpuWhereCondition,
|
|
18
|
+
// Error handling types
|
|
19
|
+
ErrorSeverity,
|
|
20
|
+
ErrorCategory,
|
|
21
|
+
ErrorCode,
|
|
22
|
+
ScatterPlotError,
|
|
23
|
+
ScatterPlotEventMap,
|
|
24
|
+
// Hover control types
|
|
25
|
+
PointId,
|
|
26
|
+
LabelIdentifier,
|
|
27
|
+
LabelHoverCallback,
|
|
28
|
+
} from './types.js';
|
|
29
|
+
|
|
30
|
+
export { diagnoseWebGPU } from './diagnostics.js';
|
|
31
|
+
export type { WebGPUDiagnostics } from './diagnostics.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 現在の環境でWebGPUがサポートされているかを確認する
|
|
35
|
+
* @returns WebGPUがサポートされている場合はtrue、そうでない場合はfalse
|
|
36
|
+
*/
|
|
37
|
+
export function isWebGPUSupported(): boolean {
|
|
38
|
+
// navigatorオブジェクトにgpuプロパティが存在するかをチェック
|
|
39
|
+
return 'gpu' in navigator;
|
|
40
|
+
}
|