Project

General

Profile

Web tech » History » Version 15

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