Project

General

Profile

Web tech » History » Version 16

jun chen, 02/13/2025 03:06 PM

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