@casualoffice/sheets 0.17.0 → 0.19.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/chrome.cjs +3339 -244
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.js +3298 -203
- package/dist/chrome.js.map +1 -1
- package/dist/embed/embed-runtime.js +203 -157
- package/dist/index.cjs +5971 -1572
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5961 -1548
- package/dist/index.js.map +1 -1
- package/dist/sheets.cjs +4937 -538
- package/dist/sheets.cjs.map +1 -1
- package/dist/sheets.js +4945 -532
- package/dist/sheets.js.map +1 -1
- package/package.json +8 -7
- package/src/charts/ChartContextMenu.tsx +264 -0
- package/src/charts/ChartLayer.tsx +333 -0
- package/src/charts/ChartOverlay.tsx +293 -0
- package/src/charts/ChartsPanel.tsx +211 -0
- package/src/charts/FormatChartDialog.tsx +419 -0
- package/src/charts/InsertChartDialog.tsx +478 -0
- package/src/charts/build-option.ts +476 -0
- package/src/charts/charts-context.tsx +205 -0
- package/src/charts/echarts-init.ts +58 -0
- package/src/charts/hit-test.ts +130 -0
- package/src/charts/insert-chart.ts +106 -0
- package/src/charts/naming.ts +38 -0
- package/src/charts/render-to-png.ts +117 -0
- package/src/charts/resources.ts +108 -0
- package/src/charts/types.ts +239 -0
- package/src/charts/univer-dom.ts +102 -0
- package/src/chrome/CommentsPanel.tsx +427 -0
- package/src/chrome/ConditionalFormattingDialog.tsx +534 -0
- package/src/chrome/CustomSortDialog.tsx +357 -0
- package/src/chrome/DataValidationDialog.tsx +536 -0
- package/src/chrome/DeleteCellsDialog.tsx +183 -0
- package/src/chrome/GoalSeekDialog.tsx +370 -0
- package/src/chrome/HistoryPanel.tsx +319 -0
- package/src/chrome/InsertCellsDialog.tsx +185 -0
- package/src/chrome/InsertChartDialog.tsx +490 -0
- package/src/chrome/InsertFunctionDialog.tsx +493 -0
- package/src/chrome/InsertPivotDialog.tsx +488 -0
- package/src/chrome/InsertSparklineDialog.tsx +344 -0
- package/src/chrome/NameManagerDialog.tsx +378 -0
- package/src/chrome/PanelHost.tsx +55 -0
- package/src/chrome/PanelRail.tsx +90 -0
- package/src/chrome/PasteSpecialDialog.tsx +286 -0
- package/src/chrome/PivotFieldsPanel.tsx +1052 -0
- package/src/chrome/TablesPanel.tsx +301 -0
- package/src/chrome/dialog-context.tsx +24 -0
- package/src/chrome/panel-context.tsx +55 -0
- package/src/chrome/panel-registry.ts +48 -0
- package/src/sheets/CasualSheets.tsx +60 -34
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 Casual Office
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useMemo, useRef, useState, type ReactElement } from 'react';
|
|
18
|
+
import { Dialog } from '../chrome/Dialog';
|
|
19
|
+
import type { FUniver } from '@univerjs/core/facade';
|
|
20
|
+
import { CHART_FAMILY_OF, CHART_TYPE_LABEL, type ChartFamily, type ChartType } from './types';
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
api: FUniver;
|
|
24
|
+
/** A1 reference to seed the source-range input. Pulled from the active
|
|
25
|
+
* selection by the caller so the dialog opens already filled in. */
|
|
26
|
+
defaultSourceA1: string;
|
|
27
|
+
/** Pre-selected chart type. Defaults to `'column'` (Excel's
|
|
28
|
+
* Insert > Chart default — Clustered Column). Set to the chart's
|
|
29
|
+
* current type when used for "Change chart type". */
|
|
30
|
+
initialType?: ChartType;
|
|
31
|
+
/** Dialog title — defaults to "Insert chart". */
|
|
32
|
+
title?: string;
|
|
33
|
+
/** Primary action label — defaults to "Insert". */
|
|
34
|
+
confirmLabel?: string;
|
|
35
|
+
onCancel: () => void;
|
|
36
|
+
onConfirm: (args: {
|
|
37
|
+
source: { startRow: number; endRow: number; startColumn: number; endColumn: number };
|
|
38
|
+
type: ChartType;
|
|
39
|
+
}) => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type FamilyDef = {
|
|
43
|
+
id: ChartFamily;
|
|
44
|
+
label: string;
|
|
45
|
+
icon: string;
|
|
46
|
+
subtypes: { id: ChartType; label: string }[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Excel chart catalog, family + subtype hierarchy. Order mirrors
|
|
51
|
+
* Excel's Insert > Chart panel so muscle memory transfers.
|
|
52
|
+
*/
|
|
53
|
+
const FAMILIES: FamilyDef[] = [
|
|
54
|
+
{
|
|
55
|
+
id: 'column',
|
|
56
|
+
label: 'Column',
|
|
57
|
+
icon: 'bar_chart',
|
|
58
|
+
subtypes: [
|
|
59
|
+
{ id: 'column', label: CHART_TYPE_LABEL.column },
|
|
60
|
+
{ id: 'column-stacked', label: CHART_TYPE_LABEL['column-stacked'] },
|
|
61
|
+
{ id: 'column-stacked-100', label: CHART_TYPE_LABEL['column-stacked-100'] },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'bar',
|
|
66
|
+
label: 'Bar',
|
|
67
|
+
icon: 'align_horizontal_left',
|
|
68
|
+
subtypes: [
|
|
69
|
+
{ id: 'bar', label: CHART_TYPE_LABEL.bar },
|
|
70
|
+
{ id: 'bar-stacked', label: CHART_TYPE_LABEL['bar-stacked'] },
|
|
71
|
+
{ id: 'bar-stacked-100', label: CHART_TYPE_LABEL['bar-stacked-100'] },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'line',
|
|
76
|
+
label: 'Line',
|
|
77
|
+
icon: 'show_chart',
|
|
78
|
+
subtypes: [
|
|
79
|
+
{ id: 'line', label: CHART_TYPE_LABEL.line },
|
|
80
|
+
{ id: 'line-stacked', label: CHART_TYPE_LABEL['line-stacked'] },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'area',
|
|
85
|
+
label: 'Area',
|
|
86
|
+
icon: 'area_chart',
|
|
87
|
+
subtypes: [
|
|
88
|
+
{ id: 'area', label: CHART_TYPE_LABEL.area },
|
|
89
|
+
{ id: 'area-stacked', label: CHART_TYPE_LABEL['area-stacked'] },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'pie',
|
|
94
|
+
label: 'Pie',
|
|
95
|
+
icon: 'pie_chart',
|
|
96
|
+
subtypes: [
|
|
97
|
+
{ id: 'pie', label: CHART_TYPE_LABEL.pie },
|
|
98
|
+
{ id: 'doughnut', label: CHART_TYPE_LABEL.doughnut },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'scatter',
|
|
103
|
+
label: 'Scatter',
|
|
104
|
+
icon: 'scatter_plot',
|
|
105
|
+
subtypes: [{ id: 'scatter', label: CHART_TYPE_LABEL.scatter }],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Excel-style "Insert chart" dialog. Two-panel layout:
|
|
111
|
+
*
|
|
112
|
+
* - Left: family list (Column / Bar / Line / Area / Pie / Scatter).
|
|
113
|
+
* Clicking switches the visible subtypes.
|
|
114
|
+
* - Right top: subtype thumbnails with hover-to-preview labels.
|
|
115
|
+
* - Right bottom: source-range input, pre-filled from the active
|
|
116
|
+
* selection so the typical flow is two clicks.
|
|
117
|
+
*
|
|
118
|
+
* Range parsing leans on `sheet.getRange(a1)` — same A1 parser as the
|
|
119
|
+
* Name Box — so anything the formula bar accepts works here too
|
|
120
|
+
* (B5, A1:C4, B:B). Below 2 rows × 2 cols we refuse with an inline
|
|
121
|
+
* hint instead of producing an empty chart.
|
|
122
|
+
*/
|
|
123
|
+
export function InsertChartDialog({
|
|
124
|
+
api,
|
|
125
|
+
defaultSourceA1,
|
|
126
|
+
initialType = 'column',
|
|
127
|
+
title = 'Insert chart',
|
|
128
|
+
confirmLabel = 'Insert',
|
|
129
|
+
onCancel,
|
|
130
|
+
onConfirm,
|
|
131
|
+
}: Props) {
|
|
132
|
+
const [type, setType] = useState<ChartType>(initialType);
|
|
133
|
+
const [family, setFamily] = useState<ChartFamily>(CHART_FAMILY_OF[initialType]);
|
|
134
|
+
const [sourceA1, setSourceA1] = useState(defaultSourceA1);
|
|
135
|
+
const [error, setError] = useState<string | null>(null);
|
|
136
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
137
|
+
|
|
138
|
+
const activeSheet = useMemo(() => {
|
|
139
|
+
const wb = api.getActiveWorkbook();
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
|
+
return wb?.getActiveSheet() as any;
|
|
142
|
+
}, [api]);
|
|
143
|
+
|
|
144
|
+
const currentFamily = FAMILIES.find((f) => f.id === family) ?? FAMILIES[0];
|
|
145
|
+
|
|
146
|
+
const pickFamily = (id: ChartFamily) => {
|
|
147
|
+
setFamily(id);
|
|
148
|
+
const fam = FAMILIES.find((f) => f.id === id);
|
|
149
|
+
if (fam && !fam.subtypes.some((s) => s.id === type)) {
|
|
150
|
+
// Switched family — pick its first subtype.
|
|
151
|
+
setType(fam.subtypes[0].id);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const confirm = () => {
|
|
156
|
+
const trimmed = sourceA1.trim();
|
|
157
|
+
if (!trimmed) {
|
|
158
|
+
setError('Pick a source range.');
|
|
159
|
+
inputRef.current?.focus();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
let range: { startRow: number; endRow: number; startColumn: number; endColumn: number } | null =
|
|
163
|
+
null;
|
|
164
|
+
try {
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
+
const r = activeSheet?.getRange(trimmed) as any;
|
|
167
|
+
const raw = r?.getRange?.();
|
|
168
|
+
if (raw) {
|
|
169
|
+
range = {
|
|
170
|
+
startRow: raw.startRow,
|
|
171
|
+
endRow: raw.endRow,
|
|
172
|
+
startColumn: raw.startColumn,
|
|
173
|
+
endColumn: raw.endColumn,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
/* fall through */
|
|
178
|
+
}
|
|
179
|
+
if (!range) {
|
|
180
|
+
setError("That doesn't look like a valid range — try A1:C4.");
|
|
181
|
+
inputRef.current?.focus();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const rows = range.endRow - range.startRow + 1;
|
|
185
|
+
const cols = range.endColumn - range.startColumn + 1;
|
|
186
|
+
if (rows < 2 || cols < 2) {
|
|
187
|
+
setError(
|
|
188
|
+
'Source needs at least a header row + a data row, and a label column + a value column.',
|
|
189
|
+
);
|
|
190
|
+
inputRef.current?.focus();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
onConfirm({ source: range, type });
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Dialog
|
|
198
|
+
title={title}
|
|
199
|
+
onClose={onCancel}
|
|
200
|
+
data-testid="insert-chart-dialog"
|
|
201
|
+
footer={
|
|
202
|
+
<>
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
className="btn-secondary"
|
|
206
|
+
data-testid="insert-chart-cancel"
|
|
207
|
+
onClick={onCancel}
|
|
208
|
+
>
|
|
209
|
+
Cancel
|
|
210
|
+
</button>
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
className="btn-primary"
|
|
214
|
+
data-testid="insert-chart-confirm"
|
|
215
|
+
onClick={confirm}
|
|
216
|
+
>
|
|
217
|
+
{confirmLabel}
|
|
218
|
+
</button>
|
|
219
|
+
</>
|
|
220
|
+
}
|
|
221
|
+
>
|
|
222
|
+
<div className="insert-chart">
|
|
223
|
+
<div className="insert-chart__catalog">
|
|
224
|
+
<ul
|
|
225
|
+
className="insert-chart__families"
|
|
226
|
+
role="tablist"
|
|
227
|
+
aria-label="Chart family"
|
|
228
|
+
data-testid="insert-chart-families"
|
|
229
|
+
>
|
|
230
|
+
{FAMILIES.map((f) => (
|
|
231
|
+
<li key={f.id}>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
role="tab"
|
|
235
|
+
aria-selected={family === f.id}
|
|
236
|
+
className={`insert-chart__family${family === f.id ? ' insert-chart__family--active' : ''}`}
|
|
237
|
+
data-testid={`insert-chart-family-${f.id}`}
|
|
238
|
+
onClick={() => pickFamily(f.id)}
|
|
239
|
+
>
|
|
240
|
+
<span className="material-symbols-outlined" aria-hidden="true">
|
|
241
|
+
{f.icon}
|
|
242
|
+
</span>
|
|
243
|
+
<span>{f.label}</span>
|
|
244
|
+
</button>
|
|
245
|
+
</li>
|
|
246
|
+
))}
|
|
247
|
+
</ul>
|
|
248
|
+
<div className="insert-chart__subtypes" role="radiogroup" aria-label="Chart subtype">
|
|
249
|
+
{currentFamily.subtypes.map((s) => (
|
|
250
|
+
<label
|
|
251
|
+
key={s.id}
|
|
252
|
+
className={`insert-chart__subtype${type === s.id ? ' insert-chart__subtype--active' : ''}`}
|
|
253
|
+
data-testid={`insert-chart-type-${s.id}`}
|
|
254
|
+
title={s.label}
|
|
255
|
+
>
|
|
256
|
+
<input
|
|
257
|
+
type="radio"
|
|
258
|
+
name="chart-subtype"
|
|
259
|
+
value={s.id}
|
|
260
|
+
checked={type === s.id}
|
|
261
|
+
onChange={() => setType(s.id)}
|
|
262
|
+
/>
|
|
263
|
+
<SubtypeThumbnail type={s.id} />
|
|
264
|
+
<span className="insert-chart__subtype-label">{s.label}</span>
|
|
265
|
+
</label>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<fieldset className="insert-chart__group">
|
|
271
|
+
<legend className="insert-chart__legend">Source data</legend>
|
|
272
|
+
{/* Error elevates ABOVE the input so the user's eye lands
|
|
273
|
+
on it before the field they're correcting. The previous
|
|
274
|
+
"small red text below the input" layout was easy to
|
|
275
|
+
miss (audit finding 1.2); a proper banner with icon +
|
|
276
|
+
role=alert + aria-live=assertive makes it impossible
|
|
277
|
+
to skip. Screen readers announce on appearance. */}
|
|
278
|
+
{error && (
|
|
279
|
+
<div
|
|
280
|
+
className="insert-chart__error"
|
|
281
|
+
data-testid="insert-chart-error"
|
|
282
|
+
role="alert"
|
|
283
|
+
aria-live="assertive"
|
|
284
|
+
>
|
|
285
|
+
<span className="insert-chart__error-icon" aria-hidden="true">
|
|
286
|
+
!
|
|
287
|
+
</span>
|
|
288
|
+
<span>{error}</span>
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
<input
|
|
292
|
+
ref={inputRef}
|
|
293
|
+
type="text"
|
|
294
|
+
className={`insert-chart__range${error ? ' insert-chart__range--error' : ''}`}
|
|
295
|
+
data-testid="insert-chart-range"
|
|
296
|
+
value={sourceA1}
|
|
297
|
+
spellCheck={false}
|
|
298
|
+
aria-invalid={Boolean(error)}
|
|
299
|
+
aria-describedby={error ? 'insert-chart-error-desc' : undefined}
|
|
300
|
+
onChange={(e) => {
|
|
301
|
+
setSourceA1(e.target.value);
|
|
302
|
+
if (error) setError(null);
|
|
303
|
+
}}
|
|
304
|
+
onKeyDown={(e) => {
|
|
305
|
+
if (e.key === 'Enter') {
|
|
306
|
+
e.preventDefault();
|
|
307
|
+
confirm();
|
|
308
|
+
}
|
|
309
|
+
}}
|
|
310
|
+
placeholder="A1:C4"
|
|
311
|
+
/>
|
|
312
|
+
<p className="insert-chart__hint" id="insert-chart-error-desc">
|
|
313
|
+
First row is used as series labels and the first column as category labels — same
|
|
314
|
+
convention as Excel's Insert > Chart.
|
|
315
|
+
</p>
|
|
316
|
+
</fieldset>
|
|
317
|
+
</div>
|
|
318
|
+
</Dialog>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Inline SVG thumbnail for each subtype. ECharts canvases would be
|
|
324
|
+
* too heavy to mount one per option; small static SVGs read at a
|
|
325
|
+
* glance and match the Excel preview tiles' role.
|
|
326
|
+
*/
|
|
327
|
+
function SubtypeThumbnail({ type }: { type: ChartType }) {
|
|
328
|
+
return (
|
|
329
|
+
<svg
|
|
330
|
+
className="insert-chart__thumb"
|
|
331
|
+
viewBox="0 0 60 40"
|
|
332
|
+
width={60}
|
|
333
|
+
height={40}
|
|
334
|
+
aria-hidden="true"
|
|
335
|
+
>
|
|
336
|
+
{renderThumbBody(type)}
|
|
337
|
+
</svg>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderThumbBody(type: ChartType): ReactElement {
|
|
342
|
+
const stroke = 'currentColor';
|
|
343
|
+
switch (type) {
|
|
344
|
+
case 'column':
|
|
345
|
+
return (
|
|
346
|
+
<g fill={stroke}>
|
|
347
|
+
<rect x="6" y="20" width="6" height="14" />
|
|
348
|
+
<rect x="16" y="12" width="6" height="22" />
|
|
349
|
+
<rect x="26" y="8" width="6" height="26" />
|
|
350
|
+
<rect x="36" y="16" width="6" height="18" />
|
|
351
|
+
<rect x="46" y="22" width="6" height="12" />
|
|
352
|
+
</g>
|
|
353
|
+
);
|
|
354
|
+
case 'column-stacked':
|
|
355
|
+
return (
|
|
356
|
+
<g>
|
|
357
|
+
<rect x="10" y="22" width="10" height="12" fill={stroke} />
|
|
358
|
+
<rect x="10" y="12" width="10" height="10" fill={stroke} opacity="0.6" />
|
|
359
|
+
<rect x="26" y="16" width="10" height="18" fill={stroke} />
|
|
360
|
+
<rect x="26" y="8" width="10" height="8" fill={stroke} opacity="0.6" />
|
|
361
|
+
<rect x="42" y="20" width="10" height="14" fill={stroke} />
|
|
362
|
+
<rect x="42" y="10" width="10" height="10" fill={stroke} opacity="0.6" />
|
|
363
|
+
</g>
|
|
364
|
+
);
|
|
365
|
+
case 'column-stacked-100':
|
|
366
|
+
return (
|
|
367
|
+
<g>
|
|
368
|
+
<rect x="10" y="20" width="10" height="14" fill={stroke} />
|
|
369
|
+
<rect x="10" y="6" width="10" height="14" fill={stroke} opacity="0.6" />
|
|
370
|
+
<rect x="26" y="14" width="10" height="20" fill={stroke} />
|
|
371
|
+
<rect x="26" y="6" width="10" height="8" fill={stroke} opacity="0.6" />
|
|
372
|
+
<rect x="42" y="24" width="10" height="10" fill={stroke} />
|
|
373
|
+
<rect x="42" y="6" width="10" height="18" fill={stroke} opacity="0.6" />
|
|
374
|
+
</g>
|
|
375
|
+
);
|
|
376
|
+
case 'bar':
|
|
377
|
+
return (
|
|
378
|
+
<g fill={stroke}>
|
|
379
|
+
<rect x="6" y="6" width="22" height="5" />
|
|
380
|
+
<rect x="6" y="14" width="36" height="5" />
|
|
381
|
+
<rect x="6" y="22" width="48" height="5" />
|
|
382
|
+
<rect x="6" y="30" width="28" height="5" />
|
|
383
|
+
</g>
|
|
384
|
+
);
|
|
385
|
+
case 'bar-stacked':
|
|
386
|
+
return (
|
|
387
|
+
<g>
|
|
388
|
+
<rect x="6" y="10" width="20" height="6" fill={stroke} />
|
|
389
|
+
<rect x="26" y="10" width="16" height="6" fill={stroke} opacity="0.6" />
|
|
390
|
+
<rect x="6" y="20" width="14" height="6" fill={stroke} />
|
|
391
|
+
<rect x="20" y="20" width="28" height="6" fill={stroke} opacity="0.6" />
|
|
392
|
+
<rect x="6" y="30" width="30" height="6" fill={stroke} />
|
|
393
|
+
<rect x="36" y="30" width="14" height="6" fill={stroke} opacity="0.6" />
|
|
394
|
+
</g>
|
|
395
|
+
);
|
|
396
|
+
case 'bar-stacked-100':
|
|
397
|
+
return (
|
|
398
|
+
<g>
|
|
399
|
+
<rect x="6" y="10" width="28" height="6" fill={stroke} />
|
|
400
|
+
<rect x="34" y="10" width="20" height="6" fill={stroke} opacity="0.6" />
|
|
401
|
+
<rect x="6" y="20" width="14" height="6" fill={stroke} />
|
|
402
|
+
<rect x="20" y="20" width="34" height="6" fill={stroke} opacity="0.6" />
|
|
403
|
+
<rect x="6" y="30" width="40" height="6" fill={stroke} />
|
|
404
|
+
<rect x="46" y="30" width="8" height="6" fill={stroke} opacity="0.6" />
|
|
405
|
+
</g>
|
|
406
|
+
);
|
|
407
|
+
case 'line':
|
|
408
|
+
return (
|
|
409
|
+
<g fill="none" stroke={stroke} strokeWidth="2">
|
|
410
|
+
<polyline points="6,30 18,18 30,24 42,10 54,16" />
|
|
411
|
+
<circle cx="6" cy="30" r="2" fill={stroke} />
|
|
412
|
+
<circle cx="18" cy="18" r="2" fill={stroke} />
|
|
413
|
+
<circle cx="30" cy="24" r="2" fill={stroke} />
|
|
414
|
+
<circle cx="42" cy="10" r="2" fill={stroke} />
|
|
415
|
+
<circle cx="54" cy="16" r="2" fill={stroke} />
|
|
416
|
+
</g>
|
|
417
|
+
);
|
|
418
|
+
case 'line-stacked':
|
|
419
|
+
return (
|
|
420
|
+
<g fill="none" strokeWidth="2">
|
|
421
|
+
<polyline points="6,32 18,28 30,30 42,24 54,26" stroke={stroke} />
|
|
422
|
+
<polyline points="6,20 18,12 30,18 42,8 54,14" stroke={stroke} opacity="0.6" />
|
|
423
|
+
</g>
|
|
424
|
+
);
|
|
425
|
+
case 'area':
|
|
426
|
+
return (
|
|
427
|
+
<g>
|
|
428
|
+
<polygon points="6,34 6,24 18,14 30,18 42,8 54,12 54,34" fill={stroke} opacity="0.4" />
|
|
429
|
+
<polyline
|
|
430
|
+
points="6,24 18,14 30,18 42,8 54,12"
|
|
431
|
+
fill="none"
|
|
432
|
+
stroke={stroke}
|
|
433
|
+
strokeWidth="2"
|
|
434
|
+
/>
|
|
435
|
+
</g>
|
|
436
|
+
);
|
|
437
|
+
case 'area-stacked':
|
|
438
|
+
return (
|
|
439
|
+
<g>
|
|
440
|
+
<polygon points="6,34 6,24 18,18 30,22 42,16 54,20 54,34" fill={stroke} opacity="0.6" />
|
|
441
|
+
<polygon
|
|
442
|
+
points="6,24 18,18 30,22 42,16 54,20 54,12 42,6 30,12 18,8 6,14"
|
|
443
|
+
fill={stroke}
|
|
444
|
+
opacity="0.3"
|
|
445
|
+
/>
|
|
446
|
+
</g>
|
|
447
|
+
);
|
|
448
|
+
case 'pie':
|
|
449
|
+
return (
|
|
450
|
+
<g>
|
|
451
|
+
<circle cx="30" cy="20" r="14" fill={stroke} opacity="0.4" />
|
|
452
|
+
<path d="M30 20 L30 6 A14 14 0 0 1 42 24 Z" fill={stroke} />
|
|
453
|
+
</g>
|
|
454
|
+
);
|
|
455
|
+
case 'doughnut':
|
|
456
|
+
return (
|
|
457
|
+
<g>
|
|
458
|
+
<circle cx="30" cy="20" r="14" fill={stroke} opacity="0.4" />
|
|
459
|
+
<path d="M30 20 L30 6 A14 14 0 0 1 42 24 Z" fill={stroke} />
|
|
460
|
+
<circle cx="30" cy="20" r="7" fill="white" />
|
|
461
|
+
</g>
|
|
462
|
+
);
|
|
463
|
+
case 'scatter':
|
|
464
|
+
return (
|
|
465
|
+
<g fill={stroke}>
|
|
466
|
+
<circle cx="10" cy="30" r="2" />
|
|
467
|
+
<circle cx="18" cy="22" r="2" />
|
|
468
|
+
<circle cx="24" cy="26" r="2" />
|
|
469
|
+
<circle cx="30" cy="14" r="2" />
|
|
470
|
+
<circle cx="36" cy="20" r="2" />
|
|
471
|
+
<circle cx="42" cy="10" r="2" />
|
|
472
|
+
<circle cx="48" cy="16" r="2" />
|
|
473
|
+
<circle cx="14" cy="14" r="2" />
|
|
474
|
+
<circle cx="50" cy="28" r="2" />
|
|
475
|
+
</g>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|