本文档面向已经了解 面板小程序开发
的开发者,您需要充分的了解什么是面板小程序
,并需要了解 Render Script 的使用。
在小程序开发时,通常会有一些业务是需要提供图表展示数据的功能,以便为用户提供分析的能力。但由于用户一般使用竖屏浏览,此时图表的展示就不够直观,对用户的使用带来不好的体验。因此通常可以再提供图表横屏展示的功能,这样就可以更好的运用屏幕的显示范围更好的展示图表数据,本文将主要介绍如何实现图表且可支持横屏展示图表的开发方案。
在智能小程序需要使用 canvas 来进行图表的绘制,这里我们使用 AntV F2 来进行图表绘制。在小程序中对于 canvas 的处理,我们使用 Render Script 进行,所以这里需要使用原生组件进行处理图表组件。
index.tyml
<canvas class="chart-canvas" style="{{style}};opacity:{{loading?0:1}};" canvas-id="chartCanvas" />
index.js
import Render from "./index.rjs";
Component({
/**
* 组件的属性列表
*/
properties: {
data: { type: Array },
style: { type: null },
},
/**
* 组件的初始数据
*/
data: {
loading: true,
},
observers: {
data: function (data) {
if (this.render) {
const systemInfo = ty.getSystemInfoSync();
this.render.drawChart(systemInfo, data);
}
},
},
pageLifetimes: {
resize() {
this.setData({ loading: true });
// 获取画布的大小
ty.createSelectorQuery()
.select("#chartCanvas")
.boundingClientRect()
.exec((res) => {
this.initChart(res[0]);
});
setTimeout(() => {}, 50);
},
},
/**
* 组件的方法列表
*/
methods: {
async initChart(rect) {
const systemInfo = ty.getSystemInfoSync();
await this.render.initChart(systemInfo, rect, "auto");
this.render.drawChart(systemInfo, this.data.data);
this.setData({ loading: false });
},
},
lifetimes: {
ready() {
this.render = new Render(this);
if (this.data.data) {
const systemInfo = ty.getSystemInfoSync();
this.render.drawChart(systemInfo, this.data.data);
this.setData({ loading: false });
}
},
},
});
index.less
// 默认 canvas的尺寸大小跟随父节点尺寸大小
.chart-canvas {
width: 100%;
height: 100%;
}
index.rjs
import F2 from "@antv/f2";
import { windowAdapter, documentAdapter } from "@tuya-miniapp/rjs-adapter";
// 这里处理 F2 的 polyfill
const noop = () => {};
const EMPTY_OBJ = {};
function createDocument() {
const doc = {
createElement: () => ({
style: {},
}),
getElementById: () => {},
};
return Object.assign(EMPTY_OBJ, doc);
}
const document = createDocument();
const navigator = {
userAgent: "",
};
const window = {
document,
navigator,
addEventListener: noop,
removeEventListener: noop,
};
windowAdapter(() => window);
documentAdapter(() => document);
let chart = null;
export default Render({
// 初始化图表
async initChart(systemInfo, rect, padding) {
if (chart) {
chart.destroy();
}
const { pixelRatio } = systemInfo;
const canvas = await getCanvasById("chartCanvas");
chart = new F2.Chart({
el: canvas,
pixelRatio,
width: rect ? rect.width : canvas.width,
height: rect ? rect.height : canvas.height,
padding,
});
},
// 绘制图表
async drawChart(systemInfo, data) {
if (!chart) {
await this.initChart(systemInfo);
}
chart.clear();
chart.source(data);
chart.axis("time", {
line: null,
label(txt, index, total) {
const color = index % 2 === 1 ? "transparent" : "rgba(0, 0, 0, 0.3)";
return {
text: txt,
fontSize: 10,
lineHeight: 14,
fill: color,
};
},
});
chart.axis("value", {
label(txt, index, total) {
return {
text: txt,
fontSize: 10,
lineHeight: 14,
fill: "rgba(0, 0, 0, 0.3)",
};
},
});
chart.legend(false);
chart
.area({
connectNulls: false,
})
.position("time*value")
.shape("smooth")
.color("code", (code) => `l(90) 0:#FD546E 1:#FFFFFF`)
.style({
fillOpacity: 1,
});
chart
.line({
connectNulls: false,
})
.position("time*value")
.shape("smooth")
.color("code", (code) => "#FD546E")
.style({
lineWidth: 2,
});
chart.render();
},
});
页面使用 Ray 实现,在页面实现全屏效果。 这里将使用 setPageOrientation
api 进行横竖屏切换,当切到横屏时,则为图表的全屏展示;当切到竖屏时,则图表普通展示,默认为普通展示。
注意: 在全屏时需要处理手机端安全区域的问题
import React, { FC, useCallback, useState } from "react";
import { View, setPageOrientation } from "@ray-js/ray";
import { Svg } from "@ray-js/svg";
import Chart from "./chart-base";
import styles from "./index.module.less";
const fullScreenIcon =
"M285.866667 810.666667H384v42.666666H213.333333v-170.666666h42.666667v98.133333l128-128 29.866667 29.866667-128 128z m494.933333 0l-128-128 29.866667-29.866667 128 128V682.666667h42.666666v170.666666h-170.666666v-42.666666h98.133333zM285.866667 256l128 128-29.866667 29.866667-128-128V384H213.333333V213.333333h170.666667v42.666667H285.866667z m494.933333 0H682.666667V213.333333h170.666666v170.666667h-42.666666V285.866667l-128 128-29.866667-29.866667 128-128z";
const cancelFullScreenIcon =
"M354.133333 682.666667H256v-42.666667h170.666667v170.666667H384v-98.133334L243.2 853.333333l-29.866667-29.866666L354.133333 682.666667z m358.4 0l140.8 140.8-29.866666 29.866666-140.8-140.8V810.666667h-42.666667v-170.666667h170.666667v42.666667h-98.133334zM354.133333 384L213.333333 243.2l29.866667-29.866667L384 354.133333V256h42.666667v170.666667H256V384h98.133333z m358.4 0H810.666667v42.666667h-170.666667V256h42.666667v98.133333L823.466667 213.333333l29.866666 29.866667L712.533333 384z";
const Page: FC = () => {
// 当前屏的方向
const [orientation, setOrientation] = useState(
"portrait" as "landscape" | "portrait"
);
const [dataList] = useState(() => {
// 模拟数据
const res = [];
for (let i = 0; i < 31; i++) {
res.push({
time: `202308${i.toString(10).padStart(2, "0")}`,
value: 5 * Math.floor(100 * Math.random()),
});
}
return res;
});
// 切换屏方向
const triggerScreen = useCallback(() => {
const newValue = orientation === "portrait" ? "landscape" : "portrait";
setPageOrientation({
pageOrientation: newValue,
success: () => {
setOrientation(newValue);
},
fail: () => {
console.warn("切换失败");
},
});
}, [orientation]);
return (
<View className={styles.page}>
<View className={styles.btn} onClick={triggerScreen}>
<Svg viewBox="0 0 1024 1024" width="36px" height="36px">
<path
d={
orientation === "portrait" ? fullScreenIcon : cancelFullScreenIcon
}
fill="#fff"
/>
</Svg>
</View>
<View
className={`${
orientation === "landscape" ? styles.chartLandscape : styles.chart
}`}
>
<Chart data={dataList} />
</View>
</View>
);
};
export default Page;
index.module.less
// 页面样式
.page {
height: 100vh;
display: flex;
flex-direction: column;
align-items: stretch;
}
// 切换全屏按钮
.btn {
position: absolute;
top: 32rpx;
right: 32rpx;
width: 50px;
height: 50px;
border-radius: 50%;
background: #4422ee;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
color: #fff;
}
// 普通图表样式
.chart {
height: 500rpx;
}
// 全屏图表样式
.chartLandscape {
flex: 1;
padding-bottom: env(safe-area-inset-bottom, 0px);
padding-bottom: constant(safe-area-inset-bottom, 0px);
padding-left: env(safe-area-inset-left, 0px);
padding-left: constant(safe-area-inset-left, 0px);
padding-top: env(safe-area-inset-top, 0px);
padding-top: constant(safe-area-inset-top, 0px);
padding-right: env(safe-area-inset-right, 0px);
padding-right: constant(safe-area-inset-right, 0px);
}