Project

General

Profile

Web tech » History » Version 13

jun chen, 02/12/2025 10:55 AM

1 1 jun chen
# Web tech
2
3 3 jun chen
{{toc}}
4
5 1 jun chen
6 4 jun chen
## How to visualize data:
7
8
D3 ref: https://observablehq.com/@d3/gallery  ,  https://johan.github.io/d3/ex/
9
Plotly ref: https://plotly.com/javascript/
10
11 12 jun chen
---
12
13
### **交互功能对比**
14
| **功能**               | **JavaScript/Plotly** | **Python/Plotly** |   **JavaScript/d3**  |
15
|------------------------|-----------------------|-------------------|----------------------|
16
| 缩放/平移              | ✔️                    | ✔️               | ✔️                  |
17
| 悬停显示数值           | ✔️                    | ✔️               | ✔️                  |
18
| 数据点高亮             | ✔️                    | ✔️               | ✔️                  |
19
| 导出为图片(PNG/JPEG) | ✔️                    | ✔️               |✔️                  | 
20
| 动态更新数据           | ✔️(需额外代码)      | ❌               | ✔️                  |
21
| 旧firefox支持          | ❌(globalthis)      | ?                | ✔️                  |
22
23
---
24
25 5 jun chen
### js d3 内嵌数据显示折线
26 4 jun chen
27 6 jun chen
{{collapse(show code...)
28
29 1 jun chen
``` 
30
<!DOCTYPE html>
31
<html lang="en">
32
<head>
33
    <meta charset="UTF-8">
34
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
35
    <title>分段填充折线图</title>
36
    <script src="https://d3js.org/d3.v7.min.js"></script>
37
    <style>
38
        .tooltip {
39
            position: absolute;
40
            background-color: rgba(255, 255, 255, 0.9);
41
            border: 1px solid #ccc;
42
            padding: 5px;
43
            font-size: 12px;
44
            pointer-events: none;
45
            opacity: 0;
46
        }
47
        .line {
48
            fill: none;
49
            stroke: black;
50
            stroke-width: 2;
51
        }
52
    </style>
53
</head>
54
<body>
55
    <div id="chart"></div>
56
    <div class="tooltip" id="tooltip"></div>
57
58
    <script>
59
        // 示例数据
60
        const data = [
61
            { time: "2025-01-01", value: 10, description: "说明1" },
62
            { time: "2025-01-02", value: 15, description: "说明2" },
63
            { time: "2025-01-03", value: 20, description: "说明3" },
64
            { time: "2025-01-04", value: 25, description: "说明4" },
65
            { time: "2025-01-05", value: 30, description: "说明5" },
66
            { time: "2025-01-06", value: 35, description: "说明6" },
67
            { time: "2025-01-07", value: 40, description: "说明7" },
68
            { time: "2025-01-08", value: 45, description: "说明8" },
69
            { time: "2025-01-09", value: 50, description: "说明9" },
70
            { time: "2025-01-10", value: 55, description: "说明10" },
71
            { time: "2025-01-11", value: 60, description: "说明11" },
72
            { time: "2025-01-12", value: 65, description: "说明12" },
73
            { time: "2025-01-13", value: 70, description: "说明13" },
74
            { time: "2025-01-14", value: 75, description: "说明14" },
75
            { time: "2025-01-15", value: 80, description: "说明15" },
76
            { time: "2025-01-16", value: 85, description: "说明16" },
77
            { time: "2025-01-17", value: 90, description: "说明17" },
78
            { time: "2025-01-18", value: 95, description: "说明18" },
79
            { time: "2025-01-19", value: 100, description: "说明19" },
80
            { time: "2025-01-20", value: 105, description: "说明20" }
81
        ];
82
83
        // 设置图表尺寸
84
        const margin = { top: 20, right: 30, bottom: 30, left: 40 };
85
        const width = 800 - margin.left - margin.right;
86
        const height = 400 - margin.top - margin.bottom;
87
88
        // 创建 SVG 容器
89
        const svg = d3.select("#chart")
90
            .append("svg")
91
            .attr("width", width + margin.left + margin.right)
92
            .attr("height", height + margin.top + margin.bottom)
93
            .append("g")
94
            .attr("transform", `translate(${margin.left},${margin.top})`);
95
96
        // 解析时间格式
97
        const parseTime = d3.timeParse("%Y-%m-%d");
98
99
        // 格式化数据
100
        data.forEach(d => {
101
            d.time = parseTime(d.time);
102
            d.value = +d.value;
103
        });
104
105
        // 设置比例尺
106
        const x = d3.scaleTime()
107
            .domain(d3.extent(data, d => d.time))
108
            .range([0, width]);
109
110
        const y = d3.scaleLinear()
111
            .domain([0, d3.max(data, d => d.value)])
112
            .range([height, 0]);
113
114
        // 添加 X 轴
115
        svg.append("g")
116
            .attr("transform", `translate(0,${height})`)
117
            .call(d3.axisBottom(x));
118
119
        // 添加 Y 轴
120
        svg.append("g")
121
            .call(d3.axisLeft(y));
122
123
        // 创建折线生成器
124
        const line = d3.line()
125
            .x(d => x(d.time))
126
            .y(d => y(d.value));
127
128
        // 绘制折线
129
        svg.append("path")
130
            .datum(data)
131
            .attr("class", "line")
132
            .attr("d", line);
133
134
        // 分段填充颜色
135
        const first10 = data.slice(0, 10);
136
        const last10 = data.slice(-10);
137
        const middle = data.slice(10, -10);
138
139
        // 填充前十个时间段的绿色区域
140
        svg.append("path")
141
            .datum(first10)
142
            .attr("fill", "green")
143
            .attr("opacity", 0.3)
144
            .attr("d", d3.area()
145
                .x(d => x(d.time))
146
                .y0(height)
147
                .y1(d => y(d.value))
148
            );
149
150
        // 填充中间时间段的蓝色区域
151
        svg.append("path")
152
            .datum(middle)
153
            .attr("fill", "blue")
154
            .attr("opacity", 0.3)
155
            .attr("d", d3.area()
156
                .x(d => x(d.time))
157
                .y0(height)
158
                .y1(d => y(d.value))
159
            );
160
161
        // 填充后十个时间段的红色区域
162
        svg.append("path")
163
            .datum(last10)
164
            .attr("fill", "red")
165
            .attr("opacity", 0.3)
166
            .attr("d", d3.area()
167
                .x(d => x(d.time))
168
                .y0(height)
169
                .y1(d => y(d.value))
170
            );
171
172
        // 添加悬停交互
173
        const tooltip = d3.select("#tooltip");
174
175
        svg.selectAll(".dot")
176
            .data(data)
177
            .enter()
178
            .append("circle")
179
            .attr("class", "dot")
180
            .attr("cx", d => x(d.time))
181
            .attr("cy", d => y(d.value))
182
            .attr("r", 5)
183
            .attr("fill", "steelblue")
184
            .on("mouseover", (event, d) => {
185
                tooltip.style("opacity", 1)
186
                    .html(`时间: ${d3.timeFormat("%Y-%m-%d")(d.time)}<br>数值: ${d.value}<br>说明: ${d.description}`)
187
                    .style("left", `${event.pageX + 5}px`)
188
                    .style("top", `${event.pageY - 20}px`);
189
            })
190
            .on("mouseout", () => {
191
                tooltip.style("opacity", 0);
192
            });
193
    </script>
194
</body>
195
</html>
196
```
197 6 jun chen
198
}}
199 1 jun chen
200 13 jun chen
### JavaScript + Plotly(纯前端实现)
201 1 jun chen
202 8 jun chen
{{collapse(show code...)
203
204 1 jun chen
```html
205
<!DOCTYPE html>
206
<html>
207
<head>
208
    <title>交互式图表</title>
209
    <!-- 引入 Plotly.js -->
210
    <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
211
</head>
212
<body>
213
    <div id="chart"></div>
214
215
    <script>
216
        // 读取CSV文件(假设文件名为 data.csv)
217
        fetch('data.csv')
218
            .then(response => response.text())
219
            .then(csvText => {
220
                // 解析CSV数据
221
                const rows = csvText.split('\n');
222
                const x = [], y = [];
223
                rows.forEach((row, index) => {
224
                    if (index === 0) return; // 跳过标题行
225
                    const [xVal, yVal] = row.split(',');
226
                    x.push(parseFloat(xVal));
227
                    y.push(parseFloat(yVal));
228
                });
229
230
                // 绘制图表
231
                Plotly.newPlot('chart', [{
232
                    x: x,
233
                    y: y,
234
                    type: 'scatter',
235
                    mode: 'lines+markers',
236
                    marker: { color: 'blue' },
237
                    line: { shape: 'spline' }
238
                }], {
239
                    title: '交互式数据图表',
240
                    xaxis: { title: 'X轴' },
241
                    yaxis: { title: 'Y轴' },
242
                    hovermode: 'closest'
243
                });
244
            });
245
    </script>
246
</body>
247
</html>
248
```
249 8 jun chen
}}
250 1 jun chen
251
#### 使用步骤:
252
1. 将CSV文件命名为 `data.csv`,格式如下:
253
   ```csv
254
   x,y
255
   1,5
256
   2,3
257
   3,7
258
   4,2
259
   5,8
260
   ```
261
2. 将HTML文件和 `data.csv` 放在同一目录下,用浏览器打开HTML文件。
262
3. 效果:支持**缩放、悬停显示数值、拖拽平移**等交互。
263
264
---
265
266 9 jun chen
### Python + Plotly(生成独立HTML文件)**
267 1 jun chen
#### 特点:适合Python用户,自动化生成图表文件。
268
269 9 jun chen
{{collapse(show code...)
270 1 jun chen
```python
271
import pandas as pd
272
import plotly.express as px
273
274
# 1. 读取CSV文件
275
df = pd.read_csv("data.csv")
276
277
# 2. 创建交互式图表
278
fig = px.line(
279
    df, x='x', y='y',
280
    title='Python生成的交互式图表',
281
    markers=True,  # 显示数据点
282
    line_shape='spline'  # 平滑曲线
283
)
284
285
# 3. 自定义悬停效果和样式
286
fig.update_traces(
287
    hoverinfo='x+y',  # 悬停显示x和y值
288
    line=dict(width=2, color='royalblue'),
289
    marker=dict(size=8, color='firebrick')
290
)
291
292
# 4. 保存为HTML文件
293
fig.write_html("interactive_chart.html")
294
```
295
}}
296
297
#### 使用步骤:
298
1. 安装依赖:
299
   ```bash
300
   pip install pandas plotly
301
   ```
302
2. 运行代码后,生成 `interactive_chart.html`,用浏览器打开即可看到图表。
303
304
305
306
### **进阶方案(可选)**
307
1. **动态数据加载**(JavaScript):
308 11 jun chen
309
{{collapse(View details...)
310 1 jun chen
   ```html
311
   <input type="file" id="csvFile" accept=".csv">
312
   <div id="chart"></div>
313
   <script>
314
     document.getElementById('csvFile').addEventListener('change', function(e) {
315
       const file = e.target.files[0];
316
       const reader = new FileReader();
317
       reader.onload = function(e) {
318
         // 解析并绘制图表(代码同方法一)
319
       };
320
       reader.readAsText(file);
321
     });
322
   </script>
323
   ```
324 11 jun chen
}}
325 1 jun chen
   - 用户可上传任意CSV文件,实时生成图表。
326
327 11 jun chen
328 1 jun chen
2. **添加控件**(Python + Dash):
329 11 jun chen
{{collapse(View details...)
330 1 jun chen
   ```python
331
   from dash import Dash, dcc, html
332
   import pandas as pd
333
   import plotly.express as px
334
335
   app = Dash(__name__)
336
   df = pd.read_csv("data.csv")
337
338
   app.layout = html.Div([
339
       dcc.Graph(
340
           id='live-chart',
341
           figure=px.scatter(df, x='x', y='y', title='Dash动态图表')
342
       ),
343
       html.Button('更新数据', id='update-button')
344
   ])
345
346
   if __name__ == '__main__':
347
       app.run_server(debug=True)
348
   ```
349 11 jun chen
}}
350
351 1 jun chen
   - 运行后访问 `http://localhost:8050`,支持动态交互和按钮触发操作。
352
353
---