全部版块 我的主页
论坛 数据科学与人工智能 人工智能
25 0
2025-12-02

散点图数据可视化

10

前言

本项目开发了一个基于 Electron 框架的二维散点图可视化工具,用于呈现和分析数据点的空间分布特征与相互关系。整个应用使用纯 JavaScript 构建,通过 Canvas API 实现图形绘制,具备高效渲染能力。支持多种数据生成模式,包括正态分布、均匀分布及完全随机分布,并集成了丰富的交互功能,如鼠标拖拽平移、滚轮缩放、悬停信息提示以及可调节的数据点尺寸等。在架构设计上,遵循 Electron 的主进程与渲染进程分离机制,利用 contextBridge 安全地暴露必要接口,保障了系统的安全性与运行效率。

技术实现解析

Electron 架构设计

应用采用标准的 Electron 主从进程模型:主进程负责管理窗口生命周期与系统级事件;渲染进程专注于界面展示与用户操作响应。通过 preload 脚本桥接两个环境,利用 contextBridge 向前端安全注入所需 API,避免直接暴露 Node.js 接口,提升安全性。该结构有效隔离了 UI 线程与系统资源访问,确保稳定性和性能表现。

散点图绘制核心逻辑

内置多种数据分布算法,能够按需生成符合特定统计规律的数据集。其中正态分布采用 Box-Muller 变换方法生成高质量高斯随机数,保证数据自然聚集特性。同时构建了坐标映射系统,实现原始数据空间到画布像素坐标的精准转换,支持动态缩放和平移后的坐标还原。

用户交互功能实现

提供完整的视图操控体验:通过鼠标按下拖动实现画布平移;使用滚轮进行缩放操作,并自动保持当前视野中心点不变;当鼠标悬停于某数据点时,显示其具体数值信息;此外还支持通过滑块实时调整所有数据点的显示大小,增强可视化的灵活性与可读性。

渲染性能优化策略

借助 Canvas 提供的低层级绘图能力,直接操作像素进行高效绘制。引入视图裁剪机制,仅渲染当前可见区域内的数据点,显著减少不必要的绘制开销。颜色方案根据数据点位置动态计算,提升视觉区分度。坐标轴与刻度标签也根据当前缩放级别智能调整密度与格式,确保不同尺度下均有良好可读性。

界面布局与用户体验

整体采用响应式设计原则,适配各种屏幕分辨率与窗口尺寸变化。控制面板布局清晰,集中管理数据生成参数与视图操作按钮,操作直观。实时更新并展示当前数据集的基本统计信息(如总数、范围等),帮助用户快速了解数据状态。所有交互操作均配有明确的状态反馈,提升使用流畅度与友好性。

关键代码结构说明

<!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>

    <!-- 散点图容器 -->
      
数据点数量: 0
X轴范围: 0 - 0
Y轴范围: 0 - 0

主进程代码实现如下:


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: '散点图'
    });

    mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));

    mainWindow.on('closed', () => {
        mainWindow = null;
    });
}

app.whenReady().then(() => {
    createWindow();

    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});
    

散点图核心渲染功能由以下类实现:


/**
 * 散点图渲染器类
 */
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);

    // 绘制X轴和Y轴
    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();
}

运行说明

安装项目所需依赖:

npm install

启动应用程序:

npm start

应用提供简洁直观的操作界面,用户可通过控制面板调节以下参数:

  • 调整图表的缩放级别
  • 拖动平移视图以查看不同区域
  • 动态更新数据并实时重绘图表
  • 设置坐标轴范围与样式选项

控制散点图中数据点的总数由“数据点数量”参数决定,用户可根据需要调节显示的数据规模。

“分布类型”用于设定数据点的排列模式,支持多种分布方式,如正态分布、均匀分布以及随机分布等。

通过“点大小”选项可以自定义散点图中各个数据点的视觉尺寸,提升图表可读性或突出重点区域。

点击“生成数据按钮”,系统将根据当前设置重新创建一组符合配置的散点数据。

若需恢复初始状态,可使用“重置视图按钮”将图表视角与参数设置还原至默认值。

用户可通过鼠标操作实现对散点图的交互式浏览:

  • 按住并拖动鼠标:实现视图平移
  • 滚动鼠标滚轮:对图表内容进行缩放
  • 将鼠标悬停在某个数据点上:实时查看该点的精确坐标信息

应用底部区域展示当前散点图的关键统计信息,包括总数据点数、X轴与Y轴的数据范围,便于用户快速掌握整体数据分布特征。

鸿蒙PC适配改造指南

1. 环境准备

系统要求

操作系统:Windows 10 或 Windows 11;内存建议8GB以上;磁盘预留至少20GB可用空间。

工具安装

需安装以下开发工具:

  • DevEco Studio 5.0及以上版本,并配置鸿蒙SDK(API 20+)
  • Node.js 18.x+

2. 获取Electron鸿蒙编译产物

访问Electron鸿蒙官方仓库,下载Electron 34或更高版本的Release发布包(格式为.zip)。

将压缩包解压至项目根目录,并确保以下路径中包含必要的核心.so库文件:

electron/libs/arm64-v8a/

3. 部署应用代码

按照如下结构组织Electron应用源码:

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调试功能,并通过Type-C数据线连接至主机。

编译运行:点击运行按钮或使用快捷键 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动画逻辑,减少复杂效果,降低页面重绘频率以提升性能表现。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群