s880453 commited on
Commit
705707d
·
verified ·
1 Parent(s): 9286808

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -541
app.py CHANGED
@@ -1,566 +1,314 @@
1
- import React, { useState, useEffect } from 'react';
2
- import {
3
- LineChart, Line, BarChart, Bar, PieChart, Pie, AreaChart, Area,
4
- XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
5
- ScatterChart, Scatter, Cell
6
- } from 'recharts';
7
- import Papa from 'papaparse';
 
8
 
9
- // 主色調
10
- const COLORS = ['#8884d8', '#82ca9d', '#ffc658', '#ff8042', '#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#a4de6c'];
 
 
 
11
 
12
- const App = () => {
13
- const [data, setData] = useState([]);
14
- const [headers, setHeaders] = useState([]);
15
- const [chartType, setChartType] = useState('bar');
16
- const [xAxis, setXAxis] = useState('');
17
- const [yAxis, setYAxis] = useState('');
18
- const [pieColumn, setPieColumn] = useState('');
19
- const [tableData, setTableData] = useState([['', ''], ['', '']]);
20
- const [customization, setCustomization] = useState({
21
- chartWidth: 600,
22
- chartHeight: 400,
23
- fontSize: 12,
24
- legendPosition: 'bottom',
25
- colorScheme: COLORS,
26
- showGrid: true,
27
- showTooltip: true,
28
- showLegend: true,
29
- });
30
 
31
- useEffect(() => {
32
- // 表格發生變化時更新數據
33
- processTableData();
34
- }, [tableData]);
35
-
36
- const processTableData = () => {
37
- if (tableData.length <= 1) return;
38
 
39
- const headers = tableData[0].filter(h => h.trim() !== '');
40
- if (headers.length < 1) return;
 
41
 
42
- setHeaders(headers);
 
 
43
 
44
- const processedData = [];
 
45
 
46
- for (let i = 1; i < tableData.length; i++) {
47
- if (tableData[i].some(cell => cell.trim() !== '')) {
48
- const rowData = {};
49
-
50
- tableData[i].forEach((cell, cellIndex) => {
51
- if (cellIndex < headers.length) {
52
- // 嘗試將數字字符串轉換為數字
53
- const value = cell.trim();
54
- const numValue = parseFloat(value);
55
- rowData[headers[cellIndex]] = isNaN(numValue) ? value : numValue;
56
- }
57
- });
58
-
59
- processedData.push(rowData);
60
- }
61
  }
62
 
63
- setData(processedData);
 
 
64
 
65
- // 預設第一個列作為X軸,第二個列作為Y軸
66
- if (headers.length >= 1 && !xAxis) {
67
- setXAxis(headers[0]);
68
- }
69
 
70
- if (headers.length >= 2 && !yAxis) {
71
- setYAxis(headers[1]);
72
- }
73
 
74
- if (headers.length >= 1 && !pieColumn) {
75
- setPieColumn(headers[0]);
76
- }
77
- };
78
-
79
- const addRow = () => {
80
- setTableData([...tableData, Array(tableData[0].length).fill('')]);
81
- };
82
-
83
- const addColumn = () => {
84
- setTableData(tableData.map(row => [...row, '']));
85
- };
86
-
87
- const handleCellChange = (rowIndex, colIndex, value) => {
88
- const newData = [...tableData];
89
- newData[rowIndex][colIndex] = value;
90
- setTableData(newData);
91
- };
92
-
93
- const handleCustomizationChange = (key, value) => {
94
- setCustomization({
95
- ...customization,
96
- [key]: value
97
- });
98
- };
99
-
100
- const handlePasteData = (e) => {
101
- e.preventDefault();
102
- const clipboard = e.clipboardData.getData('text');
103
- const rows = clipboard.split('\n').filter(row => row.trim());
104
 
105
- if (rows.length > 0) {
106
- const pastedData = rows.map(row => row.split('\t'));
107
-
108
- // 確保所有行有相同的列數
109
- const maxCols = Math.max(...pastedData.map(row => row.length));
110
- const normalizedData = pastedData.map(row => {
111
- while (row.length < maxCols) {
112
- row.push('');
113
- }
114
- return row;
115
- });
116
-
117
- setTableData(normalizedData);
118
- }
119
- };
120
-
121
- const handleFileUpload = (e) => {
122
- const file = e.target.files[0];
123
- if (file) {
124
- Papa.parse(file, {
125
- complete: (results) => {
126
- if (results.data && results.data.length > 0) {
127
- setTableData(results.data);
128
- }
129
- }
130
- });
131
- }
132
- };
133
-
134
- const renderChart = () => {
135
- if (data.length === 0) {
136
- return <div className="p-4 text-center">請輸入或粘貼數據以生成圖表</div>;
137
- }
138
-
139
- const { chartWidth, chartHeight, fontSize, showGrid, showTooltip, showLegend, legendPosition, colorScheme } = customization;
140
 
141
- const style = {
142
- fontSize: `${fontSize}px`
143
- };
144
-
145
- switch (chartType) {
146
- case 'bar':
147
- return (
148
- <ResponsiveContainer width={chartWidth} height={chartHeight}>
149
- <BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
150
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
151
- <XAxis dataKey={xAxis} style={style} />
152
- <YAxis style={style} />
153
- {showTooltip && <Tooltip />}
154
- {showLegend && <Legend layout="horizontal" verticalAlign={legendPosition} />}
155
- <Bar dataKey={yAxis} fill={colorScheme[0]} />
156
- </BarChart>
157
- </ResponsiveContainer>
158
- );
159
-
160
- case 'line':
161
- return (
162
- <ResponsiveContainer width={chartWidth} height={chartHeight}>
163
- <LineChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
164
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
165
- <XAxis dataKey={xAxis} style={style} />
166
- <YAxis style={style} />
167
- {showTooltip && <Tooltip />}
168
- {showLegend && <Legend layout="horizontal" verticalAlign={legendPosition} />}
169
- <Line type="monotone" dataKey={yAxis} stroke={colorScheme[0]} activeDot={{ r: 8 }} />
170
- </LineChart>
171
- </ResponsiveContainer>
172
- );
173
-
174
- case 'area':
175
- return (
176
- <ResponsiveContainer width={chartWidth} height={chartHeight}>
177
- <AreaChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
178
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
179
- <XAxis dataKey={xAxis} style={style} />
180
- <YAxis style={style} />
181
- {showTooltip && <Tooltip />}
182
- {showLegend && <Legend layout="horizontal" verticalAlign={legendPosition} />}
183
- <Area type="monotone" dataKey={yAxis} stroke={colorScheme[0]} fill={colorScheme[0]} />
184
- </AreaChart>
185
- </ResponsiveContainer>
186
- );
187
-
188
- case 'pie':
189
- // 對餅圖數據進行特殊處理
190
- const pieData = data.map(item => ({
191
- name: String(item[pieColumn]),
192
- value: typeof item[yAxis] === 'number' ? item[yAxis] : 0
193
- }));
194
 
195
- return (
196
- <ResponsiveContainer width={chartWidth} height={chartHeight}>
197
- <PieChart margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
198
- {showTooltip && <Tooltip />}
199
- {showLegend && <Legend layout="horizontal" verticalAlign={legendPosition} />}
200
- <Pie
201
- data={pieData}
202
- cx="50%"
203
- cy="50%"
204
- labelLine={true}
205
- outerRadius={chartHeight / 3}
206
- fill="#8884d8"
207
- dataKey="value"
208
- nameKey="name"
209
- label={({ name, percent }) => `${name}: ${(percent * 100).toFixed(0)}%`}
210
- >
211
- {pieData.map((entry, index) => (
212
- <Cell key={`cell-${index}`} fill={colorScheme[index % colorScheme.length]} />
213
- ))}
214
- </Pie>
215
- </PieChart>
216
- </ResponsiveContainer>
217
- );
218
-
219
- case 'scatter':
220
- return (
221
- <ResponsiveContainer width={chartWidth} height={chartHeight}>
222
- <ScatterChart margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
223
- {showGrid && <CartesianGrid strokeDasharray="3 3" />}
224
- <XAxis dataKey={xAxis} name={xAxis} style={style} />
225
- <YAxis dataKey={yAxis} name={yAxis} style={style} />
226
- {showTooltip && <Tooltip cursor={{ strokeDasharray: '3 3' }} />}
227
- {showLegend && <Legend layout="horizontal" verticalAlign={legendPosition} />}
228
- <Scatter name={`${xAxis} vs ${yAxis}`} data={data} fill={colorScheme[0]} />
229
- </ScatterChart>
230
- </ResponsiveContainer>
231
- );
232
-
233
- default:
234
- return null;
235
- }
236
- };
237
 
238
- const exportSvg = () => {
239
- // 獲取SVG內容
240
- const svgElement = document.querySelector('.recharts-wrapper svg');
241
- if (svgElement) {
242
- // 創建一個Blob對象
243
- const svgData = new XMLSerializer().serializeToString(svgElement);
244
- const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
245
- const svgUrl = URL.createObjectURL(svgBlob);
246
-
247
- // 創建下載鏈接
248
- const downloadLink = document.createElement('a');
249
- downloadLink.href = svgUrl;
250
- downloadLink.download = `chart_export_${new Date().toISOString()}.svg`;
251
- document.body.appendChild(downloadLink);
252
- downloadLink.click();
253
- document.body.removeChild(downloadLink);
254
- }
255
- };
256
 
257
- const exportPng = () => {
258
- // 將SVG轉換為Canvas,然後導出為PNG
259
- const svgElement = document.querySelector('.recharts-wrapper svg');
260
- if (svgElement) {
261
- const svgData = new XMLSerializer().serializeToString(svgElement);
262
- const canvas = document.createElement('canvas');
263
- const ctx = canvas.getContext('2d');
264
-
265
- // 設置Canvas大小
266
- canvas.width = customization.chartWidth;
267
- canvas.height = customization.chartHeight;
268
-
269
- const img = new Image();
270
- img.onload = function() {
271
- ctx.drawImage(img, 0, 0);
272
- const pngUrl = canvas.toDataURL('image/png');
273
 
274
- const downloadLink = document.createElement('a');
275
- downloadLink.href = pngUrl;
276
- downloadLink.download = `chart_export_${new Date().toISOString()}.png`;
277
- document.body.appendChild(downloadLink);
278
- downloadLink.click();
279
- document.body.removeChild(downloadLink);
280
- };
281
-
282
- img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
283
- }
284
- };
 
 
 
 
 
 
 
 
 
285
 
286
- const exportCsv = () => {
287
- // 導出數據為CSV
288
- const csvContent = Papa.unparse(tableData);
289
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
290
- const url = URL.createObjectURL(blob);
 
 
 
 
291
 
292
- const downloadLink = document.createElement('a');
293
- downloadLink.href = url;
294
- downloadLink.download = `data_export_${new Date().toISOString()}.csv`;
295
- document.body.appendChild(downloadLink);
296
- downloadLink.click();
297
- document.body.removeChild(downloadLink);
298
- };
299
 
300
- return (
301
- <div className="p-4 max-w-6xl mx-auto">
302
- <h1 className="text-2xl font-bold mb-6 text-center">數據可視化工具</h1>
303
-
304
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
305
- {/* 數據輸入區 */}
306
- <div className="p-4 border rounded shadow-sm">
307
- <h2 className="text-lg font-semibold mb-3">數據輸入</h2>
308
-
309
- <div className="mb-4 flex gap-2">
310
- <button
311
- onClick={addRow}
312
- className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
313
- >
314
- 添加行
315
- </button>
316
- <button
317
- onClick={addColumn}
318
- className="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600"
319
- >
320
- 添加列
321
- </button>
322
- <label className="px-3 py-1 bg-purple-500 text-white rounded hover:bg-purple-600 cursor-pointer">
323
- 上傳CSV
324
- <input
325
- type="file"
326
- accept=".csv"
327
- onChange={handleFileUpload}
328
- className="hidden"
329
- />
330
- </label>
331
- <button
332
- onClick={exportCsv}
333
- className="px-3 py-1 bg-gray-500 text-white rounded hover:bg-gray-600"
334
- >
335
- 導出CSV
336
- </button>
337
- </div>
338
-
339
- <div className="mb-4 overflow-x-auto">
340
- <div
341
- className="border rounded"
342
- onPaste={handlePasteData}
343
- >
344
- <table className="min-w-full divide-y divide-gray-200">
345
- <tbody className="bg-white divide-y divide-gray-200">
346
- {tableData.map((row, rowIndex) => (
347
- <tr key={rowIndex}>
348
- {row.map((cell, colIndex) => (
349
- <td key={colIndex} className="px-2 py-1 border">
350
- <input
351
- type="text"
352
- value={cell}
353
- onChange={(e) => handleCellChange(rowIndex, colIndex, e.target.value)}
354
- className="w-full border-0 focus:ring-0"
355
- />
356
- </td>
357
- ))}
358
- </tr>
359
- ))}
360
- </tbody>
361
- </table>
362
- </div>
363
- <p className="text-sm text-gray-500 mt-2">提示:您可以直接從Excel或其他表格軟件複製並粘貼數據</p>
364
- </div>
365
- </div>
366
 
367
- {/* 圖表區 */}
368
- <div className="p-4 border rounded shadow-sm">
369
- <h2 className="text-lg font-semibold mb-3">圖表預覽</h2>
370
-
371
- <div className="mb-4 grid grid-cols-2 gap-4">
372
- <div>
373
- <label className="block text-sm font-medium mb-1">圖表類型</label>
374
- <select
375
- value={chartType}
376
- onChange={(e) => setChartType(e.target.value)}
377
- className="w-full p-2 border rounded"
378
- >
379
- <option value="bar">長條圖</option>
380
- <option value="line">折線圖</option>
381
- <option value="area">區域圖</option>
382
- <option value="pie">圓餅圖</option>
383
- <option value="scatter">散點圖</option>
384
- </select>
385
- </div>
386
-
387
- {chartType !== 'pie' && (
388
- <div>
389
- <label className="block text-sm font-medium mb-1">X軸</label>
390
- <select
391
- value={xAxis}
392
- onChange={(e) => setXAxis(e.target.value)}
393
- className="w-full p-2 border rounded"
394
- >
395
- {headers.map((header, index) => (
396
- <option key={index} value={header}>{header}</option>
397
- ))}
398
- </select>
399
- </div>
400
- )}
401
-
402
- <div>
403
- <label className="block text-sm font-medium mb-1">
404
- {chartType === 'pie' ? '數值列' : 'Y軸'}
405
- </label>
406
- <select
407
- value={yAxis}
408
- onChange={(e) => setYAxis(e.target.value)}
409
- className="w-full p-2 border rounded"
410
- >
411
- {headers.map((header, index) => (
412
- <option key={index} value={header}>{header}</option>
413
- ))}
414
- </select>
415
- </div>
416
 
417
- {chartType === 'pie' && (
418
- <div>
419
- <label className="block text-sm font-medium mb-1">類別列</label>
420
- <select
421
- value={pieColumn}
422
- onChange={(e) => setPieColumn(e.target.value)}
423
- className="w-full p-2 border rounded"
424
- >
425
- {headers.map((header, index) => (
426
- <option key={index} value={header}>{header}</option>
427
- ))}
428
- </select>
429
- </div>
430
- )}
431
- </div>
432
-
433
- <div className="mb-4 flex justify-center overflow-auto">
434
- {renderChart()}
435
- </div>
436
-
437
- <div className="flex gap-2 justify-center">
438
- <button
439
- onClick={exportSvg}
440
- className="px-3 py-1 bg-indigo-500 text-white rounded hover:bg-indigo-600"
441
- >
442
- 導出SVG
443
- </button>
444
- <button
445
- onClick={exportPng}
446
- className="px-3 py-1 bg-pink-500 text-white rounded hover:bg-pink-600"
447
- >
448
- 導出PNG
449
- </button>
450
- </div>
451
- </div>
452
- </div>
453
-
454
- {/* 自定義選項 */}
455
- <div className="mt-6 p-4 border rounded shadow-sm">
456
- <h2 className="text-lg font-semibold mb-3">自定義選項</h2>
457
 
458
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
459
- <div>
460
- <label className="block text-sm font-medium mb-1">圖表寬度</label>
461
- <input
462
- type="range"
463
- min="300"
464
- max="1200"
465
- value={customization.chartWidth}
466
- onChange={(e) => handleCustomizationChange('chartWidth', Number(e.target.value))}
467
- className="w-full"
468
- />
469
- <div className="text-xs text-center">{customization.chartWidth}px</div>
470
- </div>
471
-
472
- <div>
473
- <label className="block text-sm font-medium mb-1">圖表高度</label>
474
- <input
475
- type="range"
476
- min="200"
477
- max="800"
478
- value={customization.chartHeight}
479
- onChange={(e) => handleCustomizationChange('chartHeight', Number(e.target.value))}
480
- className="w-full"
481
- />
482
- <div className="text-xs text-center">{customization.chartHeight}px</div>
483
- </div>
484
-
485
- <div>
486
- <label className="block text-sm font-medium mb-1">字體大小</label>
487
- <input
488
- type="range"
489
- min="8"
490
- max="24"
491
- value={customization.fontSize}
492
- onChange={(e) => handleCustomizationChange('fontSize', Number(e.target.value))}
493
- className="w-full"
494
- />
495
- <div className="text-xs text-center">{customization.fontSize}px</div>
496
- </div>
497
-
498
- <div>
499
- <label className="block text-sm font-medium mb-1">圖例位置</label>
500
- <select
501
- value={customization.legendPosition}
502
- onChange={(e) => handleCustomizationChange('legendPosition', e.target.value)}
503
- className="w-full p-2 border rounded"
504
- >
505
- <option value="top">頂部</option>
506
- <option value="bottom">底部</option>
507
- <option value="left">左側</option>
508
- <option value="right">右側</option>
509
- </select>
510
- </div>
511
-
512
- <div>
513
- <label className="block text-sm font-medium mb-1">顯示網格線</label>
514
- <input
515
- type="checkbox"
516
- checked={customization.showGrid}
517
- onChange={(e) => handleCustomizationChange('showGrid', e.target.checked)}
518
- className="mr-2"
519
- />
520
- </div>
521
-
522
- <div>
523
- <label className="block text-sm font-medium mb-1">顯示提示框</label>
524
- <input
525
- type="checkbox"
526
- checked={customization.showTooltip}
527
- onChange={(e) => handleCustomizationChange('showTooltip', e.target.checked)}
528
- className="mr-2"
529
- />
530
- </div>
531
-
532
- <div>
533
- <label className="block text-sm font-medium mb-1">顯示圖例</label>
534
- <input
535
- type="checkbox"
536
- checked={customization.showLegend}
537
- onChange={(e) => handleCustomizationChange('showLegend', e.target.checked)}
538
- className="mr-2"
539
- />
540
- </div>
541
-
542
- <div className="col-span-full">
543
- <label className="block text-sm font-medium mb-1">顏色方案</label>
544
- <div className="flex flex-wrap gap-2">
545
- {customization.colorScheme.map((color, index) => (
546
- <input
547
- key={index}
548
- type="color"
549
- value={color}
550
- onChange={(e) => {
551
- const newColors = [...customization.colorScheme];
552
- newColors[index] = e.target.value;
553
- handleCustomizationChange('colorScheme', newColors);
554
- }}
555
- className="w-8 h-8 p-0 border-0"
556
- />
557
- ))}
558
- </div>
559
- </div>
560
- </div>
561
- </div>
562
- </div>
563
- );
564
- };
565
 
566
- export default App;
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ import io
7
+ import base64
8
+ from PIL import Image
9
 
10
+ # 初始化默認數據
11
+ default_data = pd.DataFrame({
12
+ "類別": ["A", "B", "C", "D", "E"],
13
+ "數值": [10, 20, 15, 25, 30]
14
+ })
15
 
16
+ # 可用的顏色方案
17
+ COLOR_SCHEMES = {
18
+ "默認": px.colors.qualitative.Plotly,
19
+ "藍綠色系": px.colors.sequential.Blues,
20
+ "紅色系": px.colors.sequential.Reds,
21
+ "綠色系": px.colors.sequential.Greens,
22
+ "彩虹色": px.colors.sequential.Turbo
23
+ }
 
 
 
 
 
 
 
 
 
 
24
 
25
+ def create_plot(df, chart_type, x_column, y_column, color_scheme, title, width, height, show_grid, show_legend):
26
+ """創建圖表函數"""
 
 
 
 
 
27
 
28
+ # 數據預處理
29
+ if df is None or df.empty:
30
+ df = default_data
31
 
32
+ # 確保列存在
33
+ if x_column not in df.columns:
34
+ x_column = df.columns[0] if len(df.columns) > 0 else "類別"
35
 
36
+ if y_column not in df.columns:
37
+ y_column = df.columns[1] if len(df.columns) > 1 else "數值"
38
 
39
+ # 獲取選擇的顏色方案
40
+ colors = COLOR_SCHEMES[color_scheme]
41
+
42
+ # 設置圖表參數
43
+ fig_params = {
44
+ "width": width,
45
+ "height": height,
46
+ "title": title
 
 
 
 
 
 
 
47
  }
48
 
49
+ # 基於選擇的圖表類型創建圖表
50
+ if chart_type == "長條圖":
51
+ fig = px.bar(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
52
 
53
+ elif chart_type == "折線圖":
54
+ fig = px.line(df, x=x_column, y=y_column, markers=True, color_discrete_sequence=colors, **fig_params)
 
 
55
 
56
+ elif chart_type == "圓餅圖":
57
+ fig = px.pie(df, names=x_column, values=y_column, color_discrete_sequence=colors, **fig_params)
 
58
 
59
+ elif chart_type == "散點圖":
60
+ fig = px.scatter(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ elif chart_type == "區域圖":
63
+ fig = px.area(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ else: # 默認使用長條圖
66
+ fig = px.bar(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
67
+
68
+ # 設置網格
69
+ fig.update_layout(
70
+ showlegend=show_legend,
71
+ xaxis=dict(showgrid=show_grid),
72
+ yaxis=dict(showgrid=show_grid)
73
+ )
74
+
75
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ def process_upload(file):
78
+ """處理上傳的文件"""
79
+ try:
80
+ if file is None:
81
+ return None, "未上傳文件"
82
+
83
+ # 檢查文件類型
84
+ file_type = file.name.split('.')[-1].lower()
85
+
86
+ if file_type == 'csv':
87
+ df = pd.read_csv(file.name)
88
+ elif file_type in ['xls', 'xlsx']:
89
+ df = pd.read_excel(file.name)
90
+ else:
91
+ return None, f"不支持的文件類型: {file_type}。請上傳CSV或Excel文件。"
92
+
93
+ return df, f"成功載入數據,共{len(df)}行,{len(df.columns)}列"
94
+
95
+ except Exception as e:
96
+ return None, f"載入文件時出錯: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ def parse_data(csv_data):
99
+ """解析CSV文本數據"""
100
+ try:
101
+ if not csv_data or csv_data.strip() == "":
102
+ return None, "未提供數據"
103
+
104
+ # 使用StringIO讀取CSV文本
105
+ df = pd.read_csv(io.StringIO(csv_data))
106
+ return df, f"成功解析數據,共{len(df)}行,{len(df.columns)}列"
107
+
108
+ except Exception as e:
109
+ return None, f"解析數據時出錯: {str(e)}"
 
 
 
 
 
 
110
 
111
+ def export_data(df, format_type):
112
+ """導出數據為各種格式"""
113
+ if df is None or df.empty:
114
+ return None, "沒有數據可以導出"
115
+
116
+ try:
117
+ if format_type == "CSV":
118
+ buffer = io.StringIO()
119
+ df.to_csv(buffer, index=False)
120
+ data = buffer.getvalue()
121
+ filename = "exported_data.csv"
122
+ mime_type = "text/csv"
 
 
 
 
123
 
124
+ elif format_type == "Excel":
125
+ buffer = io.BytesIO()
126
+ df.to_excel(buffer, index=False)
127
+ data = buffer.getvalue()
128
+ filename = "exported_data.xlsx"
129
+ mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
130
+
131
+ elif format_type == "JSON":
132
+ buffer = io.StringIO()
133
+ data = df.to_json(orient="records")
134
+ filename = "exported_data.json"
135
+ mime_type = "application/json"
136
+
137
+ else:
138
+ return None, f"不支持的導出格式: {format_type}"
139
+
140
+ return (data, filename, mime_type), f"數據已成功導出為{format_type}格式"
141
+
142
+ except Exception as e:
143
+ return None, f"導出數據時出錯: {str(e)}"
144
 
145
+ def update_columns(df):
146
+ """更新列選擇下拉菜單"""
147
+ if df is None or df.empty:
148
+ # 默認列
149
+ return gr.Dropdown(choices=["類別", "數值"], value="類別"), gr.Dropdown(choices=["類別", "數值"], value="數值")
150
+
151
+ columns = df.columns.tolist()
152
+ x_dropdown = gr.Dropdown(choices=columns, value=columns[0] if columns else None)
153
+ y_dropdown = gr.Dropdown(choices=columns, value=columns[1] if len(columns) > 1 else columns[0])
154
 
155
+ return x_dropdown, y_dropdown
 
 
 
 
 
 
156
 
157
+ def download_figure(fig):
158
+ """導出圖表為圖像"""
159
+ if fig is None:
160
+ return None, "沒有圖表可以導出"
161
+
162
+ try:
163
+ # 將Plotly圖表轉換為PNG圖像
164
+ img_bytes = fig.to_image(format="png")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
+ # 創建PIL圖像對象
167
+ img = Image.open(io.BytesIO(img_bytes))
168
+
169
+ return img, "圖表已成功導出為PNG圖像"
170
+
171
+ except Exception as e:
172
+ return None, f"導出圖表時出錯: {str(e)}"
173
+
174
+ # 建立Gradio界面
175
+ with gr.Blocks(title="數據可視化工具") as demo:
176
+ gr.Markdown("# 數據可視化工具")
177
+ gr.Markdown("上傳CSV或Excel文件,或直接在下方輸入數據來創建各種圖表")
178
+
179
+ # 狀態變量
180
+ data_state = gr.State(None)
181
+
182
+ with gr.Tabs():
183
+ # 數據輸入頁籤
184
+ with gr.TabItem("數據輸入"):
185
+ with gr.Row():
186
+ with gr.Column():
187
+ file_upload = gr.File(label="上傳CSV或Excel文件")
188
+ upload_button = gr.Button("載入文件")
189
+ upload_status = gr.Textbox(label="上傳狀態")
190
+
191
+ with gr.Column():
192
+ csv_input = gr.Textbox(label="或直接輸入CSV數據(逗號分隔)", placeholder="類別,數值\nA,10\nB,20\nC,15\nD,25\nE,30", lines=10)
193
+ parse_button = gr.Button("解析數據")
194
+ parse_status = gr.Textbox(label="解析狀態")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
+ with gr.Row():
197
+ data_preview = gr.Dataframe(label="數據預覽")
198
+
199
+ with gr.Column():
200
+ export_format = gr.Dropdown(["CSV", "Excel", "JSON"], label="導出格式", value="CSV")
201
+ export_button = gr.Button("導出數據")
202
+ export_result = gr.File(label="導出結果")
203
+ export_status = gr.Textbox(label="導出狀態")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
+ # 圖表創建頁籤
206
+ with gr.TabItem("圖表創建"):
207
+ with gr.Row():
208
+ with gr.Column():
209
+ chart_type = gr.Dropdown(
210
+ ["長條圖", "折線圖", "圓餅圖", "散點圖", "區域圖"],
211
+ label="圖表類型",
212
+ value="長條圖"
213
+ )
214
+
215
+ x_column = gr.Dropdown(["類別"], label="X軸(或類別)")
216
+ y_column = gr.Dropdown(["數值"], label="Y軸(或數值)")
217
+
218
+ chart_title = gr.Textbox(label="圖表標題", placeholder="我的數據圖表")
219
+
220
+ color_scheme = gr.Dropdown(
221
+ list(COLOR_SCHEMES.keys()),
222
+ label="顏色方案",
223
+ value="默認"
224
+ )
225
+
226
+ with gr.Column():
227
+ chart_width = gr.Slider(300, 1200, 700, label="圖表寬度")
228
+ chart_height = gr.Slider(300, 800, 500, label="圖表高度")
229
+
230
+ show_grid = gr.Checkbox(label="顯示網格", value=True)
231
+ show_legend = gr.Checkbox(label="顯示圖例", value=True)
232
+
233
+ update_button = gr.Button("更新圖表")
234
+ download_button = gr.Button("導出為PNG圖像")
235
+
236
+ with gr.Row():
237
+ chart_output = gr.Plot(label="圖表預覽")
238
+ download_output = gr.Image(label="導出的圖表", visible=False)
239
+
240
+ # 事件處理
241
+ upload_button.click(
242
+ process_upload,
243
+ inputs=[file_upload],
244
+ outputs=[data_state, upload_status]
245
+ ).then(
246
+ lambda df: df if df is not None else pd.DataFrame(),
247
+ inputs=[data_state],
248
+ outputs=[data_preview]
249
+ ).then(
250
+ update_columns,
251
+ inputs=[data_state],
252
+ outputs=[x_column, y_column]
253
+ )
254
+
255
+ parse_button.click(
256
+ parse_data,
257
+ inputs=[csv_input],
258
+ outputs=[data_state, parse_status]
259
+ ).then(
260
+ lambda df: df if df is not None else pd.DataFrame(),
261
+ inputs=[data_state],
262
+ outputs=[data_preview]
263
+ ).then(
264
+ update_columns,
265
+ inputs=[data_state],
266
+ outputs=[x_column, y_column]
267
+ )
268
+
269
+ export_button.click(
270
+ export_data,
271
+ inputs=[data_state, export_format],
272
+ outputs=[export_result, export_status]
273
+ )
274
+
275
+ update_button.click(
276
+ create_plot,
277
+ inputs=[data_state, chart_type, x_column, y_column, color_scheme,
278
+ chart_title, chart_width, chart_height, show_grid, show_legend],
279
+ outputs=[chart_output]
280
+ )
281
+
282
+ download_button.click(
283
+ download_figure,
284
+ inputs=[chart_output],
285
+ outputs=[download_output, gr.Textbox(label="下載狀態")]
286
+ ).then(
287
+ lambda: gr.update(visible=True),
288
+ outputs=[download_output]
289
+ )
290
+
291
+ # 自動顯示圖表預覽
292
+ chart_type.change(
293
+ create_plot,
294
+ inputs=[data_state, chart_type, x_column, y_column, color_scheme,
295
+ chart_title, chart_width, chart_height, show_grid, show_legend],
296
+ outputs=[chart_output]
297
+ )
298
+
299
+ x_column.change(
300
+ create_plot,
301
+ inputs=[data_state, chart_type, x_column, y_column, color_scheme,
302
+ chart_title, chart_width, chart_height, show_grid, show_legend],
303
+ outputs=[chart_output]
304
+ )
305
+
306
+ y_column.change(
307
+ create_plot,
308
+ inputs=[data_state, chart_type, x_column, y_column, color_scheme,
309
+ chart_title, chart_width, chart_height, show_grid, show_legend],
310
+ outputs=[chart_output]
311
+ )
312
 
313
+ # 啟動應用
314
+ demo.launch()