@allternit/viz 0.1.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 (45) hide show
  1. package/README.md +217 -0
  2. package/dist/charts/canvas-renderer.d.ts +40 -0
  3. package/dist/charts/canvas-renderer.d.ts.bak +40 -0
  4. package/dist/charts/canvas-renderer.d.ts.map +1 -0
  5. package/dist/charts/canvas-renderer.js +580 -0
  6. package/dist/charts/canvas-renderer.js.bak +580 -0
  7. package/dist/charts/canvas-renderer.js.map +1 -0
  8. package/dist/charts/svg-renderer.d.ts +21 -0
  9. package/dist/charts/svg-renderer.d.ts.bak +21 -0
  10. package/dist/charts/svg-renderer.d.ts.map +1 -0
  11. package/dist/charts/svg-renderer.js +285 -0
  12. package/dist/charts/svg-renderer.js.bak +285 -0
  13. package/dist/charts/svg-renderer.js.map +1 -0
  14. package/dist/index.d.ts +34 -0
  15. package/dist/index.d.ts.bak +34 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +35 -0
  18. package/dist/index.js.bak +35 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/types.d.ts +342 -0
  21. package/dist/types.d.ts.bak +342 -0
  22. package/dist/types.d.ts.map +1 -0
  23. package/dist/types.js +39 -0
  24. package/dist/types.js.bak +39 -0
  25. package/dist/types.js.map +1 -0
  26. package/dist/utils/data-processor.d.ts +64 -0
  27. package/dist/utils/data-processor.d.ts.bak +64 -0
  28. package/dist/utils/data-processor.d.ts.map +1 -0
  29. package/dist/utils/data-processor.js +326 -0
  30. package/dist/utils/data-processor.js.bak +326 -0
  31. package/dist/utils/data-processor.js.map +1 -0
  32. package/package.json +41 -0
  33. package/src/charts/canvas-renderer.ts +735 -0
  34. package/src/charts/canvas-renderer.ts.bak +735 -0
  35. package/src/charts/svg-renderer.test.ts.bak +837 -0
  36. package/src/charts/svg-renderer.ts +380 -0
  37. package/src/charts/svg-renderer.ts.bak +380 -0
  38. package/src/index.ts +64 -0
  39. package/src/index.ts.bak +64 -0
  40. package/src/types.ts +410 -0
  41. package/src/types.ts.bak +410 -0
  42. package/src/utils/data-processor.test.ts.bak +720 -0
  43. package/src/utils/data-processor.ts +420 -0
  44. package/src/utils/data-processor.ts.bak +420 -0
  45. package/tsconfig.json +22 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Data Processor Utility
3
+ *
4
+ * Provides data transformation and normalization functions for charts.
5
+ */
6
+
7
+ import type { DataPoint, DataSeries } from '../types';
8
+
9
+ /**
10
+ * Normalize data points to standard format
11
+ */
12
+ export function normalizeDataPoints(data: DataPoint[]): DataPoint[] {
13
+ return data.map((point) => ({
14
+ ...point,
15
+ y: point.y ?? point.value ?? 0,
16
+ x: point.x ?? point.name ?? '',
17
+ }));
18
+ }
19
+
20
+ /**
21
+ * Normalize data series
22
+ */
23
+ export function normalizeSeries(series: DataSeries[]): DataSeries[] {
24
+ return series.map((s) => ({
25
+ ...s,
26
+ data: normalizeDataPoints(s.data),
27
+ }));
28
+ }
29
+
30
+ /**
31
+ * Aggregate data by categories
32
+ */
33
+ export function aggregateByCategory(
34
+ data: DataPoint[],
35
+ aggregator: 'sum' | 'avg' | 'min' | 'max' | 'count' = 'sum'
36
+ ): Map<string, number> {
37
+ const groups = new Map<string, number[]>();
38
+
39
+ // Group values by category
40
+ for (const point of data) {
41
+ const key = String(point.x ?? point.name ?? 'unknown');
42
+ const value = Number(point.y ?? point.value ?? 0);
43
+
44
+ if (!groups.has(key)) {
45
+ groups.set(key, []);
46
+ }
47
+ groups.get(key)!.push(value);
48
+ }
49
+
50
+ // Apply aggregation
51
+ const result = new Map<string, number>();
52
+ for (const [key, values] of groups) {
53
+ switch (aggregator) {
54
+ case 'sum':
55
+ result.set(key, values.reduce((a, b) => a + b, 0));
56
+ break;
57
+ case 'avg':
58
+ result.set(key, values.reduce((a, b) => a + b, 0) / values.length);
59
+ break;
60
+ case 'min':
61
+ result.set(key, Math.min(...values));
62
+ break;
63
+ case 'max':
64
+ result.set(key, Math.max(...values));
65
+ break;
66
+ case 'count':
67
+ result.set(key, values.length);
68
+ break;
69
+ }
70
+ }
71
+
72
+ return result;
73
+ }
74
+
75
+ /**
76
+ * Sort data points by value or category
77
+ */
78
+ export function sortDataPoints(
79
+ data: DataPoint[],
80
+ by: 'value' | 'category' = 'value',
81
+ order: 'asc' | 'desc' = 'desc'
82
+ ): DataPoint[] {
83
+ const sorted = [...data];
84
+
85
+ sorted.sort((a, b) => {
86
+ let comparison = 0;
87
+
88
+ if (by === 'value') {
89
+ const aVal = Number(a.y ?? a.value ?? 0);
90
+ const bVal = Number(b.y ?? b.value ?? 0);
91
+ comparison = aVal - bVal;
92
+ } else {
93
+ const aCat = String(a.x ?? a.name ?? '');
94
+ const bCat = String(b.x ?? b.name ?? '');
95
+ comparison = aCat.localeCompare(bCat);
96
+ }
97
+
98
+ return order === 'asc' ? comparison : -comparison;
99
+ });
100
+
101
+ return sorted;
102
+ }
103
+
104
+ /**
105
+ * Filter data points by value range
106
+ */
107
+ export function filterByRange(
108
+ data: DataPoint[],
109
+ min?: number,
110
+ max?: number
111
+ ): DataPoint[] {
112
+ return data.filter((point) => {
113
+ const value = Number(point.y ?? point.value ?? 0);
114
+ if (min !== undefined && value < min) return false;
115
+ if (max !== undefined && value > max) return false;
116
+ return true;
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Calculate statistics for data points
122
+ */
123
+ export function calculateStats(data: DataPoint[]): {
124
+ min: number;
125
+ max: number;
126
+ sum: number;
127
+ avg: number;
128
+ count: number;
129
+ } {
130
+ const values = data.map((p) => Number(p.y ?? p.value ?? 0));
131
+
132
+ if (values.length === 0) {
133
+ return { min: 0, max: 0, sum: 0, avg: 0, count: 0 };
134
+ }
135
+
136
+ const min = Math.min(...values);
137
+ const max = Math.max(...values);
138
+ const sum = values.reduce((a, b) => a + b, 0);
139
+ const avg = sum / values.length;
140
+
141
+ return { min, max, sum, avg, count: values.length };
142
+ }
143
+
144
+ /**
145
+ * Process time series data
146
+ */
147
+ export function processTimeSeries(
148
+ data: DataPoint[],
149
+ options: {
150
+ interval?: 'hour' | 'day' | 'week' | 'month' | 'year';
151
+ fillGaps?: boolean;
152
+ aggregation?: 'sum' | 'avg' | 'last';
153
+ } = {}
154
+ ): DataPoint[] {
155
+ const { interval = 'day', fillGaps = false, aggregation = 'sum' } = options;
156
+
157
+ // Sort by date
158
+ const sorted = [...data].sort((a, b) => {
159
+ const aDate = new Date(a.x as string | number | Date).getTime();
160
+ const bDate = new Date(b.x as string | number | Date).getTime();
161
+ return aDate - bDate;
162
+ });
163
+
164
+ if (!fillGaps) {
165
+ return sorted;
166
+ }
167
+
168
+ // Group by interval
169
+ const grouped = new Map<string, number[]>();
170
+
171
+ for (const point of sorted) {
172
+ const date = new Date(point.x as string | number | Date);
173
+ const key = formatDateToInterval(date, interval);
174
+ const value = Number(point.y ?? point.value ?? 0);
175
+
176
+ if (!grouped.has(key)) {
177
+ grouped.set(key, []);
178
+ }
179
+ grouped.get(key)!.push(value);
180
+ }
181
+
182
+ // Apply aggregation and fill gaps
183
+ const result: DataPoint[] = [];
184
+ if (sorted.length === 0) return result;
185
+
186
+ const startDate = new Date(sorted[0].x as string | number | Date);
187
+ const endDate = new Date(sorted[sorted.length - 1].x as string | number | Date);
188
+
189
+ // Normalize dates to the start of their interval for proper gap filling
190
+ let currentDate = normalizeToIntervalStart(startDate, interval);
191
+ const normalizedEndDate = normalizeToIntervalStart(endDate, interval);
192
+
193
+ while (currentDate <= normalizedEndDate) {
194
+ const key = formatDateToInterval(currentDate, interval);
195
+ const values = grouped.get(key) ?? [];
196
+
197
+ let value: number;
198
+ if (values.length === 0) {
199
+ value = 0;
200
+ } else {
201
+ switch (aggregation) {
202
+ case 'sum':
203
+ value = values.reduce((a, b) => a + b, 0);
204
+ break;
205
+ case 'avg':
206
+ value = values.reduce((a, b) => a + b, 0) / values.length;
207
+ break;
208
+ case 'last':
209
+ value = values[values.length - 1];
210
+ break;
211
+ }
212
+ }
213
+
214
+ result.push({ x: key, y: value });
215
+
216
+ // Advance to next interval
217
+ currentDate = addInterval(currentDate, interval);
218
+ }
219
+
220
+ return result;
221
+ }
222
+
223
+ /**
224
+ * Calculate moving average
225
+ */
226
+ export function movingAverage(
227
+ data: DataPoint[],
228
+ windowSize: number
229
+ ): DataPoint[] {
230
+ if (windowSize <= 1 || data.length < windowSize) {
231
+ return data;
232
+ }
233
+
234
+ const result: DataPoint[] = [];
235
+
236
+ for (let i = 0; i < data.length; i++) {
237
+ if (i < windowSize - 1) {
238
+ result.push(data[i]);
239
+ continue;
240
+ }
241
+
242
+ let sum = 0;
243
+ for (let j = 0; j < windowSize; j++) {
244
+ sum += Number(data[i - j].y ?? data[i - j].value ?? 0);
245
+ }
246
+
247
+ result.push({
248
+ ...data[i],
249
+ y: sum / windowSize,
250
+ });
251
+ }
252
+
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * Detect outliers using IQR method
258
+ */
259
+ export function detectOutliers(
260
+ data: DataPoint[],
261
+ threshold: number = 1.5
262
+ ): { inliers: DataPoint[]; outliers: DataPoint[] } {
263
+ const values = data.map((p) => Number(p.y ?? p.value ?? 0));
264
+ const sorted = [...values].sort((a, b) => a - b);
265
+
266
+ const q1Index = Math.floor(sorted.length * 0.25);
267
+ const q3Index = Math.floor(sorted.length * 0.75);
268
+ const q1 = sorted[q1Index];
269
+ const q3 = sorted[q3Index];
270
+ const iqr = q3 - q1;
271
+
272
+ const lowerBound = q1 - threshold * iqr;
273
+ const upperBound = q3 + threshold * iqr;
274
+
275
+ const inliers: DataPoint[] = [];
276
+ const outliers: DataPoint[] = [];
277
+
278
+ for (const point of data) {
279
+ const value = Number(point.y ?? point.value ?? 0);
280
+ if (value >= lowerBound && value <= upperBound) {
281
+ inliers.push(point);
282
+ } else {
283
+ outliers.push(point);
284
+ }
285
+ }
286
+
287
+ return { inliers, outliers };
288
+ }
289
+
290
+ /**
291
+ * Normalize values to 0-1 range
292
+ */
293
+ export function normalizeValues(
294
+ data: DataPoint[],
295
+ min?: number,
296
+ max?: number
297
+ ): DataPoint[] {
298
+ const values = data.map((p) => Number(p.y ?? p.value ?? 0));
299
+ const dataMin = min ?? Math.min(...values);
300
+ const dataMax = max ?? Math.max(...values);
301
+ const range = dataMax - dataMin;
302
+
303
+ if (range === 0) {
304
+ return data.map((p) => ({ ...p, y: 0 }));
305
+ }
306
+
307
+ return data.map((p) => ({
308
+ ...p,
309
+ y: (Number(p.y ?? p.value ?? 0) - dataMin) / range,
310
+ }));
311
+ }
312
+
313
+ /**
314
+ * Calculate percent change between consecutive points
315
+ */
316
+ export function calculatePercentChange(data: DataPoint[]): DataPoint[] {
317
+ if (data.length === 0) return data;
318
+ if (data.length === 1) {
319
+ return [{ ...data[0], y: 0 }];
320
+ }
321
+
322
+ const result: DataPoint[] = [];
323
+
324
+ for (let i = 0; i < data.length; i++) {
325
+ if (i === 0) {
326
+ result.push({ ...data[i], y: 0 });
327
+ continue;
328
+ }
329
+
330
+ const current = Number(data[i].y ?? data[i].value ?? 0);
331
+ const previous = Number(data[i - 1].y ?? data[i - 1].value ?? 0);
332
+
333
+ const change = previous !== 0 ? ((current - previous) / previous) * 100 : 0;
334
+
335
+ result.push({ ...data[i], y: change });
336
+ }
337
+
338
+ return result;
339
+ }
340
+
341
+ // Helper functions
342
+
343
+ function formatDateToInterval(
344
+ date: Date,
345
+ interval: 'hour' | 'day' | 'week' | 'month' | 'year'
346
+ ): string {
347
+ // Use UTC methods to avoid timezone issues
348
+ switch (interval) {
349
+ case 'hour':
350
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:00`;
351
+ case 'day':
352
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;
353
+ case 'week': {
354
+ const weekStart = new Date(date);
355
+ weekStart.setUTCDate(date.getUTCDate() - date.getUTCDay());
356
+ return `${weekStart.getUTCFullYear()}-${String(weekStart.getUTCMonth() + 1).padStart(2, '0')}-${String(weekStart.getUTCDate()).padStart(2, '0')}`;
357
+ }
358
+ case 'month':
359
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
360
+ case 'year':
361
+ return String(date.getUTCFullYear());
362
+ default:
363
+ return date.toISOString();
364
+ }
365
+ }
366
+
367
+ function addInterval(
368
+ date: Date,
369
+ interval: 'hour' | 'day' | 'week' | 'month' | 'year'
370
+ ): Date {
371
+ const result = new Date(date);
372
+ switch (interval) {
373
+ case 'hour':
374
+ result.setUTCHours(result.getUTCHours() + 1);
375
+ break;
376
+ case 'day':
377
+ result.setUTCDate(result.getUTCDate() + 1);
378
+ break;
379
+ case 'week':
380
+ result.setUTCDate(result.getUTCDate() + 7);
381
+ break;
382
+ case 'month':
383
+ result.setUTCMonth(result.getUTCMonth() + 1);
384
+ break;
385
+ case 'year':
386
+ result.setUTCFullYear(result.getUTCFullYear() + 1);
387
+ break;
388
+ }
389
+ return result;
390
+ }
391
+
392
+ function normalizeToIntervalStart(
393
+ date: Date,
394
+ interval: 'hour' | 'day' | 'week' | 'month' | 'year'
395
+ ): Date {
396
+ const result = new Date(date);
397
+ switch (interval) {
398
+ case 'hour':
399
+ result.setUTCMinutes(0, 0, 0);
400
+ break;
401
+ case 'day':
402
+ result.setUTCHours(0, 0, 0, 0);
403
+ break;
404
+ case 'week': {
405
+ const dayOfWeek = result.getUTCDay();
406
+ result.setUTCDate(result.getUTCDate() - dayOfWeek);
407
+ result.setUTCHours(0, 0, 0, 0);
408
+ break;
409
+ }
410
+ case 'month':
411
+ result.setUTCDate(1);
412
+ result.setUTCHours(0, 0, 0, 0);
413
+ break;
414
+ case 'year':
415
+ result.setUTCMonth(0, 1);
416
+ result.setUTCHours(0, 0, 0, 0);
417
+ break;
418
+ }
419
+ return result;
420
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "strict": false,
11
+ "noImplicitAny": false,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "outDir": "./dist",
16
+ "rootDir": "./src",
17
+ "resolveJsonModule": true,
18
+ "composite": true
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
22
+ }