|
|
@@ -0,0 +1,193 @@
|
|
|
+import React, { useEffect, useRef, useMemo } from 'react';
|
|
|
+import Taro from '@tarojs/taro';
|
|
|
+import { Canvas } from '@tarojs/components';
|
|
|
+import uChartsClass from '../lib/u-charts-original.js';
|
|
|
+import type { ChartsConfig, TouchEvent } from '../lib/u-charts-original';
|
|
|
+import type { ExtendedCanvasContext } from '../types';
|
|
|
+
|
|
|
+/**
|
|
|
+ * BaseChartOriginal2D 组件的 Props 接口
|
|
|
+ * 使用原始 u-charts.js + Canvas 2D API
|
|
|
+ */
|
|
|
+export interface BaseChartOriginal2DProps {
|
|
|
+ /** Canvas 元素的 ID,必须唯一 */
|
|
|
+ canvasId: string;
|
|
|
+ /** 图表宽度(像素),默认为屏幕宽度 */
|
|
|
+ width?: number;
|
|
|
+ /** 图表高度(像素),默认根据宽高比计算 */
|
|
|
+ height?: number;
|
|
|
+ /** 设备像素比,默认根据环境自动设置 */
|
|
|
+ pixelRatio?: 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;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * BaseChartOriginal2D 组件
|
|
|
+ *
|
|
|
+ * 使用原始 u-charts.js + Canvas 2D API
|
|
|
+ * 参考 docs/小程序图表库示例/taro-2d柱状图使用示例.md
|
|
|
+ */
|
|
|
+export const BaseChartOriginal2D: React.FC<BaseChartOriginal2DProps> = (props) => {
|
|
|
+ const {
|
|
|
+ canvasId,
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ pixelRatio,
|
|
|
+ type,
|
|
|
+ categories = [],
|
|
|
+ series = [],
|
|
|
+ config = {},
|
|
|
+ onTouchStart,
|
|
|
+ onTouchMove,
|
|
|
+ onTouchEnd,
|
|
|
+ } = props;
|
|
|
+
|
|
|
+ const chartRef = useRef<any>(null);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算响应式尺寸和像素比
|
|
|
+ */
|
|
|
+ const { cWidth, cHeight, actualPixelRatio } = useMemo(() => {
|
|
|
+ const sysInfo = Taro.getSystemInfoSync();
|
|
|
+ const pr = pixelRatio ?? sysInfo.pixelRatio;
|
|
|
+ // width 和 height 是逻辑像素(CSS 像素)
|
|
|
+ const cw = width ?? (750 / 750 * sysInfo.windowWidth);
|
|
|
+ const ch = height ?? (500 / 750 * cw);
|
|
|
+ return { cWidth: cw, cHeight: ch, actualPixelRatio: pr };
|
|
|
+ }, [width, height, pixelRatio]);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Canvas props - Canvas 2D API
|
|
|
+ * - width/height 属性:实际像素尺寸(逻辑像素 * pixelRatio)
|
|
|
+ * - style.width/style.height:CSS 显示尺寸(逻辑像素)
|
|
|
+ */
|
|
|
+ const canvasProps = useMemo(() => ({
|
|
|
+ width: String(cWidth * actualPixelRatio),
|
|
|
+ height: String(cHeight * actualPixelRatio),
|
|
|
+ style: { width: `${cWidth}px`, height: `${cHeight}px` }
|
|
|
+ }), [cWidth, cHeight, actualPixelRatio]);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化图表实例
|
|
|
+ * 使用 Canvas 2D API + 原始 u-charts.js
|
|
|
+ */
|
|
|
+ useEffect(() => {
|
|
|
+ // 使用 setTimeout 确保 Canvas DOM 元素已渲染完成
|
|
|
+ const timer = 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');
|
|
|
+
|
|
|
+ // 设置 canvas 的实际像素尺寸
|
|
|
+ canvas.width = res[0].width * actualPixelRatio;
|
|
|
+ canvas.height = res[0].height * actualPixelRatio;
|
|
|
+
|
|
|
+ // 将 Taro CanvasContext 转换为 uCharts 需要的 CanvasContext
|
|
|
+ const extendedCtx = ctx as ExtendedCanvasContext;
|
|
|
+
|
|
|
+ // Canvas 2D: 传入 uCharts 的 width/height 需要乘以 pixelRatio
|
|
|
+ const chartConfig: ChartsConfig = {
|
|
|
+ type,
|
|
|
+ context: extendedCtx,
|
|
|
+ categories,
|
|
|
+ series,
|
|
|
+ width: cWidth * actualPixelRatio,
|
|
|
+ height: cHeight * actualPixelRatio,
|
|
|
+ pixelRatio: actualPixelRatio,
|
|
|
+ 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 }]
|
|
|
+ },
|
|
|
+ extra: {
|
|
|
+ column: {
|
|
|
+ type: 'group',
|
|
|
+ width: 30,
|
|
|
+ activeBgColor: '#000000',
|
|
|
+ activeBgOpacity: 0.08
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ...config,
|
|
|
+ };
|
|
|
+
|
|
|
+ chartRef.current = new uChartsClass(chartConfig);
|
|
|
+ console.log('[BaseChartOriginal2D] 图表初始化完成:', canvasId, {
|
|
|
+ cWidth, cHeight, actualPixelRatio,
|
|
|
+ canvasWidth: canvas.width,
|
|
|
+ canvasHeight: canvas.height
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ console.error('[BaseChartOriginal2D] 未获取到 canvas node:', canvasId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ clearTimeout(timer);
|
|
|
+ chartRef.current = null;
|
|
|
+ };
|
|
|
+ }, [canvasId, type, categories, series, cWidth, cHeight, actualPixelRatio, config]);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 触摸事件处理
|
|
|
+ */
|
|
|
+ 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);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Canvas
|
|
|
+ canvasId={canvasId}
|
|
|
+ id={canvasId}
|
|
|
+ {...canvasProps}
|
|
|
+ onTouchStart={handleTouchStart}
|
|
|
+ onTouchMove={handleTouchMove}
|
|
|
+ onTouchEnd={handleTouchEnd}
|
|
|
+ type="2d"
|
|
|
+ />
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default BaseChartOriginal2D;
|