@chartts/json 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 chartts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ // src/index.ts
4
+ function isChartData(input) {
5
+ if (typeof input !== "object" || input === null) return false;
6
+ const obj = input;
7
+ return Array.isArray(obj["series"]) && obj["series"].length > 0 && typeof obj["series"][0]?.["name"] === "string" && Array.isArray(obj["series"][0]?.["values"]);
8
+ }
9
+ function isColumnar(input) {
10
+ const values = Object.values(input);
11
+ return values.length > 0 && values.every((v) => Array.isArray(v));
12
+ }
13
+ function isNumericArray(arr) {
14
+ return arr.length > 0 && arr.every((v) => typeof v === "number" || typeof v === "string" && !isNaN(Number(v)));
15
+ }
16
+ function fromJSON(input, options) {
17
+ const data = typeof input === "string" ? JSON.parse(input) : input;
18
+ if (isChartData(data)) return data;
19
+ if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
20
+ return fromRowArray(data, options);
21
+ }
22
+ if (typeof data === "object" && data !== null && !Array.isArray(data) && isColumnar(data)) {
23
+ return fromColumnar(data, options);
24
+ }
25
+ throw new Error("Unrecognized JSON shape. Expected array of objects, columnar object, or ChartData.");
26
+ }
27
+ function fromRowArray(rows, options) {
28
+ if (rows.length === 0) return { series: [] };
29
+ const keys = Object.keys(rows[0]);
30
+ let labelKey = options?.labelKey;
31
+ if (!labelKey) {
32
+ labelKey = keys.find(
33
+ (k) => rows.some((row) => typeof row[k] === "string" && isNaN(Number(row[k])))
34
+ ) ?? keys[0];
35
+ }
36
+ let seriesKeys = options?.seriesKeys;
37
+ if (!seriesKeys) {
38
+ seriesKeys = keys.filter((k) => {
39
+ if (k === labelKey) return false;
40
+ return rows.every((row) => {
41
+ const v = row[k];
42
+ return typeof v === "number" || typeof v === "string" && !isNaN(Number(v)) || v == null;
43
+ });
44
+ });
45
+ }
46
+ const labels = rows.map((row) => String(row[labelKey] ?? ""));
47
+ const series = seriesKeys.map((key) => ({
48
+ name: key,
49
+ values: rows.map((row) => {
50
+ const v = row[key];
51
+ if (v == null) return 0;
52
+ return typeof v === "number" ? v : Number(v);
53
+ })
54
+ }));
55
+ return { labels, series };
56
+ }
57
+ function fromColumnar(data, options) {
58
+ const keys = Object.keys(data);
59
+ let labelKey = options?.labelKey;
60
+ if (!labelKey) {
61
+ labelKey = keys.find((k) => k === "labels") ?? keys.find((k) => !isNumericArray(data[k])) ?? keys[0];
62
+ }
63
+ let seriesKeys = options?.seriesKeys;
64
+ if (!seriesKeys) {
65
+ seriesKeys = keys.filter((k) => k !== labelKey && isNumericArray(data[k]));
66
+ }
67
+ const labels = (data[labelKey] ?? []).map((v) => String(v));
68
+ const series = seriesKeys.map((key) => ({
69
+ name: key,
70
+ values: (data[key] ?? []).map((v) => Number(v))
71
+ }));
72
+ return { labels, series };
73
+ }
74
+ function toJSON(data, options) {
75
+ const format = options?.format ?? "rows";
76
+ if (format === "columnar") {
77
+ const result = {};
78
+ if (data.labels) {
79
+ result["labels"] = data.labels.map((l) => l instanceof Date ? l.toISOString() : l);
80
+ }
81
+ for (const s of data.series) {
82
+ result[s.name] = [...s.values];
83
+ }
84
+ return result;
85
+ }
86
+ const rowCount = data.series[0]?.values.length ?? 0;
87
+ const rows = [];
88
+ for (let i = 0; i < rowCount; i++) {
89
+ const row = {};
90
+ if (data.labels) {
91
+ const label = data.labels[i];
92
+ row["label"] = label instanceof Date ? label.toISOString() : label;
93
+ }
94
+ for (const s of data.series) {
95
+ row[s.name] = s.values[i];
96
+ }
97
+ rows.push(row);
98
+ }
99
+ return rows;
100
+ }
101
+
102
+ exports.fromJSON = fromJSON;
103
+ exports.toJSON = toJSON;
104
+ //# sourceMappingURL=index.cjs.map
105
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ChartData, Series } from '@chartts/core'\n\nexport interface FromJSONOptions {\n /** Key to use as labels. Default: auto-detect first string-valued key */\n labelKey?: string\n /** Keys to use as series. Default: all numeric-valued keys */\n seriesKeys?: string[]\n}\n\nexport type ToJSONFormat = 'rows' | 'columnar'\n\nexport interface ToJSONOptions {\n /** Output format. Default: 'rows' (array of objects) */\n format?: ToJSONFormat\n}\n\nfunction isChartData(input: unknown): input is ChartData {\n if (typeof input !== 'object' || input === null) return false\n const obj = input as Record<string, unknown>\n return Array.isArray(obj['series']) &&\n obj['series'].length > 0 &&\n typeof (obj['series'] as Record<string, unknown>[])[0]?.['name'] === 'string' &&\n Array.isArray((obj['series'] as Record<string, unknown>[])[0]?.['values'])\n}\n\nfunction isColumnar(input: Record<string, unknown>): boolean {\n const values = Object.values(input)\n return values.length > 0 && values.every(v => Array.isArray(v))\n}\n\nfunction isNumericArray(arr: unknown[]): boolean {\n return arr.length > 0 && arr.every(v => typeof v === 'number' || (typeof v === 'string' && !isNaN(Number(v))))\n}\n\nexport function fromJSON(input: string | unknown, options?: FromJSONOptions): ChartData {\n const data = typeof input === 'string' ? JSON.parse(input) as unknown : input\n\n // Direct ChartData passthrough\n if (isChartData(data)) return data as ChartData\n\n // Array of objects: [{month:\"Jan\", sales:10}, ...]\n if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object' && data[0] !== null) {\n return fromRowArray(data as Record<string, unknown>[], options)\n }\n\n // Columnar: {labels:[\"Jan\"], sales:[10], costs:[5]}\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && isColumnar(data as Record<string, unknown>)) {\n return fromColumnar(data as Record<string, unknown[]>, options)\n }\n\n throw new Error('Unrecognized JSON shape. Expected array of objects, columnar object, or ChartData.')\n}\n\nfunction fromRowArray(rows: Record<string, unknown>[], options?: FromJSONOptions): ChartData {\n if (rows.length === 0) return { series: [] }\n\n const keys = Object.keys(rows[0]!)\n\n // Determine label key\n let labelKey = options?.labelKey\n if (!labelKey) {\n // Auto-detect: first key whose values are not all numbers\n labelKey = keys.find(k =>\n rows.some(row => typeof row[k] === 'string' && isNaN(Number(row[k])))\n ) ?? keys[0]!\n }\n\n // Determine series keys\n let seriesKeys = options?.seriesKeys\n if (!seriesKeys) {\n seriesKeys = keys.filter(k => {\n if (k === labelKey) return false\n return rows.every(row => {\n const v = row[k]\n return typeof v === 'number' || (typeof v === 'string' && !isNaN(Number(v))) || v == null\n })\n })\n }\n\n const labels = rows.map(row => String(row[labelKey!] ?? ''))\n const series: Series[] = seriesKeys.map(key => ({\n name: key,\n values: rows.map(row => {\n const v = row[key]\n if (v == null) return 0\n return typeof v === 'number' ? v : Number(v)\n }),\n }))\n\n return { labels, series }\n}\n\nfunction fromColumnar(data: Record<string, unknown[]>, options?: FromJSONOptions): ChartData {\n const keys = Object.keys(data)\n\n let labelKey = options?.labelKey\n if (!labelKey) {\n // Auto-detect: 'labels' key, or first non-numeric array\n labelKey = keys.find(k => k === 'labels')\n ?? keys.find(k => !isNumericArray(data[k]!))\n ?? keys[0]!\n }\n\n let seriesKeys = options?.seriesKeys\n if (!seriesKeys) {\n seriesKeys = keys.filter(k => k !== labelKey && isNumericArray(data[k]!))\n }\n\n const labels = (data[labelKey] ?? []).map(v => String(v))\n const series: Series[] = seriesKeys.map(key => ({\n name: key,\n values: (data[key] ?? []).map(v => Number(v)),\n }))\n\n return { labels, series }\n}\n\nexport function toJSON(data: ChartData, options?: ToJSONOptions): unknown {\n const format = options?.format ?? 'rows'\n\n if (format === 'columnar') {\n const result: Record<string, unknown[]> = {}\n if (data.labels) {\n result['labels'] = data.labels.map(l => l instanceof Date ? l.toISOString() : l)\n }\n for (const s of data.series) {\n result[s.name] = [...s.values]\n }\n return result\n }\n\n // rows format: array of objects\n const rowCount = data.series[0]?.values.length ?? 0\n const rows: Record<string, unknown>[] = []\n for (let i = 0; i < rowCount; i++) {\n const row: Record<string, unknown> = {}\n if (data.labels) {\n const label = data.labels[i]\n row['label'] = label instanceof Date ? label.toISOString() : label\n }\n for (const s of data.series) {\n row[s.name] = s.values[i]\n }\n rows.push(row)\n }\n return rows\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { ChartData } from '@chartts/core';
2
+
3
+ interface FromJSONOptions {
4
+ /** Key to use as labels. Default: auto-detect first string-valued key */
5
+ labelKey?: string;
6
+ /** Keys to use as series. Default: all numeric-valued keys */
7
+ seriesKeys?: string[];
8
+ }
9
+ type ToJSONFormat = 'rows' | 'columnar';
10
+ interface ToJSONOptions {
11
+ /** Output format. Default: 'rows' (array of objects) */
12
+ format?: ToJSONFormat;
13
+ }
14
+ declare function fromJSON(input: string | unknown, options?: FromJSONOptions): ChartData;
15
+ declare function toJSON(data: ChartData, options?: ToJSONOptions): unknown;
16
+
17
+ export { type FromJSONOptions, type ToJSONFormat, type ToJSONOptions, fromJSON, toJSON };
@@ -0,0 +1,17 @@
1
+ import { ChartData } from '@chartts/core';
2
+
3
+ interface FromJSONOptions {
4
+ /** Key to use as labels. Default: auto-detect first string-valued key */
5
+ labelKey?: string;
6
+ /** Keys to use as series. Default: all numeric-valued keys */
7
+ seriesKeys?: string[];
8
+ }
9
+ type ToJSONFormat = 'rows' | 'columnar';
10
+ interface ToJSONOptions {
11
+ /** Output format. Default: 'rows' (array of objects) */
12
+ format?: ToJSONFormat;
13
+ }
14
+ declare function fromJSON(input: string | unknown, options?: FromJSONOptions): ChartData;
15
+ declare function toJSON(data: ChartData, options?: ToJSONOptions): unknown;
16
+
17
+ export { type FromJSONOptions, type ToJSONFormat, type ToJSONOptions, fromJSON, toJSON };
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ // src/index.ts
2
+ function isChartData(input) {
3
+ if (typeof input !== "object" || input === null) return false;
4
+ const obj = input;
5
+ return Array.isArray(obj["series"]) && obj["series"].length > 0 && typeof obj["series"][0]?.["name"] === "string" && Array.isArray(obj["series"][0]?.["values"]);
6
+ }
7
+ function isColumnar(input) {
8
+ const values = Object.values(input);
9
+ return values.length > 0 && values.every((v) => Array.isArray(v));
10
+ }
11
+ function isNumericArray(arr) {
12
+ return arr.length > 0 && arr.every((v) => typeof v === "number" || typeof v === "string" && !isNaN(Number(v)));
13
+ }
14
+ function fromJSON(input, options) {
15
+ const data = typeof input === "string" ? JSON.parse(input) : input;
16
+ if (isChartData(data)) return data;
17
+ if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
18
+ return fromRowArray(data, options);
19
+ }
20
+ if (typeof data === "object" && data !== null && !Array.isArray(data) && isColumnar(data)) {
21
+ return fromColumnar(data, options);
22
+ }
23
+ throw new Error("Unrecognized JSON shape. Expected array of objects, columnar object, or ChartData.");
24
+ }
25
+ function fromRowArray(rows, options) {
26
+ if (rows.length === 0) return { series: [] };
27
+ const keys = Object.keys(rows[0]);
28
+ let labelKey = options?.labelKey;
29
+ if (!labelKey) {
30
+ labelKey = keys.find(
31
+ (k) => rows.some((row) => typeof row[k] === "string" && isNaN(Number(row[k])))
32
+ ) ?? keys[0];
33
+ }
34
+ let seriesKeys = options?.seriesKeys;
35
+ if (!seriesKeys) {
36
+ seriesKeys = keys.filter((k) => {
37
+ if (k === labelKey) return false;
38
+ return rows.every((row) => {
39
+ const v = row[k];
40
+ return typeof v === "number" || typeof v === "string" && !isNaN(Number(v)) || v == null;
41
+ });
42
+ });
43
+ }
44
+ const labels = rows.map((row) => String(row[labelKey] ?? ""));
45
+ const series = seriesKeys.map((key) => ({
46
+ name: key,
47
+ values: rows.map((row) => {
48
+ const v = row[key];
49
+ if (v == null) return 0;
50
+ return typeof v === "number" ? v : Number(v);
51
+ })
52
+ }));
53
+ return { labels, series };
54
+ }
55
+ function fromColumnar(data, options) {
56
+ const keys = Object.keys(data);
57
+ let labelKey = options?.labelKey;
58
+ if (!labelKey) {
59
+ labelKey = keys.find((k) => k === "labels") ?? keys.find((k) => !isNumericArray(data[k])) ?? keys[0];
60
+ }
61
+ let seriesKeys = options?.seriesKeys;
62
+ if (!seriesKeys) {
63
+ seriesKeys = keys.filter((k) => k !== labelKey && isNumericArray(data[k]));
64
+ }
65
+ const labels = (data[labelKey] ?? []).map((v) => String(v));
66
+ const series = seriesKeys.map((key) => ({
67
+ name: key,
68
+ values: (data[key] ?? []).map((v) => Number(v))
69
+ }));
70
+ return { labels, series };
71
+ }
72
+ function toJSON(data, options) {
73
+ const format = options?.format ?? "rows";
74
+ if (format === "columnar") {
75
+ const result = {};
76
+ if (data.labels) {
77
+ result["labels"] = data.labels.map((l) => l instanceof Date ? l.toISOString() : l);
78
+ }
79
+ for (const s of data.series) {
80
+ result[s.name] = [...s.values];
81
+ }
82
+ return result;
83
+ }
84
+ const rowCount = data.series[0]?.values.length ?? 0;
85
+ const rows = [];
86
+ for (let i = 0; i < rowCount; i++) {
87
+ const row = {};
88
+ if (data.labels) {
89
+ const label = data.labels[i];
90
+ row["label"] = label instanceof Date ? label.toISOString() : label;
91
+ }
92
+ for (const s of data.series) {
93
+ row[s.name] = s.values[i];
94
+ }
95
+ rows.push(row);
96
+ }
97
+ return rows;
98
+ }
99
+
100
+ export { fromJSON, toJSON };
101
+ //# sourceMappingURL=index.js.map
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ChartData, Series } from '@chartts/core'\n\nexport interface FromJSONOptions {\n /** Key to use as labels. Default: auto-detect first string-valued key */\n labelKey?: string\n /** Keys to use as series. Default: all numeric-valued keys */\n seriesKeys?: string[]\n}\n\nexport type ToJSONFormat = 'rows' | 'columnar'\n\nexport interface ToJSONOptions {\n /** Output format. Default: 'rows' (array of objects) */\n format?: ToJSONFormat\n}\n\nfunction isChartData(input: unknown): input is ChartData {\n if (typeof input !== 'object' || input === null) return false\n const obj = input as Record<string, unknown>\n return Array.isArray(obj['series']) &&\n obj['series'].length > 0 &&\n typeof (obj['series'] as Record<string, unknown>[])[0]?.['name'] === 'string' &&\n Array.isArray((obj['series'] as Record<string, unknown>[])[0]?.['values'])\n}\n\nfunction isColumnar(input: Record<string, unknown>): boolean {\n const values = Object.values(input)\n return values.length > 0 && values.every(v => Array.isArray(v))\n}\n\nfunction isNumericArray(arr: unknown[]): boolean {\n return arr.length > 0 && arr.every(v => typeof v === 'number' || (typeof v === 'string' && !isNaN(Number(v))))\n}\n\nexport function fromJSON(input: string | unknown, options?: FromJSONOptions): ChartData {\n const data = typeof input === 'string' ? JSON.parse(input) as unknown : input\n\n // Direct ChartData passthrough\n if (isChartData(data)) return data as ChartData\n\n // Array of objects: [{month:\"Jan\", sales:10}, ...]\n if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object' && data[0] !== null) {\n return fromRowArray(data as Record<string, unknown>[], options)\n }\n\n // Columnar: {labels:[\"Jan\"], sales:[10], costs:[5]}\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && isColumnar(data as Record<string, unknown>)) {\n return fromColumnar(data as Record<string, unknown[]>, options)\n }\n\n throw new Error('Unrecognized JSON shape. Expected array of objects, columnar object, or ChartData.')\n}\n\nfunction fromRowArray(rows: Record<string, unknown>[], options?: FromJSONOptions): ChartData {\n if (rows.length === 0) return { series: [] }\n\n const keys = Object.keys(rows[0]!)\n\n // Determine label key\n let labelKey = options?.labelKey\n if (!labelKey) {\n // Auto-detect: first key whose values are not all numbers\n labelKey = keys.find(k =>\n rows.some(row => typeof row[k] === 'string' && isNaN(Number(row[k])))\n ) ?? keys[0]!\n }\n\n // Determine series keys\n let seriesKeys = options?.seriesKeys\n if (!seriesKeys) {\n seriesKeys = keys.filter(k => {\n if (k === labelKey) return false\n return rows.every(row => {\n const v = row[k]\n return typeof v === 'number' || (typeof v === 'string' && !isNaN(Number(v))) || v == null\n })\n })\n }\n\n const labels = rows.map(row => String(row[labelKey!] ?? ''))\n const series: Series[] = seriesKeys.map(key => ({\n name: key,\n values: rows.map(row => {\n const v = row[key]\n if (v == null) return 0\n return typeof v === 'number' ? v : Number(v)\n }),\n }))\n\n return { labels, series }\n}\n\nfunction fromColumnar(data: Record<string, unknown[]>, options?: FromJSONOptions): ChartData {\n const keys = Object.keys(data)\n\n let labelKey = options?.labelKey\n if (!labelKey) {\n // Auto-detect: 'labels' key, or first non-numeric array\n labelKey = keys.find(k => k === 'labels')\n ?? keys.find(k => !isNumericArray(data[k]!))\n ?? keys[0]!\n }\n\n let seriesKeys = options?.seriesKeys\n if (!seriesKeys) {\n seriesKeys = keys.filter(k => k !== labelKey && isNumericArray(data[k]!))\n }\n\n const labels = (data[labelKey] ?? []).map(v => String(v))\n const series: Series[] = seriesKeys.map(key => ({\n name: key,\n values: (data[key] ?? []).map(v => Number(v)),\n }))\n\n return { labels, series }\n}\n\nexport function toJSON(data: ChartData, options?: ToJSONOptions): unknown {\n const format = options?.format ?? 'rows'\n\n if (format === 'columnar') {\n const result: Record<string, unknown[]> = {}\n if (data.labels) {\n result['labels'] = data.labels.map(l => l instanceof Date ? l.toISOString() : l)\n }\n for (const s of data.series) {\n result[s.name] = [...s.values]\n }\n return result\n }\n\n // rows format: array of objects\n const rowCount = data.series[0]?.values.length ?? 0\n const rows: Record<string, unknown>[] = []\n for (let i = 0; i < rowCount; i++) {\n const row: Record<string, unknown> = {}\n if (data.labels) {\n const label = data.labels[i]\n row['label'] = label instanceof Date ? label.toISOString() : label\n }\n for (const s of data.series) {\n row[s.name] = s.values[i]\n }\n rows.push(row)\n }\n return rows\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@chartts/json",
3
+ "version": "0.1.3",
4
+ "description": "JSON import and export for Chartts",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "peerDependencies": {
21
+ "@chartts/core": "0.1.3"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/chartts/chartts"
27
+ },
28
+ "homepage": "https://chartts.com",
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "dev": "tsup --watch"
32
+ }
33
+ }