前言
本项目实现了一个基于Electron的散点图数据可视化应用,用于展示二维数据点的分布和关系。应用采用纯JavaScript实现,通过Canvas API直接绘制散点图,支持多种数据分布类型(正态分布、均匀分布、随机分布),提供交互式操作(拖拽平移、滚轮缩放、鼠标悬停数据提示),以及点大小调整等功能。应用架构遵循Electron的主进程-渲染进程模式,使用contextBridge安全暴露API,确保良好的性能和用户体验。

技术要点分析
1. Electron应用架构
- 采用主进程-渲染进程分离架构,主进程负责窗口管理和应用生命周期
- 使用preload.js进行进程间通信,通过contextBridge安全暴露API
- 渲染进程负责UI渲染和用户交互,保持与Node.js环境隔离
2. 散点图核心算法
- 实现多种数据分布生成算法:正态分布、均匀分布、随机分布
- 使用Box-Muller变换算法生成高质量的高斯随机数
- 实现数据点坐标转换系统,支持数据空间和画布空间的双向映射
3. 交互功能设计
- 实现鼠标拖拽平移视图功能
- 支持鼠标滚轮缩放操作,保持缩放中心不变
- 实现鼠标悬停时的数据点信息提示
- 提供数据点大小动态调整功能
4. 可视化渲染优化
- 使用Canvas绘图API实现高性能渲染
- 实现视图裁剪,只渲染可见区域内的数据点
- 根据数据点位置动态生成颜色,提高数据辨识度
- 优化坐标轴和刻度标签的绘制,支持不同缩放级别
5. 用户界面设计
- 采用响应式布局,适配不同屏幕尺寸
- 设计直观的控制面板,提供数据生成和视图控制功能
- 实现实时数据统计信息显示
- 提供清晰的视觉反馈和交互状态指示
核心代码解读
HTML结构
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>97-scatter-plot</title>
<link rel="stylesheet" href="../style.css">
</head>
<body>
<div class="container">
<!-- 应用头部 -->
<header class="app-header">
<h1>散点图数据可视化</h1>
</header>
<!-- 控制面板 -->
<div class="control-panel">
<div class="control-group">
<label for="data-count">数据点数量:</label>
<input type="number" id="data-count" min="10" max="1000" value="200">
</div>
<div class="control-group">
<label for="distribution">分布类型:</label>
<select id="distribution">
<option value="normal">正态分布</option>
<option value="uniform">均匀分布</option>
<option value="random">随机分布</option>
</select>
</div>
<div class="control-group">
<label for="point-size">点大小:</label>
<input type="range" id="point-size" min="5" max="20" value="10">
<span id="point-size-value">10</span>
</div>
<div class="button-group">
<button id="generate-btn">生成数据</button>
<button id="reset-btn">重置视图</button>
</div>
</div>
<!-- 散点图容器 -->
<div class="chart-container">
<canvas id="scatter-chart"></canvas>
<div class="chart-info">
<div>数据点数量: <span id="points-count">0</span></div>
<div>X轴范围: <span id="x-range">0 - 0</span></div>
<div>Y轴范围: <span id="y-range">0 - 0</span></div>
</div>
</div>
</div>
<script src="../renderer.js"></script>
</body>
</html>
主进程代码
javascript
const { app, BrowserWindow } = require('electron');
const path = require('path');
// 保持对窗口对象的全局引用
let mainWindow;
function createWindow() {
// 创建浏览器窗口
mainWindow = new BrowserWindow({
width: 1000,
height: 700,
webPreferences: {
preload: path.join(__dirname, 'src', 'preload.js'),
nodeIntegration: false,
contextIsolation: true
},
title: '散点图'
});
// 加载index.html文件
mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));
// 窗口关闭事件
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// 应用就绪事件
app.whenReady().then(() => {
createWindow();
// macOS上的特殊处理
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// 应用窗口全部关闭事件
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
散点图核心渲染功能
javascript
/**
* 散点图渲染器类
*/
class ScatterPlotRenderer {
constructor() {
// 初始化配置和状态
this.canvas = document.getElementById('scatter-chart');
this.ctx = this.canvas.getContext('2d');
this.data = [];
this.transform = { x: 0, y: 0, scale: 1 };
// 更多初始化代码...
}
/**
* 生成正态分布数据
*/
generateNormalData() {
const data = [];
const mean = 50;
const stdDev = 15;
for (let i = 0; i < this.config.dataCount; i++) {
const x = this.gaussianRandom(mean, stdDev);
const y = this.gaussianRandom(mean, stdDev);
data.push({ x: Math.max(0, Math.min(100, x)), y: Math.max(0, Math.min(100, y)) });
}
return data;
}
/**
* 高斯随机数生成(Box-Muller变换)
*/
gaussianRandom(mean, stdDev) {
let u = 0, v = 0;
while(u === 0) u = Math.random();
while(v === 0) v = Math.random();
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2 * Math.PI * v);
return mean + z * stdDev;
}
/**
* 数据坐标到画布坐标转换
*/
dataToCanvas(dataX, dataY) {
const xRange = this.xRange.max - this.xRange.min;
const yRange = this.yRange.max - this.yRange.min;
const canvasX = this.config.padding +
((dataX - this.xRange.min) / xRange) * (this.canvas.width - 2 * this.config.padding);
const canvasY = this.canvas.height - this.config.padding -
((dataY - this.yRange.min) / yRange) * (this.canvas.height - 2 * this.config.padding);
// 应用变换
return {
x: canvasX * this.transform.scale + this.transform.x,
y: canvasY * this.transform.scale + this.transform.y
};
}
/**
* 渲染散点图
*/
render() {
// 清空画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 保存当前状态并应用变换
this.ctx.save();
this.ctx.translate(this.transform.x, this.transform.y);
this.ctx.scale(this.transform.scale, this.transform.scale);
// 绘制坐标轴
this.drawAxes();
// 绘制数据点
this.drawPoints();
// 恢复状态
this.ctx.restore();
}
/**
* 处理鼠标滚轮缩放
*/
handleWheel(e) {
e.preventDefault();
const scaleFactor = e.deltaY > 0 ? 0.9 : 1.1;
const newScale = Math.max(0.1, Math.min(5, this.transform.scale * scaleFactor));
// 计算鼠标位置
const rect = this.canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 调整平移以保持缩放中心不变
const scaleRatio = newScale / this.transform.scale;
this.transform.x = mouseX - (mouseX - this.transform.x) * scaleRatio;
this.transform.y = mouseY - (mouseY - this.transform.y) * scaleRatio;
this.transform.scale = newScale;
this.render();
}
}
预加载脚本
javascript
const { contextBridge } = require('electron');
// 暴露给渲染进程的API
contextBridge.exposeInMainWorld('electronAPI', {
// 提供数据验证工具函数
scatterPlotUtils: {
validateData: (data) => {
if (!Array.isArray(data)) {
throw new Error('数据必须是数组格式');
}
const invalidPoints = data.filter(point =>
typeof point !== 'object' ||
point === null ||
typeof point.x !== 'number' ||
typeof point.y !== 'number' ||
isNaN(point.x) ||
isNaN(point.y)
);
if (invalidPoints.length > 0) {
throw new Error(`发现 ${invalidPoints.length} 个无效的数据点`);
}
return true;
}
}
});
运行说明
- 安装依赖:
npm install - 启动应用:
npm start
应用提供了直观的用户界面,用户可以通过控制面板调整以下参数:
- 数据点数量:控制散点图中显示的数据点总数
- 分布类型:选择数据点的分布模式(正态分布、均匀分布、随机分布)
- 点大小:调整数据点的显示大小
- 生成数据按钮:根据当前配置生成新的散点图数据
- 重置视图按钮:恢复初始视图状态
用户可以通过鼠标与散点图进行交互:
- 拖拽鼠标:平移视图
- 滚轮滚动:缩放视图
- 悬停在数据点上:查看具体的坐标值
应用底部显示了当前散点图的统计信息,包括数据点数量、X轴和Y轴的数据范围,帮助用户更好地理解数据分布情况。
鸿蒙PC适配改造指南
1. 环境准备
-
系统要求:Windows 10/11、8GB RAM以上、20GB可用空间
-
工具安装 :
DevEco Studio 5.0+(安装鸿蒙SDK API 20+)
-
Node.js 18.x+
2. 获取Electron鸿蒙编译产物
-
下载Electron 34+版本的Release包(.zip格式)
-
解压到项目目录,确认
electron/libs/arm64-v8a/下包含核心.so库
3. 部署应用代码
将Electron应用代码按以下目录结构放置:

plaintext
web_engine/src/main/resources/resfile/resources/app/
├── main.js
├── package.json
└── src/
├── index.html
├── preload.js
├── renderer.js
└── style.css
4. 配置与运行
-
打开项目:在DevEco Studio中打开ohos_hap目录
-
配置签名 :
进入File → Project Structure → Signing Configs
-
自动生成调试签名或导入已有签名
-
连接设备 :
启用鸿蒙设备开发者模式和USB调试
-
通过USB Type-C连接电脑
-
编译运行:点击Run按钮或按Shift+F10
5. 验证检查项
-
✅ 应用窗口正常显示
-
✅ 窗口大小可调整,响应式布局生效
-
✅ 控制台无"SysCap不匹配"或"找不到.so文件"错误
-
✅ 动画效果正常播放
跨平台兼容性
| 平台 | 适配策略 | 特殊处理 |
|---|---|---|
| Windows | 标准Electron运行 | 无特殊配置 |
| macOS | 标准Electron运行 | 保留dock图标激活逻辑 |
| Linux | 标准Electron运行 | 确保系统依赖库完整 |
| 鸿蒙PC | 通过Electron鸿蒙适配层 | 禁用硬件加速,使用特定目录结构 |
鸿蒙开发调试技巧
1. 日志查看
在DevEco Studio的Log面板中过滤"Electron"关键词,查看应用运行日志和错误信息。
2. 常见问题解决
-
"SysCap不匹配"错误:检查module.json5中的reqSysCapabilities,只保留必要系统能力
-
"找不到.so文件"错误:确认arm64-v8a目录下四个核心库文件完整
-
窗口不显示:在main.js中添加app.disableHardwareAcceleration()
-
动画卡顿:简化CSS动画效果,减少重绘频率