Electron for 鸿蒙PC项目实战案例之散点图数据可视化应用

前言

本项目实现了一个基于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;
        }
    }
});

运行说明

  1. 安装依赖:npm install
  2. 启动应用: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鸿蒙编译产物

  1. 登录Electron 鸿蒙官方仓库

  2. 下载Electron 34+版本的Release包(.zip格式)

  3. 解压到项目目录,确认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. 配置与运行

  1. 打开项目:在DevEco Studio中打开ohos_hap目录

  2. 配置签名

    进入File → Project Structure → Signing Configs

  3. 自动生成调试签名或导入已有签名

  4. 连接设备

    启用鸿蒙设备开发者模式和USB调试

  5. 通过USB Type-C连接电脑

  6. 编译运行:点击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动画效果,减少重绘频率

相关推荐
国服第二切图仔2 小时前
Electron for 鸿蒙PC项目实战案例之气泡图组件
javascript·electron·harmonyos
国服第二切图仔4 小时前
Electron for鸿蒙PC项目实战之颜色选择器组件
electron·鸿蒙pc
国服第二切图仔4 小时前
Electron for 鸿蒙PC数据可视化应用—柱状图
信息可视化·electron·鸿蒙pc
Karl_wei9 小时前
桌面应用开发,Flutter 与 Electron如何选
windows·flutter·electron
tyatyatya10 小时前
MATLAB图形标注教程:title()/xlabel()/ylabel()/legend()/grid on全解析
数据库·matlab·信息可视化
黄团团15 小时前
Vue2整合Electron开发桌面级应用以及打包发布(提供Gitee源码)
前端·javascript·vue.js·elementui·electron
#做一个清醒的人18 小时前
【Electron】IpcMainEvent 参数使用总结
前端·electron
梦里不知身是客1119 小时前
帆软仪表盘作用
信息可视化
冰糖小新新1 天前
基于CanMV K230的工地巡检机器人
人工智能·信息可视化·机器人