| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
- import Taro from '@tarojs/taro';
- import { Canvas } from '@tarojs/components';
- import uChartsClass from '../lib/u-charts-original';
- import type { ChartsConfig, TouchEvent } from '../types/u-charts-original';
- import type { ExtendedCanvasContext } from '../types';
- /**
- * BaseChart 组件的 Props 接口
- * 使用原始 u-charts.js + Canvas 2D API
- */
- export interface BaseChartProps {
- /** Canvas 元素的 ID,必须唯一 */
- canvasId: string;
- /** 图表宽度(像素),默认为 750 */
- width?: number;
- /** 图表高度(像素),默认为 500 */
- height?: number;
- /** 图表类型 */
- type: ChartsConfig['type'];
- /** X 轴分类数据 */
- categories?: string[];
- /** 系列数据 */
- series?: ChartsConfig['series'];
- /** 额外的图表配置 */
- config?: Partial<ChartsConfig>;
- /** 触摸开始事件 */
- onTouchStart?: (e: TouchEvent) => void;
- /** 触摸移动事件 */
- onTouchMove?: (e: TouchEvent) => void;
- /** 触摸结束事件 */
- onTouchEnd?: (e: TouchEvent) => void;
- }
- /**
- * BaseChartInner 内部组件
- *
- * 实际的图表渲染组件,只在数据准备好后才会挂载
- * 使用空依赖数组确保只初始化一次
- */
- const BaseChartInner: React.FC<BaseChartProps> = (props) => {
- const {
- canvasId,
- width = 750,
- height = 500,
- type,
- categories = [],
- series = [],
- config = {},
- onTouchStart,
- onTouchMove,
- onTouchEnd,
- } = props;
- const [cWidth, setCWidth] = useState(750);
- const [cHeight, setCHeight] = useState(500);
- const chartRef = useRef<any>(null);
- /**
- * 初始化图表实例
- * 使用 Canvas 2D API + 原始 u-charts.js
- * 参考 ColumnChartFCExample 的实现方式
- *
- * 注意:使用空依赖数组,只在组件首次挂载时执行一次
- * 数据变化通过 Wrapper 组件控制重新挂载来实现
- */
- useLayoutEffect(() => {
- console.debug('[BaseChart] useLayoutEffect 开始', { canvasId, width, height });
- // 计算响应式尺寸
- const sysInfo = Taro.getSystemInfoSync();
- // 这里的第一个 750 对应 css .charts 的 width
- const cw = width / 750 * sysInfo.windowWidth;
- // 这里的 500 对应 css .charts 的 height
- const ch = height / 750 * sysInfo.windowWidth;
- setCWidth(cw);
- setCHeight(ch);
- // 确保数据已准备好
- // 对于饼图(pie/ring),只需要 series 有数据
- // 对于其他图表,需要 categories 和 series 都有数据
- const isPieChart = type === 'pie' || type === 'ring';
- const isDataReady = isPieChart ? series.length > 0 : categories.length > 0 && series.length > 0;
- if (!isDataReady) {
- console.log('[BaseChart] 数据未准备好,等待数据...', {
- canvasId,
- type,
- isPieChart,
- categoriesLength: categories.length,
- seriesLength: series.length
- });
- return;
- }
- // 延迟初始化图表,等待 Canvas 尺寸渲染完成(参考 FC 示例)
- setTimeout(() => {
- // 使用 Canvas 2D API 的方式获取 context
- const query = Taro.createSelectorQuery();
- query.select('#' + canvasId).fields({ node: true, size: true }).exec((res) => {
- if (res[0]) {
- const canvas = res[0].node;
- const ctx = canvas.getContext('2d');
- console.debug('[BaseChartOriginal2D] canvas.width', canvas.width);
- console.debug('[BaseChartOriginal2D] canvas.height', canvas.height);
- // 将 Taro CanvasContext 转换为 uCharts 需要的 CanvasContext
- const extendedCtx = ctx as ExtendedCanvasContext;
- // Canvas 2D: 使用 canvas 的实际 width/height
- // 基础配置
- const chartConfig: ChartsConfig = {
- type,
- context: extendedCtx,
- categories,
- series,
- width: canvas.width,
- height: canvas.height,
- animation: true,
- background: '#FFFFFF',
- color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
- padding: [15, 15, 0, 5],
- enableScroll: false,
- legend: {},
- xAxis: {
- disableGrid: true
- },
- yAxis: {
- data: [{ min: 0 }]
- },
- ...config,
- };
- chartRef.current = new uChartsClass(chartConfig);
- console.log('[BaseChart] 图表初始化完成:', canvasId, {
- cWidth, cHeight,
- canvasWidth: canvas.width,
- canvasHeight: canvas.height,
- categoriesLength: categories.length,
- seriesLength: series.length,
- categories,
- series,
- });
- } else {
- console.error('[BaseChart] 未获取到 canvas node:', canvasId);
- }
- });
- }, 500); // 延迟 500ms,等待 Canvas 尺寸渲染完成
- }, []); // 空依赖数组:只在首次挂载时执行一次
- /**
- * 触摸事件处理
- */
- const handleTouchStart = (e: any) => {
- if (chartRef.current) {
- chartRef.current.touchLegend(e);
- chartRef.current.showToolTip(e);
- }
- onTouchStart?.(e as TouchEvent);
- };
- const handleTouchMove = (e: any) => {
- if (chartRef.current) {
- chartRef.current.scroll(e);
- }
- onTouchMove?.(e as TouchEvent);
- };
- const handleTouchEnd = (e: any) => {
- if (chartRef.current) {
- chartRef.current.touchLegend(e);
- chartRef.current.showToolTip(e);
- }
- onTouchEnd?.(e as TouchEvent);
- };
- const canvasProps = { style: { width: cWidth, height: cHeight } };
- return (
- <Canvas
- canvas-id={canvasId}
- id={canvasId}
- {...canvasProps}
- onTouchStart={handleTouchStart}
- onTouchMove={handleTouchMove}
- onTouchEnd={handleTouchEnd}
- type="2d"
- />
- );
- };
- /**
- * BaseChart 组件
- *
- * 外层 Wrapper 组件,负责:
- * - 检查数据是否准备好
- * - 缓存 config 引用
- * - 只有数据准备好后才挂载 Inner 组件
- *
- * 使用原始 u-charts.js + Canvas 2D API
- * 参考 docs/小程序图表库示例/taro-2d柱状图使用示例.md
- */
- export const BaseChart: React.FC<BaseChartProps> = (props) => {
- const {
- canvasId,
- width,
- height,
- type,
- categories = [],
- series = [],
- config = {},
- onTouchStart,
- onTouchMove,
- onTouchEnd,
- } = props;
- // 缓存配置,避免每次渲染创建新对象
- const stableConfig = useMemo(() => config, [JSON.stringify(config)]);
- // 只有数据准备好才渲染 Inner 组件
- // 对于饼图(pie/ring),只需要 series 有数据
- // 对于其他图表,需要 categories 和 series 都有数据
- const isPieChart = type === 'pie' || type === 'ring';
- const isReady = isPieChart ? series.length > 0 : categories.length > 0 && series.length > 0;
- if (!isReady) {
- return null;
- }
- return (
- <BaseChartInner
- canvasId={canvasId}
- width={width}
- height={height}
- type={type}
- categories={categories}
- series={series}
- config={stableConfig}
- onTouchStart={onTouchStart}
- onTouchMove={onTouchMove}
- onTouchEnd={onTouchEnd}
- />
- );
- };
- export default BaseChart;
|