Project

General

Profile

Web tech » History » Version 11

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