@portel/photon-core 2.3.0 → 2.5.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/dist/asset-discovery.d.ts +25 -0
- package/dist/asset-discovery.d.ts.map +1 -0
- package/dist/asset-discovery.js +145 -0
- package/dist/asset-discovery.js.map +1 -0
- package/dist/base.d.ts +6 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +11 -1
- package/dist/base.js.map +1 -1
- package/dist/class-detection.d.ts +32 -0
- package/dist/class-detection.d.ts.map +1 -0
- package/dist/class-detection.js +86 -0
- package/dist/class-detection.js.map +1 -0
- package/dist/collections/ReactiveArray.d.ts +97 -0
- package/dist/collections/ReactiveArray.d.ts.map +1 -0
- package/dist/collections/ReactiveArray.js +158 -0
- package/dist/collections/ReactiveArray.js.map +1 -0
- package/dist/collections/ReactiveMap.d.ts +50 -0
- package/dist/collections/ReactiveMap.d.ts.map +1 -0
- package/dist/collections/ReactiveMap.js +71 -0
- package/dist/collections/ReactiveMap.js.map +1 -0
- package/dist/collections/ReactiveSet.d.ts +50 -0
- package/dist/collections/ReactiveSet.d.ts.map +1 -0
- package/dist/collections/ReactiveSet.js +71 -0
- package/dist/collections/ReactiveSet.js.map +1 -0
- package/dist/collections/index.d.ts +44 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +44 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/compiler.d.ts +22 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +48 -0
- package/dist/compiler.js.map +1 -0
- package/dist/env-utils.d.ts +61 -0
- package/dist/env-utils.d.ts.map +1 -0
- package/dist/env-utils.js +171 -0
- package/dist/env-utils.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/mime-types.d.ts +13 -0
- package/dist/mime-types.d.ts.map +1 -0
- package/dist/mime-types.js +47 -0
- package/dist/mime-types.js.map +1 -0
- package/dist/rendering/index.d.ts +49 -0
- package/dist/rendering/index.d.ts.map +1 -1
- package/dist/rendering/index.js +153 -0
- package/dist/rendering/index.js.map +1 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +3 -0
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui-types/Cards.d.ts +139 -0
- package/dist/ui-types/Cards.d.ts.map +1 -0
- package/dist/ui-types/Cards.js +235 -0
- package/dist/ui-types/Cards.js.map +1 -0
- package/dist/ui-types/Chart.d.ts +136 -0
- package/dist/ui-types/Chart.d.ts.map +1 -0
- package/dist/ui-types/Chart.js +188 -0
- package/dist/ui-types/Chart.js.map +1 -0
- package/dist/ui-types/Field.d.ts +342 -0
- package/dist/ui-types/Field.d.ts.map +1 -0
- package/dist/ui-types/Field.js +200 -0
- package/dist/ui-types/Field.js.map +1 -0
- package/dist/ui-types/FieldRenderer.d.ts +32 -0
- package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
- package/dist/ui-types/FieldRenderer.js +277 -0
- package/dist/ui-types/FieldRenderer.js.map +1 -0
- package/dist/ui-types/Form.d.ts +212 -0
- package/dist/ui-types/Form.d.ts.map +1 -0
- package/dist/ui-types/Form.js +278 -0
- package/dist/ui-types/Form.js.map +1 -0
- package/dist/ui-types/Progress.d.ts +130 -0
- package/dist/ui-types/Progress.d.ts.map +1 -0
- package/dist/ui-types/Progress.js +191 -0
- package/dist/ui-types/Progress.js.map +1 -0
- package/dist/ui-types/Stats.d.ts +108 -0
- package/dist/ui-types/Stats.d.ts.map +1 -0
- package/dist/ui-types/Stats.js +162 -0
- package/dist/ui-types/Stats.js.map +1 -0
- package/dist/ui-types/Table.d.ts +206 -0
- package/dist/ui-types/Table.d.ts.map +1 -0
- package/dist/ui-types/Table.js +367 -0
- package/dist/ui-types/Table.js.map +1 -0
- package/dist/ui-types/base.d.ts +17 -0
- package/dist/ui-types/base.d.ts.map +1 -0
- package/dist/ui-types/base.js +18 -0
- package/dist/ui-types/base.js.map +1 -0
- package/dist/ui-types/index.d.ts +42 -0
- package/dist/ui-types/index.d.ts.map +1 -0
- package/dist/ui-types/index.js +50 -0
- package/dist/ui-types/index.js.map +1 -0
- package/dist/validation.d.ts +51 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +249 -0
- package/dist/validation.js.map +1 -0
- package/dist/version-check.d.ts +22 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +91 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +2 -2
- package/src/asset-discovery.ts +161 -0
- package/src/base.ts +13 -1
- package/src/class-detection.ts +94 -0
- package/src/collections/ReactiveArray.ts +179 -0
- package/src/collections/ReactiveMap.ts +81 -0
- package/src/collections/ReactiveSet.ts +81 -0
- package/src/collections/index.ts +44 -0
- package/src/compiler.ts +57 -0
- package/src/env-utils.ts +216 -0
- package/src/index.ts +155 -0
- package/src/mime-types.ts +49 -0
- package/src/rendering/index.ts +197 -0
- package/src/schema-extractor.ts +4 -0
- package/src/types.ts +4 -0
- package/src/ui-types/Cards.ts +286 -0
- package/src/ui-types/Chart.ts +239 -0
- package/src/ui-types/Field.ts +594 -0
- package/src/ui-types/FieldRenderer.ts +364 -0
- package/src/ui-types/Form.ts +363 -0
- package/src/ui-types/Progress.ts +237 -0
- package/src/ui-types/Stats.ts +204 -0
- package/src/ui-types/Table.ts +438 -0
- package/src/ui-types/base.ts +25 -0
- package/src/ui-types/index.ts +96 -0
- package/src/ui-types/ui-types.test.ts +444 -0
- package/src/validation.ts +363 -0
- package/src/version-check.ts +92 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cards - Purpose-driven type for card-based layouts
|
|
3
|
+
*
|
|
4
|
+
* Automatically renders as a grid of cards with customizable fields.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* async products() {
|
|
9
|
+
* return new Cards()
|
|
10
|
+
* .title('Featured Products')
|
|
11
|
+
* .image('imageUrl')
|
|
12
|
+
* .heading('name')
|
|
13
|
+
* .subtitle('category')
|
|
14
|
+
* .description('summary')
|
|
15
|
+
* .badge('status')
|
|
16
|
+
* .items([
|
|
17
|
+
* { name: 'Widget', category: 'Tools', summary: '...', status: 'New', imageUrl: '...' },
|
|
18
|
+
* ]);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { PhotonUIType } from './base.js';
|
|
24
|
+
|
|
25
|
+
export interface CardFieldMapping {
|
|
26
|
+
image?: string;
|
|
27
|
+
heading?: string;
|
|
28
|
+
subtitle?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
badge?: string;
|
|
31
|
+
footer?: string;
|
|
32
|
+
link?: string;
|
|
33
|
+
meta?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CardsOptions {
|
|
37
|
+
title?: string;
|
|
38
|
+
columns?: 1 | 2 | 3 | 4 | 6;
|
|
39
|
+
compact?: boolean;
|
|
40
|
+
clickable?: boolean;
|
|
41
|
+
hoverable?: boolean;
|
|
42
|
+
bordered?: boolean;
|
|
43
|
+
aspectRatio?: 'square' | 'video' | 'portrait' | 'auto';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class Cards extends PhotonUIType {
|
|
47
|
+
readonly _photonType = 'cards' as const;
|
|
48
|
+
|
|
49
|
+
private _items: Record<string, any>[] = [];
|
|
50
|
+
private _fields: CardFieldMapping = {};
|
|
51
|
+
private _options: CardsOptions = {
|
|
52
|
+
columns: 3,
|
|
53
|
+
hoverable: true,
|
|
54
|
+
bordered: true,
|
|
55
|
+
aspectRatio: 'auto',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a new Cards layout
|
|
60
|
+
* @param items Optional initial items
|
|
61
|
+
*/
|
|
62
|
+
constructor(items?: Record<string, any>[]) {
|
|
63
|
+
super();
|
|
64
|
+
if (items) {
|
|
65
|
+
this._items = items;
|
|
66
|
+
this._inferFields(items[0]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set section title
|
|
72
|
+
*/
|
|
73
|
+
title(title: string): this {
|
|
74
|
+
this._options.title = title;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Set field for card image
|
|
80
|
+
*/
|
|
81
|
+
image(field: string): this {
|
|
82
|
+
this._fields.image = field;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Set field for card heading
|
|
88
|
+
*/
|
|
89
|
+
heading(field: string): this {
|
|
90
|
+
this._fields.heading = field;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Set field for card subtitle
|
|
96
|
+
*/
|
|
97
|
+
subtitle(field: string): this {
|
|
98
|
+
this._fields.subtitle = field;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set field for card description
|
|
104
|
+
*/
|
|
105
|
+
description(field: string): this {
|
|
106
|
+
this._fields.description = field;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Set field for badge/tag
|
|
112
|
+
*/
|
|
113
|
+
badge(field: string): this {
|
|
114
|
+
this._fields.badge = field;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Set field for footer text
|
|
120
|
+
*/
|
|
121
|
+
footer(field: string): this {
|
|
122
|
+
this._fields.footer = field;
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Set field for click link
|
|
128
|
+
*/
|
|
129
|
+
link(field: string): this {
|
|
130
|
+
this._fields.link = field;
|
|
131
|
+
this._options.clickable = true;
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Set fields for meta info
|
|
137
|
+
*/
|
|
138
|
+
meta(...fields: string[]): this {
|
|
139
|
+
this._fields.meta = fields;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Set card items
|
|
145
|
+
*/
|
|
146
|
+
items(data: Record<string, any>[]): this {
|
|
147
|
+
this._items = data;
|
|
148
|
+
if (Object.keys(this._fields).length === 0 && data.length > 0) {
|
|
149
|
+
this._inferFields(data[0]);
|
|
150
|
+
}
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Add a single item
|
|
156
|
+
*/
|
|
157
|
+
item(data: Record<string, any>): this {
|
|
158
|
+
this._items.push(data);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Set number of columns
|
|
164
|
+
*/
|
|
165
|
+
columns(count: 1 | 2 | 3 | 4 | 6): this {
|
|
166
|
+
this._options.columns = count;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Use compact card style
|
|
172
|
+
*/
|
|
173
|
+
compact(enabled: boolean = true): this {
|
|
174
|
+
this._options.compact = enabled;
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Enable click interaction
|
|
180
|
+
*/
|
|
181
|
+
clickable(enabled: boolean = true): this {
|
|
182
|
+
this._options.clickable = enabled;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Enable hover effect
|
|
188
|
+
*/
|
|
189
|
+
hoverable(enabled: boolean = true): this {
|
|
190
|
+
this._options.hoverable = enabled;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Show card borders
|
|
196
|
+
*/
|
|
197
|
+
bordered(enabled: boolean = true): this {
|
|
198
|
+
this._options.bordered = enabled;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Set image aspect ratio
|
|
204
|
+
*/
|
|
205
|
+
aspectRatio(ratio: 'square' | 'video' | 'portrait' | 'auto'): this {
|
|
206
|
+
this._options.aspectRatio = ratio;
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Infer field mappings from data
|
|
212
|
+
*/
|
|
213
|
+
private _inferFields(item: Record<string, any>): void {
|
|
214
|
+
const keys = Object.keys(item);
|
|
215
|
+
|
|
216
|
+
// Look for common field names
|
|
217
|
+
const findField = (patterns: string[]): string | undefined => {
|
|
218
|
+
return keys.find(k => patterns.some(p => k.toLowerCase().includes(p)));
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this._fields.image = findField(['image', 'img', 'photo', 'picture', 'avatar', 'thumbnail']);
|
|
222
|
+
this._fields.heading = findField(['name', 'title', 'heading']);
|
|
223
|
+
this._fields.subtitle = findField(['subtitle', 'category', 'type']);
|
|
224
|
+
this._fields.description = findField(['description', 'summary', 'text', 'body', 'content']);
|
|
225
|
+
this._fields.badge = findField(['status', 'badge', 'tag', 'label']);
|
|
226
|
+
this._fields.link = findField(['url', 'link', 'href']);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get item count
|
|
231
|
+
*/
|
|
232
|
+
get length(): number {
|
|
233
|
+
return this._items.length;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
toJSON() {
|
|
237
|
+
return {
|
|
238
|
+
_photonType: this._photonType,
|
|
239
|
+
items: this._items,
|
|
240
|
+
fields: this._fields,
|
|
241
|
+
options: this._options,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Render as plain text for MCP clients
|
|
247
|
+
*/
|
|
248
|
+
toString(): string {
|
|
249
|
+
const lines: string[] = [];
|
|
250
|
+
|
|
251
|
+
if (this._options.title) {
|
|
252
|
+
lines.push(`## ${this._options.title}`, '');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (this._items.length === 0) {
|
|
256
|
+
lines.push('(No items)');
|
|
257
|
+
return lines.join('\n');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const item of this._items) {
|
|
261
|
+
// Heading
|
|
262
|
+
const heading = this._fields.heading ? item[this._fields.heading] : null;
|
|
263
|
+
if (heading) lines.push(`### ${heading}`);
|
|
264
|
+
|
|
265
|
+
// Subtitle
|
|
266
|
+
const subtitle = this._fields.subtitle ? item[this._fields.subtitle] : null;
|
|
267
|
+
if (subtitle) lines.push(`*${subtitle}*`);
|
|
268
|
+
|
|
269
|
+
// Badge
|
|
270
|
+
const badge = this._fields.badge ? item[this._fields.badge] : null;
|
|
271
|
+
if (badge) lines.push(`[${badge}]`);
|
|
272
|
+
|
|
273
|
+
// Description
|
|
274
|
+
const desc = this._fields.description ? item[this._fields.description] : null;
|
|
275
|
+
if (desc) lines.push('', desc);
|
|
276
|
+
|
|
277
|
+
// Link
|
|
278
|
+
const link = this._fields.link ? item[this._fields.link] : null;
|
|
279
|
+
if (link) lines.push('', `Link: ${link}`);
|
|
280
|
+
|
|
281
|
+
lines.push('');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return lines.join('\n').trim();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart - Purpose-driven type for data visualization
|
|
3
|
+
*
|
|
4
|
+
* Automatically renders as a chart with the specified type.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* async revenue() {
|
|
9
|
+
* return new Chart('line')
|
|
10
|
+
* .title('Monthly Revenue')
|
|
11
|
+
* .labels(['Jan', 'Feb', 'Mar', 'Apr'])
|
|
12
|
+
* .series('Revenue', [1000, 1500, 1200, 1800])
|
|
13
|
+
* .series('Costs', [800, 900, 850, 950]);
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* async distribution() {
|
|
17
|
+
* return new Chart('pie')
|
|
18
|
+
* .title('User Distribution')
|
|
19
|
+
* .data([
|
|
20
|
+
* { label: 'Free', value: 1000 },
|
|
21
|
+
* { label: 'Pro', value: 500 },
|
|
22
|
+
* { label: 'Enterprise', value: 100 },
|
|
23
|
+
* ]);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { PhotonUIType } from './base.js';
|
|
29
|
+
|
|
30
|
+
export type ChartType = 'line' | 'bar' | 'pie' | 'doughnut' | 'area' | 'scatter' | 'radar';
|
|
31
|
+
|
|
32
|
+
export interface ChartSeries {
|
|
33
|
+
name: string;
|
|
34
|
+
data: number[];
|
|
35
|
+
color?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ChartDataPoint {
|
|
39
|
+
label: string;
|
|
40
|
+
value: number;
|
|
41
|
+
color?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ChartOptions {
|
|
45
|
+
title?: string;
|
|
46
|
+
subtitle?: string;
|
|
47
|
+
legend?: boolean | 'top' | 'bottom' | 'left' | 'right';
|
|
48
|
+
stacked?: boolean;
|
|
49
|
+
showGrid?: boolean;
|
|
50
|
+
showValues?: boolean;
|
|
51
|
+
animate?: boolean;
|
|
52
|
+
height?: number;
|
|
53
|
+
colors?: string[];
|
|
54
|
+
xAxisLabel?: string;
|
|
55
|
+
yAxisLabel?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class Chart extends PhotonUIType {
|
|
59
|
+
readonly _photonType = 'chart' as const;
|
|
60
|
+
|
|
61
|
+
private _type: ChartType;
|
|
62
|
+
private _labels: string[] = [];
|
|
63
|
+
private _series: ChartSeries[] = [];
|
|
64
|
+
private _data: ChartDataPoint[] = []; // For pie/doughnut
|
|
65
|
+
private _options: ChartOptions = {
|
|
66
|
+
legend: true,
|
|
67
|
+
showGrid: true,
|
|
68
|
+
animate: true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a new Chart
|
|
73
|
+
* @param type Chart type (line, bar, pie, etc.)
|
|
74
|
+
*/
|
|
75
|
+
constructor(type: ChartType = 'line') {
|
|
76
|
+
super();
|
|
77
|
+
this._type = type;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set chart title
|
|
82
|
+
*/
|
|
83
|
+
title(title: string): this {
|
|
84
|
+
this._options.title = title;
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set chart subtitle
|
|
90
|
+
*/
|
|
91
|
+
subtitle(subtitle: string): this {
|
|
92
|
+
this._options.subtitle = subtitle;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set X-axis labels (for line, bar, area charts)
|
|
98
|
+
*/
|
|
99
|
+
labels(labels: string[]): this {
|
|
100
|
+
this._labels = labels;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Add a data series (for line, bar, area charts)
|
|
106
|
+
*/
|
|
107
|
+
series(name: string, data: number[], color?: string): this {
|
|
108
|
+
this._series.push({ name, data, color });
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Set pie/doughnut data points
|
|
114
|
+
*/
|
|
115
|
+
data(points: ChartDataPoint[] | Array<{ label: string; value: number }>): this {
|
|
116
|
+
this._data = points;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Configure legend
|
|
122
|
+
*/
|
|
123
|
+
legend(position: boolean | 'top' | 'bottom' | 'left' | 'right' = true): this {
|
|
124
|
+
this._options.legend = position;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Enable stacked mode (for bar/area charts)
|
|
130
|
+
*/
|
|
131
|
+
stacked(enabled: boolean = true): this {
|
|
132
|
+
this._options.stacked = enabled;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Show/hide grid lines
|
|
138
|
+
*/
|
|
139
|
+
grid(enabled: boolean = true): this {
|
|
140
|
+
this._options.showGrid = enabled;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Show values on chart
|
|
146
|
+
*/
|
|
147
|
+
showValues(enabled: boolean = true): this {
|
|
148
|
+
this._options.showValues = enabled;
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Enable/disable animations
|
|
154
|
+
*/
|
|
155
|
+
animate(enabled: boolean = true): this {
|
|
156
|
+
this._options.animate = enabled;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Set chart height
|
|
162
|
+
*/
|
|
163
|
+
height(pixels: number): this {
|
|
164
|
+
this._options.height = pixels;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Set color palette
|
|
170
|
+
*/
|
|
171
|
+
colors(colors: string[]): this {
|
|
172
|
+
this._options.colors = colors;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Set X-axis label
|
|
178
|
+
*/
|
|
179
|
+
xAxis(label: string): this {
|
|
180
|
+
this._options.xAxisLabel = label;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Set Y-axis label
|
|
186
|
+
*/
|
|
187
|
+
yAxis(label: string): this {
|
|
188
|
+
this._options.yAxisLabel = label;
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
toJSON() {
|
|
193
|
+
return {
|
|
194
|
+
_photonType: this._photonType,
|
|
195
|
+
chartType: this._type,
|
|
196
|
+
labels: this._labels,
|
|
197
|
+
series: this._series,
|
|
198
|
+
data: this._data,
|
|
199
|
+
options: this._options,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Render as plain text for MCP clients
|
|
205
|
+
*/
|
|
206
|
+
toString(): string {
|
|
207
|
+
const lines: string[] = [];
|
|
208
|
+
|
|
209
|
+
if (this._options.title) {
|
|
210
|
+
lines.push(`## ${this._options.title}`);
|
|
211
|
+
if (this._options.subtitle) lines.push(this._options.subtitle);
|
|
212
|
+
lines.push('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Pie/doughnut: show as list
|
|
216
|
+
if ((this._type === 'pie' || this._type === 'doughnut') && this._data.length > 0) {
|
|
217
|
+
const total = this._data.reduce((sum, d) => sum + d.value, 0);
|
|
218
|
+
for (const d of this._data) {
|
|
219
|
+
const pct = total > 0 ? ((d.value / total) * 100).toFixed(1) : '0';
|
|
220
|
+
lines.push(`- ${d.label}: ${d.value} (${pct}%)`);
|
|
221
|
+
}
|
|
222
|
+
return lines.join('\n');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Series charts: show as table
|
|
226
|
+
if (this._series.length > 0 && this._labels.length > 0) {
|
|
227
|
+
const headers = ['', ...this._series.map(s => s.name)];
|
|
228
|
+
lines.push('| ' + headers.join(' | ') + ' |');
|
|
229
|
+
lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < this._labels.length; i++) {
|
|
232
|
+
const row = [this._labels[i], ...this._series.map(s => String(s.data[i] ?? ''))];
|
|
233
|
+
lines.push('| ' + row.join(' | ') + ' |');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return lines.join('\n');
|
|
238
|
+
}
|
|
239
|
+
}
|