找回密码
 立即注册
首页 业界区 安全 React-Native开发鸿蒙NEXT-svg绘制睡眠质量图part1 ...

React-Native开发鸿蒙NEXT-svg绘制睡眠质量图part1

计海龄 2025-5-30 16:01:14
React-Native开发鸿蒙NEXT-svg绘制睡眠质量图part1

FBI WARNING:
The complete demo will be posted at the end of the series, so no need to worry.

上的个逼班,做的是个蓝牙戒指。好几个厂家的样品测了一阵,算是选中了一个开始对接。

有个功能是监测睡眠,睡眠数据会生成一张图。

这图有说叫睡眠阶段图的,有说叫睡眠质量图的,没找到权威的说法。看过好几家戒指的App,几乎都做的差不多,感觉上是一个现成的三方,或者是某个图表组件中的一个功能。找了一阵,可能就是因为不知道它的正式名称,一直没找到对应的三方组件。单论图表组件,Victory和ECharts里都没找到,这俩库都找不到合适的,感觉这么找下去有点悬,还是先出个方案开发吧。于是就想到了用svg去画一下。毕竟react-native-svg鸿蒙也支持,后续即便找到三方,也不一定能鸿蒙化,反而不美。之前从没做过图表,这玩意一般都让前端去开发,这次也是临时学了下。
首先科普下睡眠质量,这个倒是有医学上的定义的---按照阶段可以分为深睡眠,浅睡眠,快速眼动,清醒这四个阶段。定义一个最简单的模型
  1. enum SleepStageEnum {
  2.   Deep = 0, // 0 深睡眠
  3.   Light, // 1 浅睡眠
  4.   REM, // 2 快速眼动
  5.   AWAKE, // 3 清醒
  6. }
复制代码
我们不关心戒指怎么去得到这些数据,只要知道数据的采集频率,比如每隔五分钟采集一次数据,反馈当前的睡眠状态。所以数据上可以用采样时间+当前睡眠状态来模拟。
  1. // 示例数据
  2. const sleepData: SleepStageModel[] = [
  3.   {time: '0:00', stage: 3},
  4.   {time: '5:00', stage: 3},
  5.   {time: '10:00', stage: 1},
  6.   {time: '15:00', stage: 1},
  7.   {time: '20:00', stage: 3},
  8.   {time: '25:00', stage: 0},
  9.   {time: '30:00', stage: 0},
  10.   {time: '35:00', stage: 0},
  11.   {time: '40:00', stage: 0},
  12.   {time: '45:00', stage: 1},
  13.   {time: '50:00', stage: 1},
  14.   {time: '55:00', stage: 3},
  15.   {time: '60:00', stage: 3},
  16.   {time: '65:00', stage: 2},
  17.   {time: '70:00', stage: 2},
  18.   {time: '75:00', stage: 1},
  19.   {time: '80:00', stage: 3},
  20.   {time: '85:00', stage: 2},
  21.   {time: '0:00', stage: 3},
  22.   {time: '5:00', stage: 3},
  23.   {time: '10:00', stage: 1},
  24.   {time: '15:00', stage: 1},
  25.   {time: '20:00', stage: 3},
  26.   {time: '25:00', stage: 0},
  27.   // ...更多数据
  28. ];
复制代码
现在回过头来看看这个图表,看着有点科技,其实扒光效果放到excel里就类似这挫样。其中画红圈的地方是采样数据点。

可以看到,阶段的“面积变化”是由当前点和它前一个点的状态决定的。两者不一致则意味着上一个阶段结束,下一个阶段开始。这是绘制图表逻辑上最重要的地方。
react-native-svg的坐标系,原点位于左上角,横轴x纵轴y,知道坐标系就可以设计将模拟数据“摆放”到坐标系相应位置上。先定义一些辅助常量(因为是整个demo做完了才开始码字,代码难免和当前描述相比有点超前,见谅)
  1. // 组件目前默认按照屏幕宽度为基准进行布局
  2. const {width: SCREEN_WIDTH} = Dimensions.get('window');
  3. const SHOW_DATA_POINT = true; // 是否在图标展示数据点(调试阶段可以更清晰核对数据与图是否一致)
  4. const MARGIN = 24; // 左右间隙,用于支持底部指针图标的左右拖拽
  5. const POINT_RADIUS = 4; // 数据点的弧度
  6. const AREA_HEIGHT = 30; // 阶段形状的高度
  7. const CHART_HEIGHT = AREA_HEIGHT * 9; // 图表的整体高度,可以调整
  8. // const AREA_LINE_HEIGHT = 20; // 阶段形状线的高度
复制代码
将模拟数据转换成点模型
  1.   const points = useMemo(
  2.     () =>
  3.       data.map((item, index) => ({
  4.         x: (index * (SCREEN_WIDTH - MARGIN * 2)) / (data.length - 1) + MARGIN,
  5.         y: CHART_HEIGHT - (item.stage * 2 + 1) * AREA_HEIGHT - AREA_HEIGHT,
  6.         stage: item.stage,
  7.         data: item,
  8.       })),
  9.     [data],
  10.   );
复制代码
这时候,已经可以利用svg绘制出这些点了。svg中可以用Circle来绘制圆圈。通过遍历points即可将这些点用圆圈绘制到图表中。
  1. <Svg height={CHART_HEIGHT + 80} width={SCREEN_WIDTH}>
  2.           points.map((point, index) => (
  3.             <G key={`point-${index}`}>
  4.               <Circle
  5.                 cx={point.x}
  6.                 cy={point.y}
  7.                 r={POINT_RADIUS}
  8.                 fill={STAGE_CONFIG[point.stage].color}
  9.                 stroke="white"
  10.                 strokeWidth={1}
  11.               />
  12.             </G>
  13. </Svg>
复制代码
看看此时的效果!

有点简陋,先加上些辅助信息。比如加点表格线,比如加点说明。
svg中,可以通过Line来添加线段。
  1.   // 添加绘制参考线的函数
  2.   const renderReferenceLines = () => {
  3.     const lines: JSX.Element[] = [];
  4.     // 计算需要绘制的虚线数量
  5.     const lineCount = Math.floor(CHART_HEIGHT / AREA_HEIGHT);
  6.     for (let i = 0; i <= lineCount; i++) {
  7.       const y = CHART_HEIGHT - i * AREA_HEIGHT;
  8.       lines.push(
  9.         <Line
  10.           key={`reference-line-${i}`}
  11.           x1={MARGIN}
  12.           y1={y}
  13.           x2={SCREEN_WIDTH - MARGIN}
  14.           y2={y}
  15.           stroke="red"
  16.           strokeWidth="1"
  17.           strokeDasharray="4 4"
  18.         />,
  19.       );
  20.     }
  21.     // 添加两条y轴边线
  22.     lines.push(
  23.       <Line
  24.         key={`reference-yline-0`}
  25.         x1={MARGIN}
  26.         y1={0}
  27.         x2={MARGIN}
  28.         y2={CHART_HEIGHT}
  29.         stroke="red"
  30.         strokeWidth="1"
  31.         strokeDasharray="4 4"
  32.       />,
  33.     );
  34.     lines.push(
  35.       <Line
  36.         key={`reference-yline-1`}
  37.         x1={SCREEN_WIDTH - MARGIN}
  38.         y1={0}
  39.         x2={SCREEN_WIDTH - MARGIN}
  40.         y2={CHART_HEIGHT}
  41.         stroke="red"
  42.         strokeWidth="1"
  43.         strokeDasharray="4 4"
  44.       />,
  45.     );
  46.     return lines;
  47.   };
复制代码
在添加了辅助线与说明文案后,这时候的样式开始有点表格的味道了。

但它还只是点,下一步我们通过这些点来绘制出一些区域。
To Be Continued...
不经常在线,有问题可在微信公众号或者掘金社区私信留言
更多内容可关注
我的公众号悬空八只脚

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册