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

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鸿蒙也支持,后续即便找到三方,也不一定能鸿蒙化,反而不美。之前从没做过图表,这玩意一般都让前端去开发,这次也是临时学了下。

首先科普下睡眠质量,这个倒是有医学上的定义的---按照阶段可以分为深睡眠,浅睡眠,快速眼动,清醒这四个阶段。定义一个最简单的模型

typescript 复制代码
enum SleepStageEnum {
  Deep = 0, // 0 深睡眠
  Light, // 1 浅睡眠
  REM, // 2 快速眼动
  AWAKE, // 3 清醒
}

我们不关心戒指怎么去得到这些数据,只要知道数据的采集频率,比如每隔五分钟采集一次数据,反馈当前的睡眠状态。所以数据上可以用采样时间+当前睡眠状态来模拟。

typescript 复制代码
// 示例数据
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做完了才开始码字,代码难免和当前描述相比有点超前,见谅)

typescript 复制代码
// 组件目前默认按照屏幕宽度为基准进行布局
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; // 阶段形状线的高度

将模拟数据转换成点模型

typescript 复制代码
  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即可将这些点用圆圈绘制到图表中。

typescript 复制代码
<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来添加线段。

typescript 复制代码
  // 添加绘制参考线的函数
  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;
  };

构造一个对象来描述阶段颜色等配置信息,它和SleepStageEnum是一一对应关系

typescript 复制代码
// 自定义形状配置字典
const STAGE_CONFIG = {
  0: {
    gradient: ['#8314f3', '#3800FF'], // 渐变色设置
    useColorTransform: true, // 是否使用颜色变换
    transformX1: '0%',
    transformX2: '0%',
    transformY1: '0%',
    transformY2: '100%',
    shadow: '#3800FF', // 阴影颜色
    color: '#3800FF', // 默认颜色
    label: '深睡眠', // 文字标签
  },
  1: {
    gradient: ['#d248b0', '#ba2eec'],
    useColorTransform: true,
    transformX1: '0%',
    transformX2: '0%',
    transformY1: '0%',
    transformY2: '100%',
    color: '#ba2eec',
    shadow: '#ba2eec',
    label: '浅睡眠',
  },
  2: {
    gradient: ['#e05891', '#f86d5a'],
    useColorTransform: true,
    transformX1: '0%',
    transformX2: '0%',
    transformY1: '0%',
    transformY2: '100%',
    shadow: '#f86d5a',
    color: '#f86d5a',
    label: '快速眼动',
  },
  3: {
    gradient: ['#fb8b44', '#fcbb29'],
    useColorTransform: true,
    transformX1: '0%',
    transformX2: '0%',
    transformY1: '0%',
    transformY2: '100%',
    shadow: '#fcbb29',
    color: '#fcbb29',
    label: '清醒',
  },
};

绘制一个辅助说明区域

typescript 复制代码
<View style={styles.legend}>
  {Object.entries(STAGE_CONFIG).map(([stage, config]) => (
    <View key={stage} style={styles.legendItem}>
      <View style={[styles.legendDot, {backgroundColor: config.color}]} />
      <Text style={styles.legendText}>{config.label}</Text>
    </View>
  ))}
</View>

在添加了辅助线与说明文案后,这时候的样式开始有点表格的味道了。

但它还只是点,下一步我们通过这些点来绘制出一些区域。

To Be Continued...


不经常在线,有问题可在微信公众号或者掘金社区私信留言

更多内容可关注

我的公众号悬空八只脚

相关推荐
别说我什么都不会4 小时前
【仓颉三方库】对象存储——OBS Cangjie SDK
harmonyos
星释5 小时前
鸿蒙Flutter仓库停止更新?
flutter·华为·harmonyos
云_杰5 小时前
鸿蒙开发干货——手把手教你玩转多媒体文件操作
harmonyos·交互设计
王老汉6 小时前
鸿蒙生态新利器:华为ArkUI-X混合开发框架深度解析
华为·harmonyos·arkui-x
NapleC7 小时前
HarmonyOS:网络HTTP数据请求
网络·http·harmonyos
新小梦11 小时前
OpenHarmony声明为系统应用和系统签名文件
harmonyos
别说我什么都不会11 小时前
【仓颉三方库】 数据解析——ini4cj
harmonyos
搞瓶可乐1 天前
鸿蒙ArkUI实战之组件;Text组件,Image组件,Button组件,Span组件和TextInput组件的使用场景及使用方法
华为·harmonyos·鸿蒙系统·arkui·组件化开发·基础组件使用
九丘教育1 天前
【仓颉 + 鸿蒙 + AI Agent】CangjieMagic框架(15):NaiveExecutor
人工智能·华为·harmonyos