Notepad++编写html文件使用D3绘图:数据可视化

为本人作业内容。题目如下:

解题思路:notepad++编写html文件,使用开源可视化工具D3绘制三种鸢尾花的五种表示图:平行坐标图、散点图、气泡图、特征雷达图、小提琴图。

1. notepad++如何编写html文件?

html文件基本代码框架如下:

javascript 复制代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>Hello 网页测试成功</h1>
</body>
</html>

这是一个测试程序。打开notepad++,新建文件,复制该代码,保存时文件名添加后缀".html",类型选择全部类型(".")。在文件夹中双击打开保存的html文件,可以看到浏览器出现下图,测试成功。

2. 能否成功读取CSV文件?

这是一段测试能否读取CSV文件的代码。CSV表无表头,共150行5列,第一列是sepal.length,第二列是sepal.width,第三列是petal.length,第四列是petal.width,第五列是variety。

java 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CSV 读取测试(无表头)</title>
    <style>
        body { font-family: Arial; margin: 30px; }
        #result { margin-top: 20px; padding: 10px; border: 1px solid #ccc; }
    </style>
</head>
<body>
    <h2>CSV 读取测试(150行 无表头)</h2>
    <input type="file" id="csvFile" accept=".csv" />
    <div id="result"></div>

    <script>
        document.getElementById('csvFile').addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = function(event) {
                const csvData = event.target.result;
                
                // 按行拆分,过滤空行
                const lines = csvData.split(/\r\n|\n/).filter(line => line.trim() !== '');

                const resultDiv = document.getElementById('result');
                resultDiv.innerHTML = `
                    <h3>✅ 读取成功!</h3>
                    <p>📊 检测到行数:${lines.length}</p>
                    <p>📝 每行列数:5列</p>
                    <hr>
                    <h4>前 10 行数据预览:</h4>
                `;

                // 显示前10行
                for (let i = 0; i < Math.min(10, lines.length); i++) {
                    resultDiv.innerHTML += `<pre>第 ${i+1} 行:${lines[i]}</pre>`;
                }
            };
            reader.readAsText(file, 'utf-8');
        });
    </script>
</body>
</html>

实现效果如何?双击保存的html文件,出现如下选择文件页面:

选择csv文件,可以成功读取,且正确显示前10行:

说明编写的html文件可以在浏览器上正常读取csv文件。

如果无法读取,在浏览器页面按F12键,打开控制器页面观察为何报错,一般会出现:

这是因为浏览器对本地 file:// 协议的安全限制,但一般不影响CSV读取,可以不用管。

如果出现问题,可以尝试:

1.将html文件和csv文件保存到桌面

2.打开终端,进入桌面:cd Desktop

3.运行:python -m http.server 8000

4.浏览器访问:http://localhost:8000/你保存的.html

3. 数据可视化代码结构

在html结构中,首先标明title,引入D3网址,设置需要的格式(直接告诉AI你要图例标题之类的它会帮你生成),添加文件选择按钮,输入大标题和子标题,明确三种配色(三种花)和四个特征值(题目中的四个特征),依次攥写平行坐标图、散点图、气泡图、特征雷达图、小提琴图的函数内容。读取CSV文件、解析CSV文件,最后调用绘图函数。

完整代码如下:

java 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>鸢尾花数据集可视化作业</title>
	
	<!-- 引入 D3 -->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", Arial, sans-serif;
        }

        body {
            background: #f7f8fa;
            padding: 30px;
        }

        .container {
            max-width: 1100px;
            margin: 0 auto;
        }

        .main-title {
            text-align: center;
            font-size: 26px;
            color: #2d3748;
            margin-bottom: 10px;
        }

        .sub-title {
            text-align: center;
            color: #718096;
            margin-bottom: 30px;
            font-size: 14px;
        }

        .legend {
            display: flex;
            justify-content: center;
            gap: 25px;
            margin-bottom: 40px;
        }

        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
            color: #4a5568;
        }

        .legend-color {
            width: 18px;
            height: 18px;
            border-radius: 4px;
        }

        .card {
            background: white;
            border-radius: 12px;
            padding: 25px;
            margin-bottom: 30px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
        }

        .card-title {
            font-size: 18px;
            color: #2d3748;
            margin-bottom: 20px;
            font-weight: 600;
        }
    </style>
</head>

<body>

	<!-- 文件选择按钮 -->
	<input type="file" id="csvFile" accept=".csv">
    
	<div class="container">
        <h1 class="main-title">鸢尾花数据集 5 种可视化展示</h1>
        <p class="sub-title">Iris Dataset Visualization</p>

        <div class="legend">
            <div class="legend-item"><div class="legend-color" style="background:#9400d3"></div>Setosa</div>
            <div class="legend-item"><div class="legend-color" style="background:#40e0d0"></div>Versicolor</div>
            <div class="legend-item"><div class="legend-color" style="background:#ffff00"></div>Virginica</div>
        </div>

        <div class="card">
            <div class="card-title">1. 平行坐标图</div>
            <div id="parallel"></div>
        </div>

        <div class="card">
            <div class="card-title">2. 散点图(花瓣长度 vs 花瓣宽度)</div>
            <div id="scatter"></div>
        </div>

        <div class="card">
            <div class="card-title">3. 气泡图(花萼尺寸 + 花瓣大小)</div>
            <div id="bubble"></div>
        </div>

        <div class="card">
            <div class="card-title">4. 特征雷达图(三类平均值)</div>
            <div id="radar"></div>
        </div>

        <div class="card">
            <div class="card-title">5. 花瓣长度小提琴图</div>
            <div id="violin"></div>
        </div>
    </div>

    <script>
        // 配色
        const color = d3.scaleOrdinal()
            .domain(["Setosa", "Versicolor", "Virginica"])
            .range(["#9400d3", "#40e0d0", "#ffff00"]);

        const features = ["sepalLength", "sepalWidth", "petalLength", "petalWidth"];

        // ====================== 1. 平行坐标图 ======================
        function drawParallel(data) {
            const w = 900, h = 420;
            const svg = d3.select("#parallel").append("svg").attr("width", w).attr("height", h);

            const x = d3.scalePoint().domain(features).range([50, w - 50]);
            const y = {};
            features.forEach(f => {
                y[f] = d3.scaleLinear().domain(d3.extent(data, d => d[f])).range([h - 40, 20]);
            });

            features.forEach(f => {
                svg.append("g").attr("transform", `translate(${x(f)},0)`).call(d3.axisLeft(y[f]));
                svg.append("text").attr("x", x(f)).attr("y", 15).style("text-anchor", "middle").text(f);
            });

            svg.selectAll("path")
                .data(data)
                .enter().append("path")
                .attr("d", d => d3.line()(features.map(f => [x(f), y[f](d[f])])))
                .style("fill", "none")
                .style("stroke", d => color(d.variety))
                .style("opacity", 0.5);
        };

        // ====================== 2. 散点图 ======================
        function drawScatter(data) {
            const w = 700, h = 400;
            const svg = d3.select("#scatter").append("svg").attr("width", w).attr("height", h);

            const x = d3.scaleLinear().domain(d3.extent(data, d => d.petalLength)).range([50, w - 30]);
            const y = d3.scaleLinear().domain(d3.extent(data, d => d.petalWidth)).range([h - 40, 20]);

            svg.append("g").attr("transform", `translate(0,${h-40})`).call(d3.axisBottom(x));
            svg.append("g").attr("transform", `translate(50,0)`).call(d3.axisLeft(y));

            svg.selectAll("circle")
                .data(data)
                .enter().append("circle")
                .attr("cx", d => x(d.petalLength))
                .attr("cy", d => y(d.petalWidth))
                .attr("r", 4.5)
                .style("fill", d => color(d.variety))
                .style("opacity", 0.8);
        };

        // ====================== 3. 气泡图 ======================
        function drawBubble(data) {
            const w = 700, h = 400;
            const svg = d3.select("#bubble").append("svg").attr("width", w).attr("height", h);

            const x = d3.scaleLinear().domain(d3.extent(data, d => d.sepalLength)).range([50, w - 30]);
            const y = d3.scaleLinear().domain(d3.extent(data, d => d.sepalWidth)).range([h - 40, 20]);
            const r = d3.scaleSqrt().domain(d3.extent(data, d => d.petalLength)).range([3, 13]);

            svg.append("g").attr("transform", `translate(0,${h-40})`).call(d3.axisBottom(x));
            svg.append("g").attr("transform", `translate(50,0)`).call(d3.axisLeft(y));

            svg.selectAll("circle")
                .data(data)
                .enter().append("circle")
                .attr("cx", d => x(d.sepalLength))
                .attr("cy", d => y(d.sepalWidth))
                .attr("r", d => r(d.petalLength))
                .style("fill", d => color(d.variety))
                .style("opacity", 0.7)
                .style("stroke", "#fff");
        };

        // ====================== 4. 雷达图 ======================
        function drawRadar(data) {
            const w = 450, h = 450, cx = w / 2, cy = h / 2, rad = 150;
            const svg = d3.select("#radar").append("svg").attr("width", w).attr("height", h).append("g").attr("transform", `translate(${cx},${cy})`);

            const groups = d3.groups(data, d => d.variety);
            const avg = groups.map(([k, vs]) => {
                let o = { variety: k };
                features.forEach(f => o[f] = d3.mean(vs, x => x[f]));
                return o;
            });

            const angle = d3.scalePoint().domain(features).range([0, Math.PI * 2]);

            features.forEach(f => {
                const a = angle(f);
                svg.append("line").attr("x1", 0).attr("y1", 0).attr("x2", Math.cos(a) * rad).attr("y2", Math.sin(a) * rad).style("stroke", "#ddd");
                svg.append("text").attr("x", Math.cos(a) * (rad + 20)).attr("y", Math.sin(a) * (rad + 20)).style("text-anchor", "middle").text(f);
            });

            avg.forEach(g => {
                const pts = features.map(f => {
                    const a = angle(f);
                    const r = (g[f] / 7.5) * rad;
                    return [Math.cos(a) * r, Math.sin(a) * r];
                });
                svg.append("polygon")
                    .attr("points", pts.join(","))
                    .style("fill", color(g.variety))
                    .style("fill-opacity", 0.3)
                    .style("stroke", color(g.variety))
                    .style("stroke-width", 2);
            });
        };

        // ====================== 5. 小提琴图 ======================
        function drawViolin(data) {
            const w = 650, h = 400;
            const svg = d3.select("#violin").append("svg").attr("width", w).attr("height", h);

            const cats = ["Setosa", "Versicolor", "Virginica"];
            const x = d3.scaleBand().domain(cats).range([60, w - 60]).padding(0.3);
            const y = d3.scaleLinear().domain(d3.extent(data, d => d.petalLength)).range([h - 40, 20]);

            svg.append("g").attr("transform", `translate(0,${h-40})`).call(d3.axisBottom(x));
            svg.append("g").attr("transform", `translate(60,0)`).call(d3.axisLeft(y));

            const kde = (arr, v) => d3.mean(arr, x => Math.exp(-((x - v) ** 2) / 0.3));

            cats.forEach(c => {
                const arr = data.filter(d => d.variety === c).map(d => d.petalLength);
                const cx = x(c) + x.bandwidth() / 2;
                const ys = d3.range(y.domain()[0], y.domain()[1], 0.1);

                const area = d3.area()
                    .y(d => y(d))
                    .x0(d => cx - kde(arr, d) * 70)
                    .x1(d => cx + kde(arr, d) * 70)
                    .curve(d3.curveBasis);

                svg.append("path").datum(ys).attr("d", area).style("fill", color(c)).style("opacity", 0.6);
            });
        };
		
		//读取csv文件
		document.getElementById('csvFile').addEventListener('change', function(e) {
			const file = e.target.files[0];
			if (!file) return;

			const reader = new FileReader();
			reader.onload = function(event) {
				const text = event.target.result;
		
		//解析CSV文件
				const data = d3.csvParseRows(text, d => {
					return {
					sepalLength: +d[0],
					sepalWidth:  +d[1],
					petalLength: +d[2],
					petalWidth:  +d[3],
					variety:     d[4]
					};
				});

		// 画图
		console.log("解析完成,数据行数:", data.length); // 调试用
		drawParallel(data);
		drawScatter(data);
		drawBubble(data);
		drawRadar(data);
		drawViolin(data);
		}
      reader.readAsText(file);
    });		
    </script>
</body>
</html>

4. 修改代码时容易犯的错

首先,要分清楚自执行函数和普通函数:

java 复制代码
// 自执行函数
(function drawParallel() { ... })();

// 普通函数
function drawParallel(data) { ... }

由于本人一开始想直接把CSV中的数据内置在代码里,故使用自执行函数。在开头设置好数据后,直接调用可以生成图像。

但后续尝试读取CSV内容生成图像会报错,需要将五个绘图函数改成普通函数。同时,代码顺序应该是:绘图函数定义------读取CSV------解析CSV------调用绘图函数,顺序乱会画不出来。

其次,函数参数要正确:

读取CSV中特征值是d.feature形式,但特征值定义和绘图函数定义部分却是d["feature"]形式,生成不出来。

5. 结果

6. 心得

很好的可视化题目,使我的脑仁旋转。

相关推荐
Chunyyyen2 小时前
【第三十八周】论文复现记录01
学习
darkb1rd2 小时前
opencli-rs:单命令获取全网信息的 Rust 命令行神器
开源·github·好物分享
向量引擎2 小时前
肝了三天三夜!四大AI模型(DeepSeek/Gemini/ChatGPT/豆包)深度横评,开发者该如何选?
人工智能·chatgpt·架构·开源·aigc·文心一言·api调用
woodykissme2 小时前
揭秘表面粗糙度的16%规则:为什么允许16%的超差?
学习·制造·机械·粗糙度·工艺知识
老星*3 小时前
Supabase:开源Firebase替代完全指南:后端即服务的完整教程
开源·github·好用工具
蓝策电子3 小时前
蓝牙AoA技术如何实现智慧机场人员与资产管控
大数据·经验分享·物联网·信息可视化·智慧城市
卖报的大地主3 小时前
Learn Claude Code Agent 开发 | 5、按需技能加载:领域知识不用全塞系统提示
人工智能·笔记
秋刀鱼不做梦3 小时前
网络编程和Socket套接字(UDP+TCP)(如果想知道Java中有关网络编程和Socket套接字的知识,那么只看这一篇就足够了!)
网络·网络协议·学习·tcp/ip·udp
AI成长日志3 小时前
【笔面试算法学习专栏】链表操作专题:反转、环形检测与合并
学习·算法·面试