@chartts/csv 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 +21 -0
- package/dist/index.cjs +129 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
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,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
function detectDelimiter(text) {
|
|
5
|
+
const firstLine = text.split("\n")[0] ?? "";
|
|
6
|
+
const tabs = (firstLine.match(/\t/g) ?? []).length;
|
|
7
|
+
const commas = (firstLine.match(/,/g) ?? []).length;
|
|
8
|
+
return tabs > commas ? " " : ",";
|
|
9
|
+
}
|
|
10
|
+
function parseCSVLine(line, delimiter) {
|
|
11
|
+
const fields = [];
|
|
12
|
+
let current = "";
|
|
13
|
+
let inQuotes = false;
|
|
14
|
+
for (let i = 0; i < line.length; i++) {
|
|
15
|
+
const ch = line[i];
|
|
16
|
+
if (inQuotes) {
|
|
17
|
+
if (ch === '"') {
|
|
18
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
19
|
+
current += '"';
|
|
20
|
+
i++;
|
|
21
|
+
} else {
|
|
22
|
+
inQuotes = false;
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
current += ch;
|
|
26
|
+
}
|
|
27
|
+
} else if (ch === '"') {
|
|
28
|
+
inQuotes = true;
|
|
29
|
+
} else if (ch === delimiter) {
|
|
30
|
+
fields.push(current);
|
|
31
|
+
current = "";
|
|
32
|
+
} else {
|
|
33
|
+
current += ch;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
fields.push(current);
|
|
37
|
+
return fields;
|
|
38
|
+
}
|
|
39
|
+
function isNumeric(value) {
|
|
40
|
+
if (value === "") return false;
|
|
41
|
+
return !isNaN(Number(value));
|
|
42
|
+
}
|
|
43
|
+
function fromCSV(text, options) {
|
|
44
|
+
const delimiter = options?.delimiter ?? detectDelimiter(text);
|
|
45
|
+
const hasHeaders = options?.headers ?? true;
|
|
46
|
+
const lines = text.trim().split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
47
|
+
if (lines.length === 0) return { series: [] };
|
|
48
|
+
const rows = lines.map((line) => parseCSVLine(line, delimiter));
|
|
49
|
+
const headerRow = hasHeaders ? rows[0] : void 0;
|
|
50
|
+
const dataRows = hasHeaders ? rows.slice(1) : rows;
|
|
51
|
+
if (dataRows.length === 0) return { series: [] };
|
|
52
|
+
const colCount = rows[0].length;
|
|
53
|
+
let labelIdx;
|
|
54
|
+
if (options?.labelColumn != null) {
|
|
55
|
+
if (typeof options.labelColumn === "string" && headerRow) {
|
|
56
|
+
labelIdx = headerRow.indexOf(options.labelColumn);
|
|
57
|
+
if (labelIdx === -1) labelIdx = 0;
|
|
58
|
+
} else {
|
|
59
|
+
labelIdx = Number(options.labelColumn);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
labelIdx = 0;
|
|
63
|
+
if (dataRows.length > 0) {
|
|
64
|
+
for (let c = 0; c < colCount; c++) {
|
|
65
|
+
if (!isNumeric(dataRows[0][c] ?? "")) {
|
|
66
|
+
labelIdx = c;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let seriesIndices;
|
|
73
|
+
if (options?.seriesColumns) {
|
|
74
|
+
seriesIndices = options.seriesColumns.map((col) => {
|
|
75
|
+
if (typeof col === "string" && headerRow) {
|
|
76
|
+
const idx = headerRow.indexOf(col);
|
|
77
|
+
return idx === -1 ? -1 : idx;
|
|
78
|
+
}
|
|
79
|
+
return Number(col);
|
|
80
|
+
}).filter((i) => i >= 0 && i !== labelIdx);
|
|
81
|
+
} else {
|
|
82
|
+
seriesIndices = [];
|
|
83
|
+
for (let c = 0; c < colCount; c++) {
|
|
84
|
+
if (c === labelIdx) continue;
|
|
85
|
+
const allNumeric = dataRows.every((row) => {
|
|
86
|
+
const val = row[c] ?? "";
|
|
87
|
+
return val === "" || isNumeric(val);
|
|
88
|
+
});
|
|
89
|
+
if (allNumeric) seriesIndices.push(c);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const labels = dataRows.map((row) => row[labelIdx] ?? "");
|
|
93
|
+
const series = seriesIndices.map((colIdx) => ({
|
|
94
|
+
name: headerRow?.[colIdx] ?? `Column ${colIdx}`,
|
|
95
|
+
values: dataRows.map((row) => {
|
|
96
|
+
const val = row[colIdx] ?? "";
|
|
97
|
+
return val === "" ? 0 : Number(val);
|
|
98
|
+
})
|
|
99
|
+
}));
|
|
100
|
+
return { labels, series };
|
|
101
|
+
}
|
|
102
|
+
function toCSV(data, options) {
|
|
103
|
+
const delimiter = options?.delimiter ?? ",";
|
|
104
|
+
const includeHeaders = options?.headers ?? true;
|
|
105
|
+
const lines = [];
|
|
106
|
+
if (includeHeaders) {
|
|
107
|
+
const header = ["Label", ...data.series.map((s) => s.name)];
|
|
108
|
+
lines.push(header.map((h) => escapeCSVField(h, delimiter)).join(delimiter));
|
|
109
|
+
}
|
|
110
|
+
const rowCount = data.series[0]?.values.length ?? 0;
|
|
111
|
+
for (let i = 0; i < rowCount; i++) {
|
|
112
|
+
const label = data.labels?.[i] ?? "";
|
|
113
|
+
const labelStr = label instanceof Date ? label.toISOString() : String(label);
|
|
114
|
+
const values = data.series.map((s) => String(s.values[i] ?? ""));
|
|
115
|
+
lines.push([escapeCSVField(labelStr, delimiter), ...values].join(delimiter));
|
|
116
|
+
}
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
function escapeCSVField(field, delimiter) {
|
|
120
|
+
if (field.includes(delimiter) || field.includes('"') || field.includes("\n")) {
|
|
121
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
122
|
+
}
|
|
123
|
+
return field;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
exports.fromCSV = fromCSV;
|
|
127
|
+
exports.toCSV = toCSV;
|
|
128
|
+
//# sourceMappingURL=index.cjs.map
|
|
129
|
+
//# 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 FromCSVOptions {\n /** Column delimiter. Auto-detects tab vs comma if omitted. */\n delimiter?: string\n /** Whether the first row contains headers. Default: true */\n headers?: boolean\n /** Which column to use as labels (index or header name). Default: first column (0) */\n labelColumn?: number | string\n /** Which columns to use as series (indices or header names). Default: all numeric columns */\n seriesColumns?: (number | string)[]\n}\n\nexport interface ToCSVOptions {\n /** Column delimiter. Default: ',' */\n delimiter?: string\n /** Include a header row. Default: true */\n headers?: boolean\n}\n\nfunction detectDelimiter(text: string): string {\n const firstLine = text.split('\\n')[0] ?? ''\n const tabs = (firstLine.match(/\\t/g) ?? []).length\n const commas = (firstLine.match(/,/g) ?? []).length\n return tabs > commas ? '\\t' : ','\n}\n\nfunction parseCSVLine(line: string, delimiter: string): string[] {\n const fields: string[] = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n if (inQuotes) {\n if (ch === '\"') {\n if (i + 1 < line.length && line[i + 1] === '\"') {\n current += '\"'\n i++\n } else {\n inQuotes = false\n }\n } else {\n current += ch\n }\n } else if (ch === '\"') {\n inQuotes = true\n } else if (ch === delimiter) {\n fields.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n fields.push(current)\n return fields\n}\n\nfunction isNumeric(value: string): boolean {\n if (value === '') return false\n return !isNaN(Number(value))\n}\n\nexport function fromCSV(text: string, options?: FromCSVOptions): ChartData {\n const delimiter = options?.delimiter ?? detectDelimiter(text)\n const hasHeaders = options?.headers ?? true\n\n const lines = text.trim().split(/\\r?\\n/).filter(l => l.trim() !== '')\n if (lines.length === 0) return { series: [] }\n\n const rows = lines.map(line => parseCSVLine(line, delimiter))\n const headerRow = hasHeaders ? rows[0]! : undefined\n const dataRows = hasHeaders ? rows.slice(1) : rows\n\n if (dataRows.length === 0) return { series: [] }\n\n const colCount = rows[0]!.length\n\n // Resolve labelColumn index\n let labelIdx: number\n if (options?.labelColumn != null) {\n if (typeof options.labelColumn === 'string' && headerRow) {\n labelIdx = headerRow.indexOf(options.labelColumn)\n if (labelIdx === -1) labelIdx = 0\n } else {\n labelIdx = Number(options.labelColumn)\n }\n } else {\n // Default: first non-numeric column, or column 0\n labelIdx = 0\n if (dataRows.length > 0) {\n for (let c = 0; c < colCount; c++) {\n if (!isNumeric(dataRows[0]![c] ?? '')) {\n labelIdx = c\n break\n }\n }\n }\n }\n\n // Resolve series column indices\n let seriesIndices: number[]\n if (options?.seriesColumns) {\n seriesIndices = options.seriesColumns.map(col => {\n if (typeof col === 'string' && headerRow) {\n const idx = headerRow.indexOf(col)\n return idx === -1 ? -1 : idx\n }\n return Number(col)\n }).filter(i => i >= 0 && i !== labelIdx)\n } else {\n // Auto-detect: all columns with numeric data (excluding label column)\n seriesIndices = []\n for (let c = 0; c < colCount; c++) {\n if (c === labelIdx) continue\n const allNumeric = dataRows.every(row => {\n const val = row[c] ?? ''\n return val === '' || isNumeric(val)\n })\n if (allNumeric) seriesIndices.push(c)\n }\n }\n\n const labels = dataRows.map(row => row[labelIdx] ?? '')\n const series: Series[] = seriesIndices.map(colIdx => ({\n name: headerRow?.[colIdx] ?? `Column ${colIdx}`,\n values: dataRows.map(row => {\n const val = row[colIdx] ?? ''\n return val === '' ? 0 : Number(val)\n }),\n }))\n\n return { labels, series }\n}\n\nexport function toCSV(data: ChartData, options?: ToCSVOptions): string {\n const delimiter = options?.delimiter ?? ','\n const includeHeaders = options?.headers ?? true\n\n const lines: string[] = []\n\n if (includeHeaders) {\n const header = ['Label', ...data.series.map(s => s.name)]\n lines.push(header.map(h => escapeCSVField(h, delimiter)).join(delimiter))\n }\n\n const rowCount = data.series[0]?.values.length ?? 0\n for (let i = 0; i < rowCount; i++) {\n const label = data.labels?.[i] ?? ''\n const labelStr = label instanceof Date ? label.toISOString() : String(label)\n const values = data.series.map(s => String(s.values[i] ?? ''))\n lines.push([escapeCSVField(labelStr, delimiter), ...values].join(delimiter))\n }\n\n return lines.join('\\n')\n}\n\nfunction escapeCSVField(field: string, delimiter: string): string {\n if (field.includes(delimiter) || field.includes('\"') || field.includes('\\n')) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`\n }\n return field\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChartData } from '@chartts/core';
|
|
2
|
+
|
|
3
|
+
interface FromCSVOptions {
|
|
4
|
+
/** Column delimiter. Auto-detects tab vs comma if omitted. */
|
|
5
|
+
delimiter?: string;
|
|
6
|
+
/** Whether the first row contains headers. Default: true */
|
|
7
|
+
headers?: boolean;
|
|
8
|
+
/** Which column to use as labels (index or header name). Default: first column (0) */
|
|
9
|
+
labelColumn?: number | string;
|
|
10
|
+
/** Which columns to use as series (indices or header names). Default: all numeric columns */
|
|
11
|
+
seriesColumns?: (number | string)[];
|
|
12
|
+
}
|
|
13
|
+
interface ToCSVOptions {
|
|
14
|
+
/** Column delimiter. Default: ',' */
|
|
15
|
+
delimiter?: string;
|
|
16
|
+
/** Include a header row. Default: true */
|
|
17
|
+
headers?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function fromCSV(text: string, options?: FromCSVOptions): ChartData;
|
|
20
|
+
declare function toCSV(data: ChartData, options?: ToCSVOptions): string;
|
|
21
|
+
|
|
22
|
+
export { type FromCSVOptions, type ToCSVOptions, fromCSV, toCSV };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ChartData } from '@chartts/core';
|
|
2
|
+
|
|
3
|
+
interface FromCSVOptions {
|
|
4
|
+
/** Column delimiter. Auto-detects tab vs comma if omitted. */
|
|
5
|
+
delimiter?: string;
|
|
6
|
+
/** Whether the first row contains headers. Default: true */
|
|
7
|
+
headers?: boolean;
|
|
8
|
+
/** Which column to use as labels (index or header name). Default: first column (0) */
|
|
9
|
+
labelColumn?: number | string;
|
|
10
|
+
/** Which columns to use as series (indices or header names). Default: all numeric columns */
|
|
11
|
+
seriesColumns?: (number | string)[];
|
|
12
|
+
}
|
|
13
|
+
interface ToCSVOptions {
|
|
14
|
+
/** Column delimiter. Default: ',' */
|
|
15
|
+
delimiter?: string;
|
|
16
|
+
/** Include a header row. Default: true */
|
|
17
|
+
headers?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function fromCSV(text: string, options?: FromCSVOptions): ChartData;
|
|
20
|
+
declare function toCSV(data: ChartData, options?: ToCSVOptions): string;
|
|
21
|
+
|
|
22
|
+
export { type FromCSVOptions, type ToCSVOptions, fromCSV, toCSV };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function detectDelimiter(text) {
|
|
3
|
+
const firstLine = text.split("\n")[0] ?? "";
|
|
4
|
+
const tabs = (firstLine.match(/\t/g) ?? []).length;
|
|
5
|
+
const commas = (firstLine.match(/,/g) ?? []).length;
|
|
6
|
+
return tabs > commas ? " " : ",";
|
|
7
|
+
}
|
|
8
|
+
function parseCSVLine(line, delimiter) {
|
|
9
|
+
const fields = [];
|
|
10
|
+
let current = "";
|
|
11
|
+
let inQuotes = false;
|
|
12
|
+
for (let i = 0; i < line.length; i++) {
|
|
13
|
+
const ch = line[i];
|
|
14
|
+
if (inQuotes) {
|
|
15
|
+
if (ch === '"') {
|
|
16
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
17
|
+
current += '"';
|
|
18
|
+
i++;
|
|
19
|
+
} else {
|
|
20
|
+
inQuotes = false;
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
current += ch;
|
|
24
|
+
}
|
|
25
|
+
} else if (ch === '"') {
|
|
26
|
+
inQuotes = true;
|
|
27
|
+
} else if (ch === delimiter) {
|
|
28
|
+
fields.push(current);
|
|
29
|
+
current = "";
|
|
30
|
+
} else {
|
|
31
|
+
current += ch;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
fields.push(current);
|
|
35
|
+
return fields;
|
|
36
|
+
}
|
|
37
|
+
function isNumeric(value) {
|
|
38
|
+
if (value === "") return false;
|
|
39
|
+
return !isNaN(Number(value));
|
|
40
|
+
}
|
|
41
|
+
function fromCSV(text, options) {
|
|
42
|
+
const delimiter = options?.delimiter ?? detectDelimiter(text);
|
|
43
|
+
const hasHeaders = options?.headers ?? true;
|
|
44
|
+
const lines = text.trim().split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
45
|
+
if (lines.length === 0) return { series: [] };
|
|
46
|
+
const rows = lines.map((line) => parseCSVLine(line, delimiter));
|
|
47
|
+
const headerRow = hasHeaders ? rows[0] : void 0;
|
|
48
|
+
const dataRows = hasHeaders ? rows.slice(1) : rows;
|
|
49
|
+
if (dataRows.length === 0) return { series: [] };
|
|
50
|
+
const colCount = rows[0].length;
|
|
51
|
+
let labelIdx;
|
|
52
|
+
if (options?.labelColumn != null) {
|
|
53
|
+
if (typeof options.labelColumn === "string" && headerRow) {
|
|
54
|
+
labelIdx = headerRow.indexOf(options.labelColumn);
|
|
55
|
+
if (labelIdx === -1) labelIdx = 0;
|
|
56
|
+
} else {
|
|
57
|
+
labelIdx = Number(options.labelColumn);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
labelIdx = 0;
|
|
61
|
+
if (dataRows.length > 0) {
|
|
62
|
+
for (let c = 0; c < colCount; c++) {
|
|
63
|
+
if (!isNumeric(dataRows[0][c] ?? "")) {
|
|
64
|
+
labelIdx = c;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let seriesIndices;
|
|
71
|
+
if (options?.seriesColumns) {
|
|
72
|
+
seriesIndices = options.seriesColumns.map((col) => {
|
|
73
|
+
if (typeof col === "string" && headerRow) {
|
|
74
|
+
const idx = headerRow.indexOf(col);
|
|
75
|
+
return idx === -1 ? -1 : idx;
|
|
76
|
+
}
|
|
77
|
+
return Number(col);
|
|
78
|
+
}).filter((i) => i >= 0 && i !== labelIdx);
|
|
79
|
+
} else {
|
|
80
|
+
seriesIndices = [];
|
|
81
|
+
for (let c = 0; c < colCount; c++) {
|
|
82
|
+
if (c === labelIdx) continue;
|
|
83
|
+
const allNumeric = dataRows.every((row) => {
|
|
84
|
+
const val = row[c] ?? "";
|
|
85
|
+
return val === "" || isNumeric(val);
|
|
86
|
+
});
|
|
87
|
+
if (allNumeric) seriesIndices.push(c);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const labels = dataRows.map((row) => row[labelIdx] ?? "");
|
|
91
|
+
const series = seriesIndices.map((colIdx) => ({
|
|
92
|
+
name: headerRow?.[colIdx] ?? `Column ${colIdx}`,
|
|
93
|
+
values: dataRows.map((row) => {
|
|
94
|
+
const val = row[colIdx] ?? "";
|
|
95
|
+
return val === "" ? 0 : Number(val);
|
|
96
|
+
})
|
|
97
|
+
}));
|
|
98
|
+
return { labels, series };
|
|
99
|
+
}
|
|
100
|
+
function toCSV(data, options) {
|
|
101
|
+
const delimiter = options?.delimiter ?? ",";
|
|
102
|
+
const includeHeaders = options?.headers ?? true;
|
|
103
|
+
const lines = [];
|
|
104
|
+
if (includeHeaders) {
|
|
105
|
+
const header = ["Label", ...data.series.map((s) => s.name)];
|
|
106
|
+
lines.push(header.map((h) => escapeCSVField(h, delimiter)).join(delimiter));
|
|
107
|
+
}
|
|
108
|
+
const rowCount = data.series[0]?.values.length ?? 0;
|
|
109
|
+
for (let i = 0; i < rowCount; i++) {
|
|
110
|
+
const label = data.labels?.[i] ?? "";
|
|
111
|
+
const labelStr = label instanceof Date ? label.toISOString() : String(label);
|
|
112
|
+
const values = data.series.map((s) => String(s.values[i] ?? ""));
|
|
113
|
+
lines.push([escapeCSVField(labelStr, delimiter), ...values].join(delimiter));
|
|
114
|
+
}
|
|
115
|
+
return lines.join("\n");
|
|
116
|
+
}
|
|
117
|
+
function escapeCSVField(field, delimiter) {
|
|
118
|
+
if (field.includes(delimiter) || field.includes('"') || field.includes("\n")) {
|
|
119
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
120
|
+
}
|
|
121
|
+
return field;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { fromCSV, toCSV };
|
|
125
|
+
//# sourceMappingURL=index.js.map
|
|
126
|
+
//# 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 FromCSVOptions {\n /** Column delimiter. Auto-detects tab vs comma if omitted. */\n delimiter?: string\n /** Whether the first row contains headers. Default: true */\n headers?: boolean\n /** Which column to use as labels (index or header name). Default: first column (0) */\n labelColumn?: number | string\n /** Which columns to use as series (indices or header names). Default: all numeric columns */\n seriesColumns?: (number | string)[]\n}\n\nexport interface ToCSVOptions {\n /** Column delimiter. Default: ',' */\n delimiter?: string\n /** Include a header row. Default: true */\n headers?: boolean\n}\n\nfunction detectDelimiter(text: string): string {\n const firstLine = text.split('\\n')[0] ?? ''\n const tabs = (firstLine.match(/\\t/g) ?? []).length\n const commas = (firstLine.match(/,/g) ?? []).length\n return tabs > commas ? '\\t' : ','\n}\n\nfunction parseCSVLine(line: string, delimiter: string): string[] {\n const fields: string[] = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const ch = line[i]!\n if (inQuotes) {\n if (ch === '\"') {\n if (i + 1 < line.length && line[i + 1] === '\"') {\n current += '\"'\n i++\n } else {\n inQuotes = false\n }\n } else {\n current += ch\n }\n } else if (ch === '\"') {\n inQuotes = true\n } else if (ch === delimiter) {\n fields.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n fields.push(current)\n return fields\n}\n\nfunction isNumeric(value: string): boolean {\n if (value === '') return false\n return !isNaN(Number(value))\n}\n\nexport function fromCSV(text: string, options?: FromCSVOptions): ChartData {\n const delimiter = options?.delimiter ?? detectDelimiter(text)\n const hasHeaders = options?.headers ?? true\n\n const lines = text.trim().split(/\\r?\\n/).filter(l => l.trim() !== '')\n if (lines.length === 0) return { series: [] }\n\n const rows = lines.map(line => parseCSVLine(line, delimiter))\n const headerRow = hasHeaders ? rows[0]! : undefined\n const dataRows = hasHeaders ? rows.slice(1) : rows\n\n if (dataRows.length === 0) return { series: [] }\n\n const colCount = rows[0]!.length\n\n // Resolve labelColumn index\n let labelIdx: number\n if (options?.labelColumn != null) {\n if (typeof options.labelColumn === 'string' && headerRow) {\n labelIdx = headerRow.indexOf(options.labelColumn)\n if (labelIdx === -1) labelIdx = 0\n } else {\n labelIdx = Number(options.labelColumn)\n }\n } else {\n // Default: first non-numeric column, or column 0\n labelIdx = 0\n if (dataRows.length > 0) {\n for (let c = 0; c < colCount; c++) {\n if (!isNumeric(dataRows[0]![c] ?? '')) {\n labelIdx = c\n break\n }\n }\n }\n }\n\n // Resolve series column indices\n let seriesIndices: number[]\n if (options?.seriesColumns) {\n seriesIndices = options.seriesColumns.map(col => {\n if (typeof col === 'string' && headerRow) {\n const idx = headerRow.indexOf(col)\n return idx === -1 ? -1 : idx\n }\n return Number(col)\n }).filter(i => i >= 0 && i !== labelIdx)\n } else {\n // Auto-detect: all columns with numeric data (excluding label column)\n seriesIndices = []\n for (let c = 0; c < colCount; c++) {\n if (c === labelIdx) continue\n const allNumeric = dataRows.every(row => {\n const val = row[c] ?? ''\n return val === '' || isNumeric(val)\n })\n if (allNumeric) seriesIndices.push(c)\n }\n }\n\n const labels = dataRows.map(row => row[labelIdx] ?? '')\n const series: Series[] = seriesIndices.map(colIdx => ({\n name: headerRow?.[colIdx] ?? `Column ${colIdx}`,\n values: dataRows.map(row => {\n const val = row[colIdx] ?? ''\n return val === '' ? 0 : Number(val)\n }),\n }))\n\n return { labels, series }\n}\n\nexport function toCSV(data: ChartData, options?: ToCSVOptions): string {\n const delimiter = options?.delimiter ?? ','\n const includeHeaders = options?.headers ?? true\n\n const lines: string[] = []\n\n if (includeHeaders) {\n const header = ['Label', ...data.series.map(s => s.name)]\n lines.push(header.map(h => escapeCSVField(h, delimiter)).join(delimiter))\n }\n\n const rowCount = data.series[0]?.values.length ?? 0\n for (let i = 0; i < rowCount; i++) {\n const label = data.labels?.[i] ?? ''\n const labelStr = label instanceof Date ? label.toISOString() : String(label)\n const values = data.series.map(s => String(s.values[i] ?? ''))\n lines.push([escapeCSVField(labelStr, delimiter), ...values].join(delimiter))\n }\n\n return lines.join('\\n')\n}\n\nfunction escapeCSVField(field: string, delimiter: string): string {\n if (field.includes(delimiter) || field.includes('\"') || field.includes('\\n')) {\n return `\"${field.replace(/\"/g, '\"\"')}\"`\n }\n return field\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chartts/csv",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "CSV/TSV 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
|
+
}
|