Skip to content

功能-情绪雷达图展示

字数
2285 字
阅读时间
11 分钟

版本:v1.0.0 | 更新日期:2025-10-08 关联页面:心语精灵对话界面 | 关联组件:EmotionRadarChart

功能概述

情绪雷达图展示功能将AI分析的情绪数据以直观的雷达图形式可视化呈现,帮助用户更好地理解自己的情绪状态和心理特征,为情绪管理和心理健康提供数据支撑。

设计理念

  • 直观可视化:将抽象的心理维度转化为直观的图形展示
  • 动态交互:支持动态展示和历史对比,增强用户参与感
  • 专业标准:基于心理学理论设计的五维评估体系
  • 个性化反馈:根据雷达图特征提供个性化的情绪管理建议

雷达图维度设计

五维心理特征

        信任度 (trust)


开放度 ───┼─── 抵抗度
(openness) │ (resistance)

        压力度 (stress)

        掌控度 (control)

1. 信任度 (trust)

定义:用户对他人的信任程度和社交开放性 高分特征:愿意信任他人,乐于社交,寻求支持 低分特征:防备心理强,难以信任他人,倾向自我保护

2. 开放度 (openness)

定义:用户表达情感和接纳新观念的开放程度 高分特征:情感表达丰富,乐于尝试新事物,接纳度高 低分特征:情感内敛,习惯固守原有认知,变化适应性差

3. 抵抗度 (resistance)

定义:用户对建议、改变或外界影响的抵抗程度 高分特征:坚持己见,不易被说服,独立性强 低分特征:易受影响,顺从性强,缺乏主见

4. 压力度 (stress)

定义:用户当前感受到的压力水平和紧张程度 高分特征:感到压力山大,紧张焦虑,难以放松 低分特征:心态平和,压力感知低,放松状态

5. 掌控度 (control)

定义:用户对生活和情绪的掌控感程度 高分特征:感到生活可控,能管理情绪,积极主动 低分特征:感到失控,被动应对,缺乏自主性

界面设计

雷达图样式

基础样式

css
.radar-chart {
  width: 200px;
  height: 200px;
  position: relative;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 50%;
}

.radar-grid {
  stroke: rgba(255, 255, 255, 0.3);
  stroke-width: 1;
  fill: none;
}

.radar-axis {
  stroke: rgba(255, 255, 255, 0.5);
  stroke-width: 1;
}

.radar-data {
  fill: rgba(74, 144, 226, 0.3);
  stroke: #4a90e2;
  stroke-width: 2;
}

.radar-point {
  fill: #4a90e2;
  stroke: white;
  stroke-width: 2;
}

动画效果

css
@keyframes radarDraw {
  0% {
    opacity: 0;
    transform: scale(0.8);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

.radar-data {
  animation: radarDraw 0.6s ease-out;
  transform-origin: center;
}

组件结构

EmotionRadarChart
├── RadarCanvas (雷达图画布)
├── DimensionLabels (维度标签)
├── DataPoints (数据点)
├── TrendOverlay (趋势对比层)
├── Tooltip (悬停提示)
└── ControlPanel (控制面板)
    ├── HistoryToggle (历史对比开关)
    ├── AnimationSpeed (动画速度)
    └── ExportButton (导出功能)

技术实现

Canvas绘制实现

javascript
class EmotionRadarChart {
  constructor(canvas, data) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.data = data;
    this.dimensions = ['trust', 'openness', 'resistance', 'stress', 'control'];
    this.center = { x: canvas.width / 2, y: canvas.height / 2 };
    this.radius = Math.min(canvas.width, canvas.height) / 2 - 20;
  }

  drawGrid() {
    const levels = 5;
    for (let i = 1; i <= levels; i++) {
      const levelRadius = (this.radius / levels) * i;
      this.drawPolygon(levelRadius, `rgba(255, 255, 255, ${0.1 * i})`);
    }
  }

  drawAxes() {
    const angleStep = (Math.PI * 2) / this.dimensions.length;

    this.dimensions.forEach((dimension, index) => {
      const angle = angleStep * index - Math.PI / 2;
      const x = this.center.x + Math.cos(angle) * this.radius;
      const y = this.center.y + Math.sin(angle) * this.radius;

      this.ctx.beginPath();
      this.ctx.moveTo(this.center.x, this.center.y);
      this.ctx.lineTo(x, y);
      this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
      this.ctx.stroke();

      this.drawLabel(dimension, x, y, angle);
    });
  }

  drawData(data, color = '#4a90e2') {
    const angleStep = (Math.PI * 2) / this.dimensions.length;
    const points = [];

    this.dimensions.forEach((dimension, index) => {
      const angle = angleStep * index - Math.PI / 2;
      const value = data[dimension] || 0;
      const distance = (value / 100) * this.radius;
      const x = this.center.x + Math.cos(angle) * distance;
      const y = this.center.y + Math.sin(angle) * distance;
      points.push({ x, y });
    });

    this.drawPolygonFromPoints(points, color);
    this.drawDataPoints(points);
  }

  animateTransition(fromData, toData, duration = 600) {
    const startTime = Date.now();

    const animate = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const easeProgress = this.easeInOutCubic(progress);

      const currentData = this.interpolateData(fromData, toData, easeProgress);
      this.clear();
      this.drawGrid();
      this.drawAxes();
      this.drawData(currentData);

      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    };

    animate();
  }
}

数据处理

javascript
// 数据归一化处理
function normalizeEmotionData(rawData) {
  return {
    trust: Math.max(0, Math.min(100, rawData.trust * 100)),
    openness: Math.max(0, Math.min(100, rawData.openness * 100)),
    resistance: Math.max(0, Math.min(100, rawData.resistance * 100)),
    stress: Math.max(0, Math.min(100, rawData.stress * 100)),
    control: Math.max(0, Math.min(100, rawData.control * 100))
  };
}

// 趋势分析
function analyzeEmotionTrend(historicalData) {
  if (historicalData.length < 2) return null;

  const trend = {};
  Object.keys(historicalData[0]).forEach(dimension => {
    const values = historicalData.map(data => data[dimension]);
    const change = values[values.length - 1] - values[0];
    trend[dimension] = {
      change: change,
      direction: change > 0 ? 'up' : change < 0 ? 'down' : 'stable',
      significance: Math.abs(change) > 10 ? 'high' : 'low'
    };
  });

  return trend;
}

交互功能

基础交互

  1. 悬停提示

    • 鼠标悬停在数据点上显示具体数值
    • 显示维度名称和当前值
    • 提供维度的简要说明
  2. 点击详情

    • 点击数据点展开详细信息
    • 显示该维度的历史趋势
    • 提供相关建议和资源
  3. 缩放功能

    • 支持双指缩放查看细节
    • 保持中心点不变
    • 自动适配显示区域

高级交互

  1. 历史对比

    • 选择历史时间点进行对比
    • 使用不同颜色区分不同时间
    • 显示变化趋势和差异
  2. 动画控制

    • 播放/暂停动画
    • 调整动画速度
    • 循环播放历史变化
  3. 数据导出

    • 导出当前雷达图为图片
    • 导出数据为CSV格式
    • 分享到社交媒体

响应式设计

尺寸适配

css
/* 移动端适配 */
@media (max-width: 768px) {
  .radar-chart {
    width: 150px;
    height: 150px;
  }

  .dimension-label {
    font-size: 12px;
  }
}

/* 桌面端适配 */
@media (min-width: 769px) {
  .radar-chart {
    width: 250px;
    height: 250px;
  }

  .dimension-label {
    font-size: 14px;
  }
}

触摸优化

javascript
class TouchRadarChart extends EmotionRadarChart {
  constructor(canvas, data) {
    super(canvas, data);
    this.setupTouchEvents();
  }

  setupTouchEvents() {
    let lastTouch = null;

    this.canvas.addEventListener('touchstart', (e) => {
      lastTouch = e.touches[0];
    });

    this.canvas.addEventListener('touchmove', (e) => {
      if (lastTouch) {
        const deltaX = e.touches[0].clientX - lastTouch.clientX;
        const deltaY = e.touches[0].clientY - lastTouch.clientY;

        // 处理触摸手势
        this.handleTouchGesture(deltaX, deltaY);
        lastTouch = e.touches[0];
      }
    });
  }
}

数据分析功能

情绪模式识别

javascript
function analyzeEmotionPattern(data) {
  const patterns = [];

  // 高压力模式
  if (data.stress > 70 && data.control < 30) {
    patterns.push({
      type: 'high_stress',
      description: '高压力低掌控模式',
      suggestion: '建议寻求专业支持,学习压力管理技巧'
    });
  }

  // 社交开放模式
  if (data.trust > 70 && data.openness > 70) {
    patterns.push({
      type: 'social_open',
      description: '社交开放模式',
      suggestion: '良好的社交状态,继续保持开放心态'
    });
  }

  // 抗拒改变模式
  if (data.resistance > 70 && data.openness < 30) {
    patterns.push({
      type: 'resistance_to_change',
      description: '抗拒改变模式',
      suggestion: '尝试逐步接纳新观念,保持开放心态'
    });
  }

  return patterns;
}

健康状态评估

javascript
function assessEmotionalHealth(data) {
  let score = 0;
  let factors = [];

  // 理想状态:高信任、高开放、中低抵抗、低压力、高掌控
  if (data.trust > 60) score += 20;
  else factors.push('提升人际信任');

  if (data.openness > 60) score += 20;
  else factors.push('增强开放心态');

  if (data.resistance < 60) score += 20;
  else factors.push('降低抗拒心理');

  if (data.stress < 40) score += 20;
  else factors.push('管理压力水平');

  if (data.control > 60) score += 20;
  else factors.push('增强生活掌控感');

  return {
    score: score,
    level: score >= 80 ? 'excellent' : score >= 60 ? 'good' : 'needs_improvement',
    factors: factors
  };
}

性能优化

渲染优化

  1. Canvas优化

    • 使用 requestAnimationFrame 优化动画
    • 避免频繁的重绘操作
    • 合并绘制指令减少调用
  2. 数据缓存

    • 缓存计算结果避免重复计算
    • 使用 Web Workers 处理复杂计算
    • 本地存储历史数据
  3. 内存管理

    • 及时清理不需要的图形对象
    • 限制历史数据存储数量
    • 定期垃圾回收

加载优化

javascript
// 懒加载雷达图组件
class LazyRadarChart {
  constructor(container) {
    this.container = container;
    this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
    this.observer.observe(container);
  }

  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        this.loadChart();
        this.observer.unobserve(entry.target);
      }
    });
  }

  async loadChart() {
    const { EmotionRadarChart } = await import('./components/EmotionRadarChart.js');
    this.chart = new EmotionRadarChart(this.container);
  }
}

测试策略

单元测试

javascript
describe('EmotionRadarChart', () => {
  test('should normalize data correctly', () => {
    const rawData = { trust: 0.8, openness: 0.6, resistance: 0.3, stress: 0.2, control: 0.9 };
    const normalized = normalizeEmotionData(rawData);

    expect(normalized.trust).toBe(80);
    expect(normalized.openness).toBe(60);
    expect(normalized.resistance).toBe(30);
    expect(normalized.stress).toBe(20);
    expect(normalized.control).toBe(90);
  });

  test('should identify emotion patterns', () => {
    const highStressData = { trust: 50, openness: 40, resistance: 30, stress: 80, control: 20 };
    const patterns = analyzeEmotionPattern(highStressData);

    expect(patterns).toContainEqual({
      type: 'high_stress',
      description: '高压力低掌控模式',
      suggestion: '建议寻求专业支持,学习压力管理技巧'
    });
  });
});

集成测试

javascript
describe('Radar Chart Integration', () => {
  test('should render with real data', async () => {
    const mockData = {
      primary_emotion: '焦虑',
      radar_dimensions: {
        trust: 0.3,
        openness: 0.4,
        resistance: 0.7,
        stress: 0.8,
        control: 0.2
      }
    };

    const component = mount(EmotionRadarChart, { data: mockData });
    await component.update();

    expect(component.canvas).toBeDefined();
    expect(component.dataPoints).toHaveLength(5);
  });
});

相关文档

版本记录

版本日期变更内容作者
v1.0.02025-10-08初始版本,完成雷达图核心功能设计哈雷酱

贡献者

The avatar of contributor named as Cai Hongyu Cai Hongyu

文件历史

撰写