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鸿蒙也支持,后续即便找到三方,也不一定能鸿蒙化,反而不美。之前从没做过图表,这玩意一般都让前端去开发,这次也是临时学了下。
首先科普下睡眠质量,这个倒是有医学上的定义的---按照阶段可以分为深睡眠,浅睡眠,快速眼动,清醒这四个阶段。定义一个最简单的模型- enum SleepStageEnum {
- Deep = 0, // 0 深睡眠
- Light, // 1 浅睡眠
- REM, // 2 快速眼动
- AWAKE, // 3 清醒
- }
复制代码 我们不关心戒指怎么去得到这些数据,只要知道数据的采集频率,比如每隔五分钟采集一次数据,反馈当前的睡眠状态。所以数据上可以用采样时间+当前睡眠状态来模拟。- // 示例数据
- const sleepData: SleepStageModel[] = [
- {time: '0:00', stage: 3},
- {time: '5:00', stage: 3},
- {time: '10:00', stage: 1},
- {time: '15:00', stage: 1},
- {time: '20:00', stage: 3},
- {time: '25:00', stage: 0},
- {time: '30:00', stage: 0},
- {time: '35:00', stage: 0},
- {time: '40:00', stage: 0},
- {time: '45:00', stage: 1},
- {time: '50:00', stage: 1},
- {time: '55:00', stage: 3},
- {time: '60:00', stage: 3},
- {time: '65:00', stage: 2},
- {time: '70:00', stage: 2},
- {time: '75:00', stage: 1},
- {time: '80:00', stage: 3},
- {time: '85:00', stage: 2},
- {time: '0:00', stage: 3},
- {time: '5:00', stage: 3},
- {time: '10:00', stage: 1},
- {time: '15:00', stage: 1},
- {time: '20:00', stage: 3},
- {time: '25:00', stage: 0},
- // ...更多数据
- ];
复制代码 现在回过头来看看这个图表,看着有点科技,其实扒光效果放到excel里就类似这挫样。其中画红圈的地方是采样数据点。

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

有点简陋,先加上些辅助信息。比如加点表格线,比如加点说明。
svg中,可以通过Line来添加线段。- // 添加绘制参考线的函数
- const renderReferenceLines = () => {
- const lines: JSX.Element[] = [];
- // 计算需要绘制的虚线数量
- const lineCount = Math.floor(CHART_HEIGHT / AREA_HEIGHT);
- for (let i = 0; i <= lineCount; i++) {
- const y = CHART_HEIGHT - i * AREA_HEIGHT;
- lines.push(
- <Line
- key={`reference-line-${i}`}
- x1={MARGIN}
- y1={y}
- x2={SCREEN_WIDTH - MARGIN}
- y2={y}
- stroke="red"
- strokeWidth="1"
- strokeDasharray="4 4"
- />,
- );
- }
- // 添加两条y轴边线
- lines.push(
- <Line
- key={`reference-yline-0`}
- x1={MARGIN}
- y1={0}
- x2={MARGIN}
- y2={CHART_HEIGHT}
- stroke="red"
- strokeWidth="1"
- strokeDasharray="4 4"
- />,
- );
- lines.push(
- <Line
- key={`reference-yline-1`}
- x1={SCREEN_WIDTH - MARGIN}
- y1={0}
- x2={SCREEN_WIDTH - MARGIN}
- y2={CHART_HEIGHT}
- stroke="red"
- strokeWidth="1"
- strokeDasharray="4 4"
- />,
- );
- return lines;
- };
复制代码 在添加了辅助线与说明文案后,这时候的样式开始有点表格的味道了。
但它还只是点,下一步我们通过这些点来绘制出一些区域。
To Be Continued...
不经常在线,有问题可在微信公众号或者掘金社区私信留言
更多内容可关注
我的公众号悬空八只脚
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |