Browse Source

增加mini-ui-packages

yourname 2 weeks ago
parent
commit
bd18e58aa2
100 changed files with 32431 additions and 0 deletions
  1. 38 0
      mini-ui-packages/mini-charts/jest.config.cjs
  2. 116 0
      mini-ui-packages/mini-charts/package.json
  3. 83 0
      mini-ui-packages/mini-charts/src/components/BarChart.tsx
  4. 249 0
      mini-ui-packages/mini-charts/src/components/BaseChart.tsx
  5. 82 0
      mini-ui-packages/mini-charts/src/components/ColumnChart.tsx
  6. 151 0
      mini-ui-packages/mini-charts/src/components/ColumnChartFCExample.tsx
  7. 88 0
      mini-ui-packages/mini-charts/src/components/LineChart.tsx
  8. 77 0
      mini-ui-packages/mini-charts/src/components/PieChart.tsx
  9. 134 0
      mini-ui-packages/mini-charts/src/components/PieChartFCExample.tsx
  10. 80 0
      mini-ui-packages/mini-charts/src/components/RingChart.tsx
  11. 151 0
      mini-ui-packages/mini-charts/src/components/RingChartFCExample.tsx
  12. 1 0
      mini-ui-packages/mini-charts/src/components/index.ts
  13. 271 0
      mini-ui-packages/mini-charts/src/index.ts
  14. 606 0
      mini-ui-packages/mini-charts/src/lib/charts-data/basic-charts.ts
  15. 46 0
      mini-ui-packages/mini-charts/src/lib/charts-data/funnel-charts.ts
  16. 219 0
      mini-ui-packages/mini-charts/src/lib/charts-data/gauge-charts.ts
  17. 76 0
      mini-ui-packages/mini-charts/src/lib/charts-data/index.ts
  18. 106 0
      mini-ui-packages/mini-charts/src/lib/charts-data/pie-charts.ts
  19. 110 0
      mini-ui-packages/mini-charts/src/lib/charts-data/radar-charts.ts
  20. 29 0
      mini-ui-packages/mini-charts/src/lib/charts/index.ts
  21. 72 0
      mini-ui-packages/mini-charts/src/lib/charts/u-charts-event.ts
  22. 1117 0
      mini-ui-packages/mini-charts/src/lib/charts/u-charts.ts
  23. 165 0
      mini-ui-packages/mini-charts/src/lib/config.ts
  24. 556 0
      mini-ui-packages/mini-charts/src/lib/data-processing/axis-calculator.ts
  25. 132 0
      mini-ui-packages/mini-charts/src/lib/data-processing/categories-calculator.ts
  26. 65 0
      mini-ui-packages/mini-charts/src/lib/data-processing/index.ts
  27. 411 0
      mini-ui-packages/mini-charts/src/lib/data-processing/series-calculator.ts
  28. 175 0
      mini-ui-packages/mini-charts/src/lib/data-processing/tooltip-calculator.ts
  29. 108 0
      mini-ui-packages/mini-charts/src/lib/draw-controllers/animation.ts
  30. 9 0
      mini-ui-packages/mini-charts/src/lib/draw-controllers/draw-canvas.ts
  31. 751 0
      mini-ui-packages/mini-charts/src/lib/draw-controllers/draw-charts.ts
  32. 20 0
      mini-ui-packages/mini-charts/src/lib/draw-controllers/index.ts
  33. 63 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/area-checkers.ts
  34. 216 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/coordinate-helpers.ts
  35. 259 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/data-fixers.ts
  36. 252 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/data-helpers.ts
  37. 405 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/index-finders.ts
  38. 16 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/index.ts
  39. 231 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/legend-helpers.ts
  40. 462 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/misc-helpers.ts
  41. 43 0
      mini-ui-packages/mini-charts/src/lib/helper-functions/types.ts
  42. 781 0
      mini-ui-packages/mini-charts/src/lib/renderers/axis-renderer.ts
  43. 178 0
      mini-ui-packages/mini-charts/src/lib/renderers/candle-renderer.ts
  44. 1133 0
      mini-ui-packages/mini-charts/src/lib/renderers/column-renderer.ts
  45. 957 0
      mini-ui-packages/mini-charts/src/lib/renderers/common-renderer.ts
  46. 71 0
      mini-ui-packages/mini-charts/src/lib/renderers/index.ts
  47. 481 0
      mini-ui-packages/mini-charts/src/lib/renderers/line-renderer.ts
  48. 212 0
      mini-ui-packages/mini-charts/src/lib/renderers/map-renderer.ts
  49. 834 0
      mini-ui-packages/mini-charts/src/lib/renderers/pie-renderer.ts
  50. 358 0
      mini-ui-packages/mini-charts/src/lib/renderers/radar-renderer.ts
  51. 688 0
      mini-ui-packages/mini-charts/src/lib/renderers/special-renderer.ts
  52. 7706 0
      mini-ui-packages/mini-charts/src/lib/u-charts-original.js
  53. 7 0
      mini-ui-packages/mini-charts/src/lib/u-charts-original.ts
  54. 7680 0
      mini-ui-packages/mini-charts/src/lib/u-charts.ts.backup
  55. 88 0
      mini-ui-packages/mini-charts/src/lib/utils/collision.ts
  56. 20 0
      mini-ui-packages/mini-charts/src/lib/utils/color.ts
  57. 155 0
      mini-ui-packages/mini-charts/src/lib/utils/coordinate.ts
  58. 33 0
      mini-ui-packages/mini-charts/src/lib/utils/index.ts
  59. 92 0
      mini-ui-packages/mini-charts/src/lib/utils/math.ts
  60. 233 0
      mini-ui-packages/mini-charts/src/lib/utils/misc.ts
  61. 59 0
      mini-ui-packages/mini-charts/src/lib/utils/text.ts
  62. 18 0
      mini-ui-packages/mini-charts/src/types.ts
  63. 71 0
      mini-ui-packages/mini-charts/src/types/u-charts-original.d.ts
  64. 1 0
      mini-ui-packages/mini-charts/tests/__mocks__/fileMock.js
  65. 1 0
      mini-ui-packages/mini-charts/tests/__mocks__/styleMock.js
  66. 106 0
      mini-ui-packages/mini-charts/tests/components/BaseChart.test.tsx
  67. 163 0
      mini-ui-packages/mini-charts/tests/components/ColumnChart.test.tsx
  68. 2 0
      mini-ui-packages/mini-charts/tests/setup.ts
  69. 29 0
      mini-ui-packages/mini-charts/tsconfig.json
  70. 40 0
      mini-ui-packages/mini-shared-ui-components/jest.config.cjs
  71. 139 0
      mini-ui-packages/mini-shared-ui-components/package.json
  72. 93 0
      mini-ui-packages/mini-shared-ui-components/src/components/avatar-upload.tsx
  73. 46 0
      mini-ui-packages/mini-shared-ui-components/src/components/button.tsx
  74. 54 0
      mini-ui-packages/mini-shared-ui-components/src/components/card.tsx
  75. 95 0
      mini-ui-packages/mini-shared-ui-components/src/components/dialog.tsx
  76. 168 0
      mini-ui-packages/mini-shared-ui-components/src/components/form.tsx
  77. 127 0
      mini-ui-packages/mini-shared-ui-components/src/components/image.tsx
  78. 102 0
      mini-ui-packages/mini-shared-ui-components/src/components/input.tsx
  79. 55 0
      mini-ui-packages/mini-shared-ui-components/src/components/label.tsx
  80. 230 0
      mini-ui-packages/mini-shared-ui-components/src/components/navbar.tsx
  81. 37 0
      mini-ui-packages/mini-shared-ui-components/src/components/page-container.tsx
  82. 155 0
      mini-ui-packages/mini-shared-ui-components/src/components/tab-bar.tsx
  83. 58 0
      mini-ui-packages/mini-shared-ui-components/src/components/user-status-bar.tsx
  84. 15 0
      mini-ui-packages/mini-shared-ui-components/src/index.ts
  85. 15 0
      mini-ui-packages/mini-shared-ui-components/src/utils/cn.ts
  86. 16 0
      mini-ui-packages/mini-shared-ui-components/src/utils/platform.ts
  87. 16 0
      mini-ui-packages/mini-shared-ui-components/src/utils/rpc/headers-polyfill.d.ts
  88. 87 0
      mini-ui-packages/mini-shared-ui-components/src/utils/rpc/headers-polyfill.js
  89. 93 0
      mini-ui-packages/mini-shared-ui-components/src/utils/rpc/response-polyfill.ts
  90. 189 0
      mini-ui-packages/mini-shared-ui-components/src/utils/rpc/rpc-client.ts
  91. 10 0
      mini-ui-packages/mini-shared-ui-components/testing/index.ts
  92. 17 0
      mini-ui-packages/mini-shared-ui-components/tests/__config__/jest-preset.d.ts
  93. 39 0
      mini-ui-packages/mini-shared-ui-components/tests/__config__/jest-preset.js
  94. 9 0
      mini-ui-packages/mini-shared-ui-components/tests/__config__/tsconfig.test.json
  95. 10 0
      mini-ui-packages/mini-shared-ui-components/tests/__helpers__/env-setup.ts
  96. 8 0
      mini-ui-packages/mini-shared-ui-components/tests/__helpers__/taro-mocks.ts
  97. 8 0
      mini-ui-packages/mini-shared-ui-components/tests/__helpers__/test-utils.ts
  98. 1 0
      mini-ui-packages/mini-shared-ui-components/tests/__mocks__/fileMock.js
  99. 1 0
      mini-ui-packages/mini-shared-ui-components/tests/__mocks__/styleMock.js
  100. 100 0
      mini-ui-packages/mini-shared-ui-components/tests/__mocks__/taroMock.ts

+ 38 - 0
mini-ui-packages/mini-charts/jest.config.cjs

@@ -0,0 +1,38 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'jsdom',
+  setupFilesAfterEnv: ['@d8d/mini-testing-utils/setup'],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^~/(.*)$': '<rootDir>/tests/$1',
+    '^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
+    '\\.(css|less|scss|sass)$': '@d8d/mini-testing-utils/testing/style-mock.js',
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+      '@d8d/mini-testing-utils/testing/file-mock.js'
+  },
+  testMatch: [
+    '<rootDir>/tests/**/*.spec.{ts,tsx}',
+    '<rootDir>/tests/**/*.test.{ts,tsx}'
+  ],
+  collectCoverageFrom: [
+    'src/**/*.{ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/**/index.{ts,tsx}',
+    '!src/**/*.stories.{ts,tsx}'
+  ],
+  coverageDirectory: 'coverage',
+  coverageReporters: ['text', 'lcov', 'html'],
+  testPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/coverage/'
+  ],
+  transform: {
+    '^.+\\.(ts|tsx)$': 'ts-jest',
+    '^.+\\.(js|jsx)$': 'babel-jest'
+  },
+  transformIgnorePatterns: [
+    '/node_modules/(?!(swiper|@tarojs)/)'
+  ],
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
+}

+ 116 - 0
mini-ui-packages/mini-charts/package.json

@@ -0,0 +1,116 @@
+{
+  "name": "@d8d/mini-charts",
+  "version": "1.0.0",
+  "type": "module",
+  "description": "小程序图表库包 - 提供 u-charts 图表库核心功能",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./dist/src/index.d.ts",
+      "import": "./dist/src/index.js",
+      "require": "./dist/src/index.js"
+    },
+    "./components/BaseChart": {
+      "types": "./dist/src/components/BaseChart.d.ts",
+      "import": "./dist/src/components/BaseChart.js",
+      "require": "./dist/src/components/BaseChart.js"
+    },
+    "./components/BarChart": {
+      "types": "./dist/src/components/BarChart.d.ts",
+      "import": "./dist/src/components/BarChart.js",
+      "require": "./dist/src/components/BarChart.js"
+    },
+    "./components/CandleChart": {
+      "types": "./dist/src/components/CandleChart.d.ts",
+      "import": "./dist/src/components/CandleChart.js",
+      "require": "./dist/src/components/CandleChart.js"
+    },
+    "./components/ColumnChart": {
+      "types": "./dist/src/components/ColumnChart.d.ts",
+      "import": "./dist/src/components/ColumnChart.js",
+      "require": "./dist/src/components/ColumnChart.js"
+    },
+    "./components/LineChart": {
+      "types": "./dist/src/components/LineChart.d.ts",
+      "import": "./dist/src/components/LineChart.js",
+      "require": "./dist/src/components/LineChart.js"
+    },
+    "./components/PieChart": {
+      "types": "./dist/src/components/PieChart.d.ts",
+      "import": "./dist/src/components/PieChart.js",
+      "require": "./dist/src/components/PieChart.js"
+    },
+    "./components/RadarChart": {
+      "types": "./dist/src/components/RadarChart.d.ts",
+      "import": "./dist/src/components/RadarChart.js",
+      "require": "./dist/src/components/RadarChart.js"
+    },
+    "./components/ColumnChartFCExample": {
+      "types": "./dist/src/components/ColumnChartFCExample.d.ts",
+      "import": "./dist/src/components/ColumnChartFCExample.js",
+      "require": "./dist/src/components/ColumnChartFCExample.js"
+    },
+    "./components/PieChartFCExample": {
+      "types": "./dist/src/components/PieChartFCExample.d.ts",
+      "import": "./dist/src/components/PieChartFCExample.js",
+      "require": "./dist/src/components/PieChartFCExample.js"
+    },
+    "./components/RingChart": {
+      "types": "./dist/src/components/RingChart.d.ts",
+      "import": "./dist/src/components/RingChart.js",
+      "require": "./dist/src/components/RingChart.js"
+    },
+    "./components/RingChartFCExample": {
+      "types": "./dist/src/components/RingChartFCExample.d.ts",
+      "import": "./dist/src/components/RingChartFCExample.js",
+      "require": "./dist/src/components/RingChartFCExample.js"
+    }
+  },
+  "scripts": {
+    "clean": "rimraf dist",
+    "build": "tsc && pnpm run copy-assets",
+    "prebuild": "pnpm run clean",
+    "copy-assets": "mkdir -p dist/src/lib && cp src/lib/u-charts-original.js dist/src/lib/",
+    "dev": "pnpm build && tsc --watch",
+    "typecheck": "tsc --noEmit",
+    "test": "jest",
+    "test:watch": "jest --watch",
+    "test:coverage": "jest --coverage"
+  },
+  "dependencies": {
+    "@tarojs/components": "4.1.4",
+    "@tarojs/plugin-platform-weapp": "4.1.4",
+    "@tarojs/react": "4.1.4",
+    "@tarojs/taro": "4.1.4",
+    "react": "^18.0.0",
+    "react-dom": "^18.0.0"
+  },
+  "devDependencies": {
+    "@d8d/mini-testing-utils": "workspace:*",
+    "@testing-library/jest-dom": "^6.8.0",
+    "@testing-library/react": "^16.3.0",
+    "@types/jest": "^29.5.14",
+    "@types/node": "^18",
+    "@types/react": "^18.0.0",
+    "@types/react-dom": "^18.0.0",
+    "jest": "^30.2.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "rimraf": "^6.1.2",
+    "ts-jest": "^29.4.5",
+    "typescript": "^5.4.5"
+  },
+  "files": [
+    "src"
+  ],
+  "keywords": [
+    "mini",
+    "charts",
+    "u-charts",
+    "taro",
+    "react",
+    "canvas"
+  ],
+  "author": "D8D Team",
+  "license": "MIT"
+}

+ 83 - 0
mini-ui-packages/mini-charts/src/components/BarChart.tsx

@@ -0,0 +1,83 @@
+import React from 'react';
+import { BaseChart } from './BaseChart';
+import type { ChartsConfig } from '../lib/u-charts-original';
+
+/**
+ * BarChart 组件的 Props 接口
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export interface BarChartProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId: string;
+  /** 图表宽度(像素) */
+  width?: number;
+  /** 图表高度(像素) */
+  height?: number;
+  /** Y 轴分类数据(条形图的分类在Y轴) */
+  categories: string[];
+  /** 系列数据 */
+  series: ChartsConfig['series'];
+  /** 额外的图表配置 */
+  config?: Partial<ChartsConfig>;
+}
+
+/**
+ * BarChart 横向柱状图组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ * 用于显示分类数据的横向柱状图
+ */
+export const BarChart: React.FC<BarChartProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    categories,
+    series,
+    config = {},
+  } = props;
+
+  /**
+   * 合并默认配置
+   */
+  const mergedConfig: Partial<ChartsConfig> = {
+    animation: true,
+    background: '#FFFFFF',
+    color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+    padding: [15, 15, 0, 5],
+    enableScroll: false,
+    legend: {},
+    xAxis: {
+      disableGrid: true,
+      ...config.xAxis,
+    },
+    yAxis: {
+      data: [{ min: 0 }],
+      ...config.yAxis,
+    },
+    extra: {
+      bar: {
+        type: 'group',
+        width: 30,
+        activeBgColor: '#000000',
+        activeBgOpacity: 0.08,
+        ...config.extra?.bar,
+      }
+    },
+    ...config,
+  };
+
+  return (
+    <BaseChart
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type="bar"
+      categories={categories}
+      series={series}
+      config={mergedConfig}
+    />
+  );
+};
+
+export default BarChart;

+ 249 - 0
mini-ui-packages/mini-charts/src/components/BaseChart.tsx

@@ -0,0 +1,249 @@
+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.js';
+import type { ChartsConfig, TouchEvent } from '../lib/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;

+ 82 - 0
mini-ui-packages/mini-charts/src/components/ColumnChart.tsx

@@ -0,0 +1,82 @@
+import React from 'react';
+import { BaseChart } from './BaseChart';
+import type { ChartsConfig } from '../lib/u-charts-original';
+
+/**
+ * ColumnChart 组件的 Props 接口
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export interface ColumnChartProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId: string;
+  /** 图表宽度(像素) */
+  width?: number;
+  /** 图表高度(像素) */
+  height?: number;
+  /** X 轴分类数据 */
+  categories: string[];
+  /** 系列数据 */
+  series: ChartsConfig['series'];
+  /** 额外的图表配置 */
+  config?: Partial<ChartsConfig>;
+}
+
+/**
+ * ColumnChart 柱状图组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export const ColumnChart: React.FC<ColumnChartProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    categories,
+    series,
+    config = {},
+  } = props;
+
+  /**
+   * 合并默认配置
+   */
+  const mergedConfig: Partial<ChartsConfig> = {
+    animation: true,
+    background: '#FFFFFF',
+    color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+    padding: [15, 15, 0, 5],
+    enableScroll: false,
+    legend: {},
+    xAxis: {
+      disableGrid: true,
+      ...config.xAxis,
+    },
+    yAxis: {
+      data: [{ min: 0 }],
+      ...config.yAxis,
+    },
+    extra: {
+      column: {
+        type: 'group',
+        width: 30,
+        activeBgColor: '#000000',
+        activeBgOpacity: 0.08,
+        ...config.extra?.column,
+      }
+    },
+    ...config,
+  };
+
+  return (
+    <BaseChart
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type="column"
+      categories={categories}
+      series={series}
+      config={mergedConfig}
+    />
+  );
+};
+
+export default ColumnChart;

+ 151 - 0
mini-ui-packages/mini-charts/src/components/ColumnChartFCExample.tsx

@@ -0,0 +1,151 @@
+import React, { useState, useRef, useLayoutEffect } from 'react';
+import Taro from '@tarojs/taro';
+import { View, 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';
+
+/**
+ * ColumnChartFCExample 组件
+ *
+ * FC 示例组件(基于 docs/小程序图表库示例/taro-2d柱状图FC使用示例.md)
+ * 使用原始 u-charts.js + Canvas 2D API
+ *
+ * 用于与 BaseChartOriginal2D 进行对比调试
+ */
+interface ColumnChartFCExampleProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId?: string;
+  /** 图表宽度(像素),默认为屏幕宽度 */
+  width?: number;
+  /** 图表高度(像素),默认根据宽高比计算 */
+  height?: number;
+}
+
+function ColumnChartFCComponent(props: ColumnChartFCExampleProps) {
+  const { canvasId = 'vTsUbNQurVfZNRMaupTQjygMuEHwuTNT', width = 750, height = 500 } = props;
+
+  const [cWidth, setCWidth] = useState(750);
+  const [cHeight, setCHeight] = useState(500);
+  const [pixelRatio, setPixelRatio] = useState(1);
+
+  // 使用 ref 存储图表实例
+  const uChartsInstanceRef = useRef<Record<string, any>>({});
+
+  const drawCharts = (id: string, data: any) => {
+    const query = Taro.createSelectorQuery();
+    query.select('#' + id).fields({ node: true, size: true }).exec(res => {
+      if (res[0]) {
+        const canvas = res[0].node;
+        const ctx = canvas.getContext('2d') as ExtendedCanvasContext;
+        const chart = new uChartsClass({
+          type: "column",
+          context: ctx,
+          width: canvas.width,
+          height: canvas.height,
+          categories: data.categories,
+          series: data.series,
+          animation: true,
+          background: "#FFFFFF",
+          color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
+          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
+            }
+          }
+        } as ChartsConfig);
+        uChartsInstanceRef.current[id] = chart;
+        const categories = data.categories;
+        const series = data.series;
+        console.log('[ColumnChartFCExample] 图表初始化完成:', id, {
+          cWidth, cHeight,
+          canvasWidth: canvas.width,
+          canvasHeight: canvas.height,
+          categoriesLength: categories.length,
+          seriesLength: series.length,
+          categories,
+          series,
+        });
+      } else {
+        console.error("[ColumnChartFCExample]: 未获取到 context");
+      }
+    });
+  }
+
+  const getServerData = () => {
+    //模拟从服务器获取数据时的延时
+    setTimeout(() => {
+      //模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
+      let res = {
+        categories: ["2018", "2019", "2020", "2021", "2022", "2023"],
+        series: [
+          {
+            name: "目标值",
+            data: [35, 36, 31, 33, 13, 34]
+          },
+          {
+            name: "完成量",
+            data: [18, 27, 21, 24, 6, 28]
+          }
+        ]
+      };
+      drawCharts(canvasId, res);
+    }, 500);
+  }
+
+  const tap = (e: any) => {
+    uChartsInstanceRef.current[e.currentTarget.id]?.touchLegend(e);
+    uChartsInstanceRef.current[e.currentTarget.id]?.showToolTip(e);
+  }
+
+  useLayoutEffect(() => {
+    console.debug('[ColumnChartFCExample] useLayoutEffect')
+    const sysInfo = Taro.getSystemInfoSync();
+    const pr = sysInfo.pixelRatio;
+    //这里的第一个 750 对应 css .charts 的 width
+    const cw = width / 750 * sysInfo.windowWidth;
+    //这里的 500 对应 css .charts 的 height
+    const ch = height / 750 * sysInfo.windowWidth;
+    setCWidth(cw);
+    setCHeight(ch);
+    setPixelRatio(pr);
+
+    // 直接在这里获取数据
+    getServerData();
+  }, [width, height, canvasId]);
+
+  const canvasProps = { style: { width: cWidth, height: cHeight } };
+  return (
+    <View>
+      <Canvas
+        {...canvasProps}
+        canvas-id={canvasId}
+        id={canvasId}
+        type="2d"
+        onTouchEnd={tap}
+      />
+    </View>
+  );
+}
+
+// 默认导出
+export default ColumnChartFCComponent;
+
+// 命名导出,保持向后兼容
+export const ColumnChartFCExample = ColumnChartFCComponent;

+ 88 - 0
mini-ui-packages/mini-charts/src/components/LineChart.tsx

@@ -0,0 +1,88 @@
+import React from 'react';
+import { BaseChart } from './BaseChart';
+import type { ChartsConfig } from '../lib/u-charts-original';
+
+/**
+ * 数据点形状
+ */
+export type DataPointShape = 'circle' | 'rect' | 'triangle' | 'diamond';
+
+/**
+ * LineChart 组件的 Props 接口
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export interface LineChartProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId: string;
+  /** 图表宽度(像素) */
+  width?: number;
+  /** 图表高度(像素) */
+  height?: number;
+  /** X 轴分类数据 */
+  categories: string[];
+  /** 系列数据 */
+  series: ChartsConfig['series'];
+  /** 额外的图表配置 */
+  config?: Partial<ChartsConfig>;
+}
+
+/**
+ * LineChart 折线图组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ * 用于显示趋势数据的折线图
+ */
+export const LineChart: React.FC<LineChartProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    categories,
+    series,
+    config = {},
+  } = props;
+
+  /**
+   * 合并默认配置
+   */
+  const mergedConfig: Partial<ChartsConfig> = {
+    animation: true,
+    background: '#FFFFFF',
+    color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+    padding: [15, 15, 0, 5],
+    enableScroll: false,
+    legend: {},
+    xAxis: {
+      disableGrid: false,
+      ...config.xAxis,
+    },
+    yAxis: {
+      data: [{ min: 0 }],
+      ...config.yAxis,
+    },
+    extra: {
+      line: {
+        type: 'curve',
+        width: 2,
+        activeType: 'hollow',
+        ...config.extra?.line,
+      },
+      ...config.extra,
+    },
+    ...config,
+  };
+
+  return (
+    <BaseChart
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type="line"
+      categories={categories}
+      series={series}
+      config={mergedConfig}
+    />
+  );
+};
+
+export default LineChart;

+ 77 - 0
mini-ui-packages/mini-charts/src/components/PieChart.tsx

@@ -0,0 +1,77 @@
+import React from 'react';
+import { BaseChart } from './BaseChart';
+import type { ChartsConfig } from '../lib/u-charts-original';
+
+/**
+ * PieChart 组件的 Props 接口
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export interface PieChartProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId: string;
+  /** 图表宽度(像素) */
+  width?: number;
+  /** 图表高度(像素) */
+  height?: number;
+  /** 系列数据 */
+  series: ChartsConfig['series'];
+  /** 额外的图表配置 */
+  config?: Partial<ChartsConfig>;
+}
+
+/**
+ * PieChart 饼图组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ * 用于显示占比数据的饼图
+ *
+ * 注意:环形图请使用 RingChart 组件
+ */
+export const PieChart: React.FC<PieChartProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    series,
+    config = {},
+  } = props;
+
+  /**
+   * 合并默认配置
+   */
+  const mergedConfig: Partial<ChartsConfig> = {
+    animation: true,
+    background: '#FFFFFF',
+    color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+    padding: [5, 5, 5, 5],
+    enableScroll: false,
+    legend: {},
+    extra: {
+      pie: {
+        activeOpacity: 0.5,
+        activeRadius: 10,
+        offsetAngle: 0,
+        labelWidth: 15,
+        border: false,
+        borderWidth: 3,
+        borderColor: "#FFFFFF",
+        ...config.extra?.pie,
+      },
+      ...config.extra,
+    },
+    ...config,
+  };
+
+  return (
+    <BaseChart
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type="pie"
+      series={series}
+      config={mergedConfig}
+    />
+  );
+};
+
+export default PieChart;

+ 134 - 0
mini-ui-packages/mini-charts/src/components/PieChartFCExample.tsx

@@ -0,0 +1,134 @@
+import React, { useState, useRef, useLayoutEffect } from 'react';
+import Taro from '@tarojs/taro';
+import { View, 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';
+
+/**
+ * PieChartFCExample 组件
+ *
+ * FC 示例组件(基于 docs/小程序图表库示例/taro-2d饼状图class使用示例.md)
+ * 使用原始 u-charts.js + Canvas 2D API
+ *
+ * 用于与 BaseChartOriginal2D 进行对比调试
+ */
+interface PieChartFCExampleProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId?: string;
+  /** 图表宽度(像素),默认为屏幕宽度 */
+  width?: number;
+  /** 图表高度(像素),默认根据宽高比计算 */
+  height?: number;
+}
+
+function PieChartFCComponent(props: PieChartFCExampleProps) {
+  const { canvasId = 'HPTdeOYFgaHlwidEmFOZkYTRwhnebAad', width = 750, height = 500 } = props;
+
+  const [cWidth, setCWidth] = useState(750);
+  const [cHeight, setCHeight] = useState(500);
+  const [pixelRatio, setPixelRatio] = useState(1);
+
+  // 使用 ref 存储图表实例
+  const uChartsInstanceRef = useRef<Record<string, any>>({});
+
+  const drawCharts = (id: string, data: any) => {
+    const query = Taro.createSelectorQuery();
+    query.select('#' + id).fields({ node: true, size: true }).exec(res => {
+      if (res[0]) {
+        const canvas = res[0].node;
+        const ctx = canvas.getContext('2d') as ExtendedCanvasContext;
+        const chart = new uChartsClass({
+          type: "pie",
+          context: ctx,
+          width: canvas.width,
+          height: canvas.height,
+          series: data.series,
+          animation: true,
+          background: "#FFFFFF",
+          color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
+          padding: [5,5,5,5],
+          enableScroll: false,
+          extra: {
+            pie: {
+              activeOpacity: 0.5,
+              activeRadius: 10,
+              offsetAngle: 0,
+              labelWidth: 15,
+              border: false,
+              borderWidth: 3,
+              borderColor: "#FFFFFF"
+            }
+          }
+        } as ChartsConfig);
+        uChartsInstanceRef.current[id] = chart;
+        const series = data.series;
+        console.log('[PieChartFCExample] 图表初始化完成:', id, {
+          cWidth, cHeight,
+          canvasWidth: canvas.width,
+          canvasHeight: canvas.height,
+          seriesLength: series.length,
+          series,
+        });
+      } else {
+        console.error("[PieChartFCExample]: 未获取到 context");
+      }
+    });
+  }
+
+  const getServerData = () => {
+    //模拟从服务器获取数据时的延时
+    setTimeout(() => {
+      //模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
+      let res = {
+        series: [
+          {
+            data: [{"name":"一班","value":50},{"name":"二班","value":30},{"name":"三班","value":20},{"name":"四班","value":18},{"name":"五班","value":8}]
+          }
+        ]
+      };
+      drawCharts(canvasId, res);
+    }, 500);
+  }
+
+  const tap = (e: any) => {
+    uChartsInstanceRef.current[e.currentTarget.id]?.touchLegend(e);
+    uChartsInstanceRef.current[e.currentTarget.id]?.showToolTip(e);
+  }
+
+  useLayoutEffect(() => {
+    console.debug('[PieChartFCExample] useLayoutEffect')
+    const sysInfo = Taro.getSystemInfoSync();
+    const pr = sysInfo.pixelRatio;
+    //这里的第一个 750 对应 css .charts 的 width
+    const cw = width / 750 * sysInfo.windowWidth;
+    //这里的 500 对应 css .charts 的 height
+    const ch = height / 750 * sysInfo.windowWidth;
+    setCWidth(cw);
+    setCHeight(ch);
+    setPixelRatio(pr);
+
+    // 直接在这里获取数据
+    getServerData();
+  }, [width, height, canvasId]);
+
+  const canvasProps = { style: { width: cWidth, height: cHeight } };
+  return (
+    <View>
+      <Canvas
+        {...canvasProps}
+        canvas-id={canvasId}
+        id={canvasId}
+        type="2d"
+        className="charts"
+        onTouchEnd={tap}
+      />
+    </View>
+  );
+}
+
+// 默认导出
+export default PieChartFCComponent;
+
+// 命名导出,保持向后兼容
+export const PieChartFCExample = PieChartFCComponent;

+ 80 - 0
mini-ui-packages/mini-charts/src/components/RingChart.tsx

@@ -0,0 +1,80 @@
+import React from 'react';
+import { BaseChart } from './BaseChart';
+import type { ChartsConfig } from '../lib/u-charts-original';
+
+/**
+ * RingChart 环形图组件的 Props 接口
+ * 使用原始 u-charts.js + Canvas 2D API
+ */
+export interface RingChartProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId: string;
+  /** 图表宽度(像素) */
+  width?: number;
+  /** 图表高度(像素) */
+  height?: number;
+  /** 环形宽度(0-1之间的比例,默认 0.6) */
+  ringWidth?: number;
+  /** 系列数据 */
+  series: ChartsConfig['series'];
+  /** 额外的图表配置 */
+  config?: Partial<ChartsConfig>;
+}
+
+/**
+ * RingChart 环形图组件
+ *
+ * 使用原始 u-charts.js + Canvas 2D API
+ * 用于显示占比数据的环形图
+ */
+export const RingChart: React.FC<RingChartProps> = (props) => {
+  const {
+    canvasId,
+    width,
+    height,
+    ringWidth = 0.6,
+    series,
+    config = {},
+  } = props;
+
+  /**
+   * 合并默认配置
+   */
+  const mergedConfig: Partial<ChartsConfig> = {
+    animation: true,
+    background: '#FFFFFF',
+    color: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#ef4444'],
+    padding: [5, 5, 5, 5],
+    enableScroll: false,
+    dataLabel: true,
+    legend: {},
+    extra: {
+      ring: {
+        ringWidth: ringWidth,
+        activeOpacity: 0.5,
+        activeRadius: 10,
+        offsetAngle: 0,
+        labelWidth: 15,
+        border: false,
+        borderWidth: 3,
+        borderColor: "#FFFFFF",
+        ...config.extra?.ring,
+      },
+      ...config.extra,
+    },
+    ...config,
+  };
+
+  return (
+    <BaseChart
+      canvasId={canvasId}
+      width={width}
+      height={height}
+      type="ring"
+      series={series}
+      config={mergedConfig}
+    />
+  );
+};
+
+export default RingChart;

+ 151 - 0
mini-ui-packages/mini-charts/src/components/RingChartFCExample.tsx

@@ -0,0 +1,151 @@
+import React, { useState, useRef, useLayoutEffect } from 'react';
+import Taro from '@tarojs/taro';
+import { View, 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';
+
+/**
+ * RingChartFCExample 组件
+ *
+ * FC 示例组件(环形图版本)
+ * 使用原始 u-charts.js + Canvas 2D API
+ *
+ * 用于与 BaseChartOriginal2D 进行对比调试
+ */
+interface RingChartFCExampleProps {
+  /** Canvas 元素的 ID,必须唯一 */
+  canvasId?: string;
+  /** 图表宽度(像素),默认为屏幕宽度 */
+  width?: number;
+  /** 图表高度(像素),默认根据宽高比计算 */
+  height?: number;
+}
+
+function RingChartFCComponent(props: RingChartFCExampleProps) {
+  const { canvasId = 'RingChartFCExample', width = 750, height = 500 } = props;
+
+  const [cWidth, setCWidth] = useState(750);
+  const [cHeight, setCHeight] = useState(500);
+  const [pixelRatio, setPixelRatio] = useState(1);
+
+  // 使用 ref 存储图表实例
+  const uChartsInstanceRef = useRef<Record<string, any>>({});
+
+  const drawCharts = (id: string, data: any) => {
+    const query = Taro.createSelectorQuery();
+    query.select('#' + id).fields({ node: true, size: true }).exec(res => {
+      if (res[0]) {
+        const canvas = res[0].node;
+        const ctx = canvas.getContext('2d') as ExtendedCanvasContext;
+        const chart = new uChartsClass({
+          type: "ring",
+          context: ctx,
+          width: canvas.width,
+          height: canvas.height,
+          series: data.series,
+          animation: true,
+          background: "#FFFFFF",
+          color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
+          padding: [5,5,5,5],
+          enableScroll: false,
+          dataLabel: true,
+          legend: {
+            show: true,
+            position: "right",
+            lineHeight: 25
+          },
+          title: {
+            name: "在职状态",
+            fontSize: 15,
+            color: "#666666"
+          },
+          subtitle: {
+            name: "70%",
+            fontSize: 25,
+            color: "#7cb5ec"
+          },
+          extra: {
+            ring: {
+              ringWidth: 60,
+              activeOpacity: 0.5,
+              activeRadius: 10,
+              offsetAngle: 0,
+              labelWidth: 15,
+              border: false,
+              borderWidth: 3,
+              borderColor: "#FFFFFF"
+            }
+          }
+        } as ChartsConfig);
+        uChartsInstanceRef.current[id] = chart;
+        const series = data.series;
+        console.log('[RingChartFCExample] 图表初始化完成:', id, {
+          cWidth, cHeight,
+          canvasWidth: canvas.width,
+          canvasHeight: canvas.height,
+          seriesLength: series.length,
+          series,
+        });
+      } else {
+        console.error("[RingChartFCExample]: 未获取到 context");
+      }
+    });
+  }
+
+  const getServerData = () => {
+    //模拟从服务器获取数据时的延时
+    setTimeout(() => {
+      //模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
+      let res = {
+        series: [
+          {
+            data: [{"name":"在职","value":24},{"name":"离职","value":8},{"name":"试用期","value":5},{"name":"停薪留职","value":2}]
+          }
+        ]
+      };
+      drawCharts(canvasId, res);
+    }, 500);
+  }
+
+  const tap = (e: any) => {
+    uChartsInstanceRef.current[e.currentTarget.id]?.touchLegend(e);
+    uChartsInstanceRef.current[e.currentTarget.id]?.showToolTip(e);
+  }
+
+  useLayoutEffect(() => {
+    console.debug('[RingChartFCExample] useLayoutEffect')
+    const sysInfo = Taro.getSystemInfoSync();
+    const pr = sysInfo.pixelRatio;
+    //这里的第一个 750 对应 css .charts 的 width
+    const cw = width / 750 * sysInfo.windowWidth;
+    //这里的 500 对应 css .charts 的 height
+    const ch = height / 750 * sysInfo.windowWidth;
+    setCWidth(cw);
+    setCHeight(ch);
+    setPixelRatio(pr);
+
+    // 直接在这里获取数据
+    getServerData();
+  }, [width, height, canvasId]);
+
+  const canvasProps = { style: { width: cWidth, height: cHeight } };
+  return (
+    <View>
+      <Canvas
+        {...canvasProps}
+        canvas-id={canvasId}
+        id={canvasId}
+        type="2d"
+        className="charts"
+        onTouchEnd={tap}
+      />
+    </View>
+  );
+}
+
+// 默认导出
+export default RingChartFCComponent;
+
+// 命名导出,保持向后兼容
+export const RingChartFCExample = RingChartFCComponent;

+ 1 - 0
mini-ui-packages/mini-charts/src/components/index.ts

@@ -0,0 +1 @@
+// 暂时不导出任何组件

+ 271 - 0
mini-ui-packages/mini-charts/src/index.ts

@@ -0,0 +1,271 @@
+// // Export modularized config and utility functions with type definitions
+// export {
+//   config,
+//   assign,
+//   util
+// } from './lib/config';
+
+// export {
+//   hexToRgb
+// } from './lib/utils/color';
+
+// export {
+//   findRange,
+//   calCandleMA
+// } from './lib/utils/math';
+
+// export {
+//   convertCoordinateOrigin,
+//   isInAngleRange,
+//   calValidDistance
+// } from './lib/utils/coordinate';
+
+// export {
+//   measureText
+// } from './lib/utils/text';
+
+// export {
+//   avoidCollision,
+//   isCollision
+// } from './lib/utils/collision';
+
+// export {
+//   getH5Offset,
+//   createCurveControlPoints,
+//   getTouches
+// } from './lib/utils/misc';
+
+// // Export core chart classes
+// export {
+//   uCharts,
+//   uChartsEvent
+// } from './lib/charts/index';
+
+// // Default export for backward compatibility
+// export { default } from './lib/charts/index';
+
+// // Re-export config and util from u-charts for backward compatibility
+// export { config as uChartsConfig, util as uChartsUtil } from './lib/config';
+
+// // Export chart types
+// export type {
+//   CanvasContext,
+//   ChartsTitle,
+//   ChartsConfig,
+//   YAxisConfig,
+//   XAxisConfig,
+//   LegendConfig,
+//   ExtraConfig,
+//   ScrollOption,
+//   TouchEvent,
+//   TouchPoint,
+//   ToolTipOption as ChartsToolTipOption,
+//   EventListener,
+//   EventMap
+// } from './lib/charts/index';
+
+// // Export data processing functions
+// export {
+//   fixPieSeries,
+//   fillSeries,
+//   fillCustomColor,
+//   getDataRange,
+//   dataCombine,
+//   dataCombineStack,
+//   calXAxisData,
+//   getXAxisPoints,
+//   calYAxisData,
+//   calCategoriesData,
+//   getToolTipData,
+//   getMixToolTipData
+// } from './lib/data-processing/index';
+
+// export type {
+//   SeriesItem,
+//   ChartOptions,
+//   ChartExtraOptions,
+//   BarOptions,
+//   ColumnOptions,
+//   TooltipOptions,
+//   MountOptions,
+//   XAxisOptions,
+//   YAxisOptions,
+//   YAxisDataItem,
+//   ChartData,
+//   UChartsConfig,
+//   DataRange,
+//   XAxisDataResult,
+//   YAxisDataResult,
+//   AxisPointsResult,
+//   CategoriesDataResult,
+//   ToolTipOption,
+//   ToolTipDataResult
+// } from './lib/data-processing/index';
+
+// // Export charts data points calculation functions
+// export {
+//   getDataPoints,
+//   getLineDataPoints,
+//   getColumnDataPoints,
+//   getCandleDataPoints,
+//   getMountDataPoints,
+//   getBarDataPoints,
+//   getStackDataPoints,
+//   getBarStackDataPoints,
+//   getPieDataPoints,
+//   getRoseDataPoints,
+//   getRadarDataPoints,
+//   getGaugeDataPoints,
+//   getGaugeArcbarDataPoints,
+//   getArcbarDataPoints,
+//   getGaugeAxisPoints,
+//   getFunnelDataPoints
+// } from './lib/charts-data/index';
+
+// // Export renderer functions
+// export {
+//   drawPointShape,
+//   drawActivePoint,
+//   drawRingTitle,
+//   drawPointText,
+//   drawToolTipSplitLine,
+//   drawMarkLine,
+//   drawToolTipHorizentalLine,
+//   drawToolTipSplitArea,
+//   drawBarToolTipSplitArea,
+//   drawToolTip,
+//   drawToolTipBridge,
+//   drawCanvas
+// } from './lib/renderers/index';
+
+// export type {
+//   Point as RendererPoint,
+//   ToolTipTextItem,
+//   ToolTipOption as RendererToolTipOption,
+//   MarkLineDataItem,
+//   ActivePointOption,
+//   TitleOption
+// } from './lib/renderers/index';
+
+// // Export helper functions
+// export {
+//   // Index finders
+//   findCurrentIndex,
+//   findBarChartCurrentIndex,
+//   findLegendIndex,
+//   findRadarChartCurrentIndex,
+//   findFunnelChartCurrentIndex,
+//   findWordChartCurrentIndex,
+//   findMapChartCurrentIndex,
+//   findRoseChartCurrentIndex,
+//   findPieChartCurrentIndex,
+//   // Area checkers
+//   isInExactLegendArea,
+//   isInExactChartArea,
+//   isInExactPieChartArea,
+//   // Data helpers
+//   getSeriesDataItem,
+//   filterSeries,
+//   splitPoints,
+//   getMaxTextListLength,
+//   getRadarCoordinateSeries,
+//   // Legend helpers
+//   calLegendData,
+//   getPieTextMaxLength,
+//   // Coordinate helpers
+//   lonlat2mercator,
+//   mercator2lonlat,
+//   getBoundingBox,
+//   coordinateToPoint,
+//   pointToCoordinate,
+//   isRayIntersectsSegment,
+//   isPoiWithinPoly,
+//   // Data fixers
+//   fixColumeData,
+//   fixBarData,
+//   fixColumeMeterData,
+//   fixColumeStackData,
+//   fixBarStackData,
+//   // Misc helpers
+//   getXAxisTextList,
+//   getYAxisTextList,
+//   calTooltipYAxisData,
+//   calMarkLineData,
+//   contextRotate,
+//   normalInt,
+//   collisionNew,
+//   getWordCloudPoint
+// } from './lib/helper-functions/index';
+
+// export type {
+//   LegendData,
+//   PieData,
+//   RadarData
+// } from './lib/helper-functions/index';
+
+// // Export draw controllers (core drawing control functions)
+// export {
+//   drawCharts
+// } from './lib/draw-controllers/index';
+
+// export {
+//   Animation,
+//   AnimationFunction
+// } from './lib/draw-controllers/index';
+
+// export type {
+//   DrawChartsContext,
+//   DrawChartsFunction,
+//   AnimationOptions,
+//   TimingFunction,
+//   TimingFunctions
+// } from './lib/draw-controllers/index';
+
+// ============================================================================
+// React Chart Components (Story 016.009)
+// ============================================================================
+
+// export {
+//   // BaseChart component
+//   BaseChart,
+//   BaseChartDefault,
+//   // BarChart component (横向柱状图)
+//   BarChart,
+//   BarChartDefault,
+//   // ColumnChart component
+//   ColumnChart,
+//   ColumnChartDefault,
+//   // LineChart component
+//   LineChart,
+//   LineChartDefault,
+//   // CandleChart component
+//   CandleChart,
+//   CandleChartDefault,
+//   // PieChart component
+//   PieChart,
+//   PieChartDefault,
+//   // RadarChart component
+//   RadarChart,
+//   RadarChartDefault
+// } from './components/index';
+
+// export type {
+//   // BaseChart types
+//   BaseChartProps,
+//   // BarChart types
+//   BarChartProps,
+//   BarType,
+//   // ColumnChart types
+//   ColumnChartProps,
+//   ColumnType,
+//   // LineChart types
+//   LineChartProps,
+//   DataPointShape,
+//   // CandleChart types
+//   CandleChartProps,
+//   // PieChart types
+//   PieChartProps,
+//   PieChartType,
+//   // RadarChart types
+//   RadarChartProps
+// } from './components/index';

+ 606 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/basic-charts.ts

@@ -0,0 +1,606 @@
+// 动画进度全局变量
+declare const process: number;
+
+/**
+ * 图表数据点接口
+ */
+export interface DataPoint {
+  x: number;
+  y: number;
+  color?: string;
+  value?: number;
+  width?: number;
+  height?: number;
+  r?: number; // 气泡图半径
+  t?: string; // 气泡图文本
+}
+
+/**
+ * 蜡烛图数据点
+ */
+export interface CandleDataPoint {
+  x: number;
+  y: number;
+}
+
+/**
+ * 堆叠数据点(包含 y0 位置)
+ */
+export interface StackDataPoint extends DataPoint {
+  y0: number;
+}
+
+/**
+ * 条形堆叠数据点(包含 x0 位置)
+ */
+export interface BarStackDataPoint {
+  color?: string;
+  y: number;
+  height: number;
+  x: number;
+  x0: number;
+}
+
+/**
+ * 山丘图数据点(包含宽度和值)
+ */
+export interface MountDataPoint extends DataPoint {
+  value: number;
+  width: number;
+}
+
+/**
+ * 图表配置接口
+ */
+export interface ChartOptions {
+  type: string;
+  categories?: string[];
+  xAxis?: {
+    boundaryGap?: string;
+    itemcount?: number;
+    [key: string]: any;
+  };
+  area: [number, number, number, number];
+  width: number;
+  height: number;
+  pix?: number;
+  enableScroll?: boolean;
+  extra?: {
+    mix?: {
+      column?: {
+        seriesGap?: number;
+        categoryGap?: number;
+        width?: number;
+      };
+    };
+    column?: {
+      seriesGap?: number;
+      categoryGap?: number;
+      width?: number;
+    };
+    bar?: {
+      seriesGap?: number;
+      categoryGap?: number;
+      width?: number;
+    };
+    mount?: {
+      widthRatio?: number;
+    };
+    radar?: {
+      max?: number;
+    };
+    [key: string]: any;
+  };
+  chartData?: {
+    xAxisData?: {
+      ranges?: number[][];
+    };
+    [key: string]: any;
+  };
+  [key: string]: any;
+}
+
+/**
+ * uCharts 配置接口
+ */
+export interface UChartsConfig {
+  version: string;
+  color: string[];
+  [key: string]: any;
+}
+
+/**
+ * 堆叠图系列项
+ */
+export interface SeriesItem {
+  data: (number | null)[];
+  color?: string;
+  [key: string]: any;
+}
+
+/**
+ * 折线动画选项
+ */
+export interface LineOption {
+  animation?: string;
+}
+
+/**
+ * 山丘图配置选项
+ */
+export interface MountOption {
+  widthRatio?: number;
+}
+
+/**
+ * 获取蜡烛图(K线图)数据点
+ * @param data - 蜡烛图数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @returns 蜡烛图数据点数组
+ */
+export function getCandleDataPoints(
+  data: (number | { value?: number })[][],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig
+): (CandleDataPoint[] | null)[] {
+  let points: (CandleDataPoint[] | null)[] = [];
+  let validHeight = opts.height! - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let cPoints: CandleDataPoint[] = [];
+      item.forEach(function(items, indexs) {
+        let point: CandleDataPoint = { x: 0, y: 0 };
+        point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+        let value = typeof items === 'object' && items.value !== undefined ? items.value : items;
+        let height = validHeight * ((value as number) - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height! - Math.round(height) - opts.area[2];
+        cPoints.push(point);
+      });
+      points.push(cPoints);
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取散点图和气泡图数据点
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @param process - 动画进度(默认为1)
+ * @returns 数据点数组
+ */
+export function getDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  process = 1
+): (DataPoint | null)[] {
+  let boundaryGap = 'center';
+  if (opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' ) {
+    boundaryGap = opts.xAxis?.boundaryGap || 'center';
+  }
+  let points: (DataPoint | null)[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: DataPoint = { x: 0, y: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      point.x = xAxisPoints[index];
+      let value: any = item;
+      if (typeof item === 'object' && item !== null) {
+        if (Array.isArray(item)) {
+          const ranges = opts.chartData?.xAxisData?.ranges;
+          if (ranges && ranges.length > 0) {
+            const xranges = ranges as number[][];
+            const allRanges: number[] = [...xranges[0], ...xranges[xranges.length - 1]];
+            const xminRange = allRanges[0];
+            const xmaxRange = allRanges[allRanges.length - 1];
+            value = item[1];
+            point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          }
+          if(opts.type == 'bubble'){
+            point.r = item[2] as number | undefined;
+            point.t = item[3] as string | undefined;
+          }
+        } else {
+          value = item.value;
+        }
+      }
+      if (boundaryGap == 'center') {
+        point.x += eachSpacing / 2;
+      }
+      let height = validHeight * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取折线图和面积图数据点
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @param lineOption - 折线动画选项
+ * @param process - 动画进度(默认为1)
+ * @returns 数据点数组
+ */
+export function getLineDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  lineOption: LineOption,
+  process = 1
+): (DataPoint | null)[] {
+  let boundaryGap = opts.xAxis?.boundaryGap || 'center';
+  let points: (DataPoint | null)[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: DataPoint = { x: 0, y: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      if(lineOption.animation == 'vertical'){
+        point.x = xAxisPoints[index];
+        let value: any = item;
+        if (typeof item === 'object' && item !== null) {
+          if (Array.isArray(item)) {
+            const ranges = opts.chartData?.xAxisData?.ranges;
+            if (ranges && ranges.length > 0) {
+              const xranges = ranges as number[][];
+              const allRanges: number[] = [...xranges[0], ...xranges[xranges.length - 1]];
+              const xminRange = allRanges[0];
+              const xmaxRange = allRanges[allRanges.length - 1];
+              value = item[1];
+              point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+            }
+          } else {
+            value = item.value;
+          }
+        }
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }else{
+        point.x = xAxisPoints[0] + eachSpacing * index * process;
+        let value: any = item;
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取柱状图数据点
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @param zeroPoints - 零点(未使用,保留用于兼容性)
+ * @param process - 动画进度(默认为1)
+ * @returns 数据点数组
+ */
+export function getColumnDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  zeroPoints: number[],
+  process = 1
+): (DataPoint | null)[] {
+  let points: (DataPoint | null)[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: DataPoint = { x: 0, y: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      point.x = xAxisPoints[index];
+      let value: any = item;
+      if (typeof item === 'object' && item !== null) {
+        if (Array.isArray(item)) {
+          const ranges = opts.chartData?.xAxisData?.ranges;
+          if (ranges && ranges.length > 0) {
+            const xranges = ranges as number[][];
+            const allRanges: number[] = [...xranges[0], ...xranges[xranges.length - 1]];
+            const xminRange = allRanges[0];
+            const xmaxRange = allRanges[allRanges.length - 1];
+            value = item[1];
+            point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          }
+        } else {
+          value = item.value;
+        }
+      }
+      point.x += eachSpacing / 2;
+      let height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+
+  return points;
+}
+
+/**
+ * 获取山丘图数据点
+ * @param series - 系列数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param mountOption - 山丘图配置选项
+ * @param zeroPoints - 零点(未使用,保留用于兼容性)
+ * @returns 山丘图数据点数组
+ */
+export function getMountDataPoints(
+  series: { data: number; color?: string }[],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  mountOption: MountOption,
+  zeroPoints: number[],
+  process = 1
+): (MountDataPoint | null)[] {
+  let points: (MountDataPoint | null)[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  let mountWidth = eachSpacing * (mountOption.widthRatio || 1);
+  series.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: MountDataPoint = { x: 0, y: 0, value: 0, width: 0 };
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      point.x += eachSpacing / 2;
+      let value = item.data;
+      let height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      point.value = value;
+      point.width = mountWidth;
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取条形图数据点
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param yAxisPoints - Y轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @returns 数据点数组
+ */
+export function getBarDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  yAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  process = 1
+): DataPoint[] {
+  let points: DataPoint[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push({ x: 0, y: 0 });
+    } else {
+      let point: DataPoint = { x: 0, y: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      point.y = yAxisPoints[index];
+      let value: any = item;
+      if (typeof item === 'object' && item !== null) {
+        value = item.value;
+      }
+      let height = validWidth * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      if ('height' in point) {
+        (point as any).height = height;
+      }
+      if ('value' in point) {
+        point.value = value;
+      }
+      point.x = height + opts.area[3];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取堆叠图数据点(用于柱状图/折线图堆叠)
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param xAxisPoints - X轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @param seriesIndex - 当前系列索引
+ * @param stackSeries - 所有堆叠系列数据
+ * @returns 堆叠数据点数组
+ */
+export function getStackDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  xAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  seriesIndex: number,
+  stackSeries: SeriesItem[],
+  process = 1
+): (StackDataPoint | null)[] {
+  let points: (StackDataPoint | null)[] = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: StackDataPoint = { x: 0, y: 0, y0: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+
+      let height: number, height0: number;
+      if (seriesIndex > 0) {
+        let value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index] || 0;
+        }
+        let value0 = value - (typeof item === 'object' && item !== null ? item.value || 0 : item);
+        height = validHeight * (value - minRange) / (maxRange - minRange);
+        height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        let value = typeof item === 'object' && item !== null ? item.value || 0 : item;
+        height = validHeight * (value - minRange) / (maxRange - minRange);
+        height0 = 0;
+      }
+      let heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.y = opts.height - Math.round(height) - opts.area[2];
+      point.y0 = opts.height - Math.round(heightc) - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+/**
+ * 获取条形堆叠图数据点
+ * @param data - 数据数组
+ * @param minRange - 最小范围值
+ * @param maxRange - 最大范围值
+ * @param yAxisPoints - Y轴点数组
+ * @param eachSpacing - 每个点之间的间距
+ * @param opts - 图表配置
+ * @param config - uCharts配置
+ * @param seriesIndex - 当前系列索引
+ * @param stackSeries - 所有堆叠系列数据
+ * @returns 条形堆叠数据点数组
+ */
+export function getBarStackDataPoints(
+  data: (number | DataPoint | null)[],
+  minRange: number,
+  maxRange: number,
+  yAxisPoints: number[],
+  eachSpacing: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  seriesIndex: number,
+  stackSeries: SeriesItem[],
+  process = 1
+): (BarStackDataPoint | null)[] {
+  let points: (BarStackDataPoint | null)[] = [];
+  let validHeight = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point: BarStackDataPoint = { x: 0, x0: 0, y: 0, height: 0 };
+      if (typeof item === 'object' && item !== null) {
+        point.color = item.color;
+      }
+      point.y = yAxisPoints[index];
+      let height: number, height0: number;
+      if (seriesIndex > 0) {
+        let value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index] || 0;
+        }
+        let value0 = value - (typeof item === 'object' && item !== null ? item.value || 0 : item);
+        height = validHeight * (value - minRange) / (maxRange - minRange);
+        height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        let value = typeof item === 'object' && item !== null ? item.value || 0 : item;
+        height = validHeight * (value - minRange) / (maxRange - minRange);
+        height0 = 0;
+      }
+      let heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.height = height - heightc;
+      point.x = opts.area[3] + height;
+      point.x0 = opts.area[3] + heightc;
+      points.push(point);
+    }
+  });
+  return points;
+}

+ 46 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/funnel-charts.ts

@@ -0,0 +1,46 @@
+// 动画进度全局变量
+declare const process: number;
+
+/**
+ * 漏斗图数据项接口
+ */
+export interface FunnelDataItem {
+  data: number;
+  radius?: number;
+  _proportion_?: number;
+  [key: string]: any;
+}
+
+/**
+ * 漏斗图配置选项
+ */
+export interface FunnelOption {
+  type?: string;
+  [key: string]: any;
+}
+
+/**
+ * 获取漏斗图数据点
+ * 计算漏斗图和金字塔图的半径和比例
+ * @param series - 漏斗图数据项数组
+ * @param radius - 漏斗图最大半径
+ * @param option - 漏斗图配置选项(type: 'funnel' 或 'pyramid')
+ * @param eachSpacing - 每项之间的间距
+ * @returns 包含计算后半径和比例的漏斗图数据项数组
+ */
+export function getFunnelDataPoints(
+  series: FunnelDataItem[],
+  radius: number,
+  option: FunnelOption,
+  eachSpacing: number
+): FunnelDataItem[] {
+  for (let i = 0; i < series.length; i++) {
+    if(option.type == 'funnel'){
+      series[i].radius = series[i].data / series[0].data * radius * process;
+    }else{
+      series[i].radius = (eachSpacing * (series.length - i)) / (eachSpacing * series.length) * radius * process;
+    }
+    series[i]._proportion_ = series[i].data / series[0].data;
+  }
+  return series;
+}

+ 219 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/gauge-charts.ts

@@ -0,0 +1,219 @@
+/**
+ * 仪表盘数据项接口
+ */
+export interface GaugeDataItem {
+  data: number | null;
+  _proportion_?: number;
+  color?: string;
+  _endAngle_?: number;
+  _oldAngle_?: number;
+  [key: string]: any;
+}
+
+/**
+ * 仪表盘分类项
+ */
+export interface GaugeCategoryItem {
+  value: number;
+  color: string;
+  _startAngle_?: number;
+  _endAngle_?: number;
+  [key: string]: any;
+}
+
+/**
+ * 仪表盘配置选项
+ */
+export interface GaugeOption {
+  type?: string;
+  startAngle?: number;
+  endAngle?: number;
+  direction?: string;
+  oldAngle?: number;
+  oldData?: number;
+  pointer?: {
+    color?: string;
+  };
+  [key: string]: any;
+}
+
+/**
+ * 环形条配置选项
+ */
+export interface ArcbarOption {
+  type?: string;
+  startAngle?: number;
+  endAngle?: number;
+  direction?: string;
+  [key: string]: any;
+}
+
+/**
+ * 获取仪表盘数据点
+ * 计算仪表盘显示的角度和颜色
+ * @param series - 仪表盘数据项数组
+ * @param categories - 颜色范围分类项数组
+ * @param gaugeOption - 仪表盘配置选项
+ * @param process - 动画进度(0-1),默认为1(无动画)
+ * @returns 包含计算后角度的仪表盘数据项数组
+ */
+export function getGaugeDataPoints(
+  series: GaugeDataItem[],
+  categories: GaugeCategoryItem[],
+  gaugeOption: GaugeOption,
+  process: number = 1
+): GaugeDataItem[] {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (gaugeOption.pointer?.color == 'auto') {
+      for (let j = 0; j < categories.length; j++) {
+        if (item.data <= categories[j].value) {
+          item.color = categories[j].color;
+          break;
+        }
+      }
+    } else {
+      item.color = gaugeOption.pointer?.color;
+    }
+    let totalAngle: number;
+    if (gaugeOption.endAngle! < gaugeOption.startAngle!) {
+      totalAngle = 2 + gaugeOption.endAngle! - gaugeOption.startAngle!;
+    } else {
+      totalAngle = gaugeOption.startAngle! - gaugeOption.endAngle!;
+    }
+    item._endAngle_ = totalAngle * item.data + gaugeOption.startAngle!;
+    item._oldAngle_ = gaugeOption.oldAngle || 0;
+    if (gaugeOption.oldAngle! < gaugeOption.endAngle!) {
+      item._oldAngle_! += 2;
+    }
+    if (item.data >= (gaugeOption.oldData || 0)) {
+      item._proportion_ = (item._endAngle_! - item._oldAngle_!) * process + (gaugeOption.oldAngle || 0);
+    } else {
+      item._proportion_ = item._oldAngle_! - (item._oldAngle_! - item._endAngle_!) * process;
+    }
+    if (item._proportion_! >= 2) {
+      item._proportion_ = item._proportion_! % 2;
+    }
+  }
+  return series;
+}
+
+/**
+ * 获取仪表盘环形条数据点
+ * 计算仪表盘环形条显示的比例
+ * @param series - 仪表盘数据项数组
+ * @param arcbarOption - 环形条配置选项
+ * @param process - 动画进度(0-1),默认为1(无动画)
+ * @returns 包含计算后比例的仪表盘数据项数组
+ */
+export function getGaugeArcbarDataPoints(
+  series: GaugeDataItem[],
+  arcbarOption: ArcbarOption,
+  process: number = 1
+): GaugeDataItem[] {
+  let currentProcess = process;
+  if (currentProcess == 1) {
+    currentProcess = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle: number;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if (arcbarOption.endAngle! < arcbarOption.startAngle!) {
+        totalAngle = 2 + arcbarOption.endAngle! - arcbarOption.startAngle!;
+      } else {
+        totalAngle = arcbarOption.startAngle! - arcbarOption.endAngle!;
+      }
+    }
+    item._proportion_ = totalAngle * item.data * currentProcess + arcbarOption.startAngle!;
+    if (item._proportion_! >= 2) {
+      item._proportion_ = item._proportion_! % 2;
+    }
+  }
+  return series;
+}
+
+/**
+ * 获取环形条数据点
+ * 计算环形条(圆形进度条)图表显示的比例
+ * @param series - 仪表盘数据项数组
+ * @param arcbarOption - 环形条配置选项
+ * @param process - 动画进度(0-1),默认为1(无动画)
+ * @returns 包含计算后比例的仪表盘数据项数组
+ */
+export function getArcbarDataPoints(
+  series: GaugeDataItem[],
+  arcbarOption: ArcbarOption,
+  process: number = 1
+): GaugeDataItem[] {
+  let currentProcess = process;
+  if (currentProcess == 1) {
+    currentProcess = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle: number;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if(arcbarOption.direction == 'ccw'){
+        if (arcbarOption.startAngle! < arcbarOption.endAngle!) {
+          totalAngle = 2 + arcbarOption.startAngle! - arcbarOption.endAngle!;
+        } else {
+          totalAngle = arcbarOption.startAngle! - arcbarOption.endAngle!;
+        }
+      }else{
+        if (arcbarOption.endAngle! < arcbarOption.startAngle!) {
+          totalAngle = 2 + arcbarOption.endAngle! - arcbarOption.startAngle!;
+        } else {
+          totalAngle = arcbarOption.startAngle! - arcbarOption.endAngle!;
+        }
+      }
+    }
+    item._proportion_ = totalAngle * item.data * currentProcess + arcbarOption.startAngle!;
+    if(arcbarOption.direction == 'ccw'){
+      item._proportion_ = arcbarOption.startAngle! - totalAngle * item.data * currentProcess;
+    }
+    if (item._proportion_! >= 2) {
+      item._proportion_ = item._proportion_! % 2;
+    }
+  }
+  return series;
+}
+
+/**
+ * 获取仪表盘轴线点
+ * 计算仪表盘的轴线点
+ * @param categories - 分类项数组
+ * @param startAngle - 仪表盘起始角度
+ * @param endAngle - 仪表盘结束角度
+ * @returns 包含计算后角度的分类项数组
+ */
+export function getGaugeAxisPoints(
+  categories: GaugeCategoryItem[],
+  startAngle: number,
+  endAngle: number
+): GaugeCategoryItem[] {
+  let totalAngle: number;
+  if (endAngle < startAngle) {
+    totalAngle = 2 + endAngle - startAngle;
+  } else {
+    totalAngle = startAngle - endAngle;
+  }
+  let tempStartAngle = startAngle;
+  for (let i = 0; i < categories.length; i++) {
+    categories[i].value = categories[i].value === null ? 0 : categories[i].value;
+    categories[i]._startAngle_ = tempStartAngle;
+    categories[i]._endAngle_ = totalAngle * categories[i].value + startAngle;
+    if (categories[i]._endAngle_! >= 2) {
+      categories[i]._endAngle_ = categories[i]._endAngle_! % 2;
+    }
+    tempStartAngle = categories[i]._endAngle_!;
+  }
+  return categories;
+}

+ 76 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/index.ts

@@ -0,0 +1,76 @@
+// Basic charts data points calculation functions
+export {
+  getDataPoints,
+  getLineDataPoints,
+  getColumnDataPoints,
+  getCandleDataPoints,
+  getMountDataPoints,
+  getBarDataPoints,
+  getStackDataPoints,
+  getBarStackDataPoints
+} from './basic-charts';
+
+// Pie and rose charts data points calculation functions
+export {
+  getPieDataPoints,
+  getRoseDataPoints
+} from './pie-charts';
+
+// Radar chart data points calculation functions
+export {
+  getRadarDataPoints
+} from './radar-charts';
+
+// Gauge and arcbar charts data points calculation functions
+export {
+  getGaugeDataPoints,
+  getGaugeArcbarDataPoints,
+  getArcbarDataPoints,
+  getGaugeAxisPoints
+} from './gauge-charts';
+
+// Funnel chart data points calculation functions
+export {
+  getFunnelDataPoints
+} from './funnel-charts';
+
+// Type exports for basic charts
+export type {
+  DataPoint,
+  CandleDataPoint,
+  StackDataPoint,
+  BarStackDataPoint,
+  MountDataPoint,
+  ChartOptions,
+  UChartsConfig,
+  SeriesItem,
+  LineOption,
+  MountOption
+} from './basic-charts';
+
+// Type exports for pie charts
+export type {
+  PieDataItem
+} from './pie-charts';
+
+// Type exports for radar charts
+export type {
+  ChartOptions as RadarChartOptions,
+  RadarSeriesItem,
+  RadarDataItem,
+  CenterPoint
+} from './radar-charts';
+
+// Type exports for gauge charts
+export type {
+  GaugeDataItem,
+  GaugeCategoryItem,
+  GaugeOption,
+  ArcbarOption
+} from './gauge-charts';
+
+// Type exports for funnel charts
+export type {
+  FunnelDataItem,
+  FunnelOption
+} from './funnel-charts';

+ 106 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/pie-charts.ts

@@ -0,0 +1,106 @@
+/**
+ * 饼图数据项接口
+ */
+export interface PieDataItem {
+  data: number | null;
+  _proportion_?: number;
+  _rose_proportion_?: number;
+  _radius_?: number;
+  _start_?: number;
+  color?: string;
+  name?: string;
+  formatter?: (value: number) => string;
+  textSize?: number;
+  [key: string]: any;
+}
+
+/**
+ * 获取饼图数据点
+ * 计算饼图扇区的比例、角度和半径
+ * @param series - 饼图数据项数组
+ * @param radius - 饼图半径
+ * @param process - 动画进度(0-1),默认为1(无动画)
+ * @returns 包含计算后的比例和角度的饼图数据项数组
+ */
+export function getPieDataPoints(
+  series: PieDataItem[],
+  radius: number,
+  process: number = 1
+): PieDataItem[] {
+  let count = 0;
+  let _start_ = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+    }
+    item._radius_ = radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._proportion_! * Math.PI;
+  }
+  return series;
+}
+
+/**
+ * 获取玫瑰图数据点
+ * 计算玫瑰图(南丁格尔图)扇区的比例、角度和半径
+ * @param series - 饼图数据项数组
+ * @param type - 玫瑰图类型('area' 或 'radius')
+ * @param minRadius - 玫瑰图最小半径
+ * @param radius - 玫瑰图最大半径
+ * @param process - 动画进度(0-1),默认为1(无动画)
+ * @returns 包含计算后的比例和角度的饼图数据项数组
+ */
+export function getRoseDataPoints(
+  series: PieDataItem[],
+  type: string,
+  minRadius: number,
+  radius: number,
+  process: number = 1
+): PieDataItem[] {
+  let count = 0;
+  let _start_ = 0;
+  let dataArr: number[] = [];
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+    dataArr.push(item.data);
+  }
+  let minData = Math.min.apply(null, dataArr);
+  let maxData = Math.max.apply(null, dataArr);
+  let radiusLength = radius - minRadius;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+      item._rose_proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+      if(type == 'area'){
+        item._rose_proportion_ = 1 / series.length * process;
+      }else{
+        item._rose_proportion_ = item.data / count * process;
+      }
+    }
+    item._radius_ = minRadius + radiusLength * ((item.data - minData) / (maxData - minData)) || radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._rose_proportion_! * Math.PI;
+  }
+  return series;
+}

+ 110 - 0
mini-ui-packages/mini-charts/src/lib/charts-data/radar-charts.ts

@@ -0,0 +1,110 @@
+import { convertCoordinateOrigin } from '../utils/coordinate';
+
+// 动画进度全局变量
+declare const process: number;
+
+/**
+ * 图表配置接口
+ */
+export interface ChartOptions {
+  extra?: {
+    radar?: {
+      max?: number;
+    };
+  };
+  [key: string]: any;
+}
+
+/**
+ * 雷达图系列数据
+ */
+export interface RadarSeriesItem {
+  data: number[];
+  color?: string;
+  [key: string]: any;
+}
+
+/**
+ * 雷达图数据项(包含计算后的位置)
+ */
+export interface RadarDataItem {
+  color?: string;
+  legendShape?: string;
+  pointShape?: string;
+  data: {
+    angle: number;
+    proportion: number;
+    value: number;
+    position: { x: number; y: number };
+  }[];
+}
+
+/**
+ * 中心点接口
+ */
+export interface CenterPoint {
+  x: number;
+  y: number;
+}
+
+/**
+ * 合并雷达图系列数据到单个数组
+ * 本地实现以避免与 data-processing 模块的类型冲突
+ */
+function dataCombineRadar(series: RadarSeriesItem[]): number[] {
+  return series.reduce(function(a: number[], b) {
+    return a.concat(b.data);
+  }, [] as number[]);
+}
+
+/**
+ * 获取雷达图数据点
+ * 计算雷达图上每个数据点的位置
+ * @param angleList - 每个轴的角度数组
+ * @param center - 中心点 {x, y}
+ * @param radius - 雷达图半径
+ * @param series - 系列数据数组
+ * @param opts - 图表配置
+ * @returns 包含计算后位置的雷达图数据项数组
+ */
+export function getRadarDataPoints(
+  angleList: number[],
+  center: CenterPoint,
+  radius: number,
+  series: RadarSeriesItem[],
+  opts: ChartOptions
+): RadarDataItem[] {
+  let radarOption = opts.extra?.radar || {};
+  radarOption.max = radarOption.max || 0;
+  let maxData = Math.max(radarOption.max || 0, Math.max.apply(null, dataCombineRadar(series)));
+  let data: RadarDataItem[] = [];
+  for (let i = 0; i < series.length; i++) {
+    let each = series[i];
+    let listItem: RadarDataItem = {
+      data: []
+    };
+    listItem.color = each.color;
+    listItem.legendShape = each.legendShape;
+    listItem.pointShape = each.pointShape;
+    listItem.data = [];
+    each.data.forEach(function(item, index) {
+      let tmp: {
+        angle: number;
+        proportion: number;
+        value: number;
+        position: { x: number; y: number };
+      } = { angle: 0, proportion: 0, value: 0, position: { x: 0, y: 0 } };
+      tmp.angle = angleList[index];
+      tmp.proportion = item / maxData;
+      tmp.value = item;
+      tmp.position = convertCoordinateOrigin(
+        radius * tmp.proportion * process * Math.cos(tmp.angle),
+        radius * tmp.proportion * process * Math.sin(tmp.angle),
+        center
+      );
+      listItem.data.push(tmp);
+    });
+    data.push(listItem);
+  }
+  return data;
+}

+ 29 - 0
mini-ui-packages/mini-charts/src/lib/charts/index.ts

@@ -0,0 +1,29 @@
+/**
+ * 核心类模块统一导出
+ *
+ * 导出 uCharts 主类和 uChartsEvent 事件类
+ * 以及相关的类型定义
+ */
+
+// 导出 uChartsEvent 事件类
+export { uChartsEvent } from './u-charts-event';
+export type { EventListener, EventMap } from './u-charts-event';
+
+// 导出 uCharts 主类
+export { uCharts } from './u-charts';
+export type {
+  CanvasContext,
+  ChartsTitle,
+  ChartsConfig,
+  YAxisConfig,
+  XAxisConfig,
+  LegendConfig,
+  ExtraConfig,
+  ScrollOption,
+  TouchEvent,
+  TouchPoint,
+  ToolTipOption
+} from './u-charts';
+
+// 默认导出 uCharts 主类(保持向后兼容)
+export { default } from './u-charts';

+ 72 - 0
mini-ui-packages/mini-charts/src/lib/charts/u-charts-event.ts

@@ -0,0 +1,72 @@
+/**
+ * uCharts 事件系统
+ *
+ * 提供事件监听、触发和删除功能
+ * 从 u-charts 核心库搬迁并添加完整类型注解
+ */
+
+/**
+ * 事件监听器函数类型
+ */
+export interface EventListener {
+  (...args: any[]): void;
+}
+
+/**
+ * 事件存储映射类型
+ */
+export interface EventMap {
+  [type: string]: EventListener[];
+}
+
+/**
+ * uCharts 事件类
+ *
+ * 提供事件监听、删除和触发功能
+ */
+export class uChartsEvent {
+  /** 事件存储对象 */
+  events: EventMap;
+
+  constructor() {
+    this.events = {};
+  }
+
+  /**
+   * 添加事件监听器
+   * @param type - 事件类型
+   * @param listener - 事件监听器函数
+   */
+  addEventListener(type: string, listener: EventListener): void {
+    this.events[type] = this.events[type] || [];
+    this.events[type].push(listener);
+  }
+
+  /**
+   * 删除事件类型的所有监听器
+   * @param type - 事件类型
+   */
+  delEventListener(type: string): void {
+    this.events[type] = [];
+  }
+
+  /**
+   * 触发事件,调用该类型的所有监听器
+   * @param params - 事件类型和其他参数
+   */
+  trigger(...params: any[]): void {
+    const type = params[0];
+    const eventParams = params.slice(1);
+    if (!!this.events[type]) {
+      this.events[type].forEach(function(listener) {
+        try {
+          listener.apply(null, eventParams);
+        } catch (e) {
+          // console.log('[uCharts] ' + e);
+        }
+      });
+    }
+  }
+}
+
+export default uChartsEvent;

+ 1117 - 0
mini-ui-packages/mini-charts/src/lib/charts/u-charts.ts

@@ -0,0 +1,1117 @@
+/**
+ * uCharts 主类
+ *
+ * 从 u-charts 核心库搬迁的核心图表类
+ * 添加完整类型注解
+ */
+
+import { uChartsEvent } from './u-charts-event';
+import { config as defaultConfig, assign, util, UChartsConfig } from '../config';
+import { getH5Offset, getTouches as getTouchesUtil, DOMEvent } from '../utils/misc';
+import { measureText } from '../utils/text';
+import { calValidDistance, UChartInstance as CoordinateUChartInstance } from '../utils/coordinate';
+import * as helperFunctions from '../helper-functions';
+import * as dataProcessing from '../data-processing';
+import * as chartsData from '../charts-data';
+import * as renderers from '../renderers';
+import { drawCharts } from '../draw-controllers';
+
+// 导入所有需要的函数
+const {
+  // Finders
+  findPieChartCurrentIndex,
+  findRoseChartCurrentIndex,
+  findRadarChartCurrentIndex,
+  findFunnelChartCurrentIndex,
+  findMapChartCurrentIndex,
+  findWordChartCurrentIndex,
+  findBarChartCurrentIndex,
+  findCurrentIndex,
+  findLegendIndex,
+  // Data helpers
+  getSeriesDataItem,
+  getCandleToolTipData,
+  // Legend helpers
+  calLegendData,
+  getPieTextMaxLength
+} = helperFunctions;
+
+const {
+  fixPieSeries,
+  fillSeries,
+  fillCustomColor,
+  getDataRange,
+  dataCombine,
+  dataCombineStack,
+  calXAxisData,
+  getXAxisPoints,
+  calYAxisData,
+  calCategoriesData,
+  getToolTipData,
+  getMixToolTipData
+} = dataProcessing;
+
+const {
+  getDataPoints,
+  getLineDataPoints,
+  getColumnDataPoints,
+  getCandleDataPoints,
+  getMountDataPoints,
+  getBarDataPoints,
+  getStackDataPoints,
+  getBarStackDataPoints,
+  getPieDataPoints,
+  getRoseDataPoints,
+  getRadarDataPoints,
+  getGaugeDataPoints,
+  getGaugeArcbarDataPoints,
+  getArcbarDataPoints,
+  getGaugeAxisPoints,
+  getFunnelDataPoints
+} = chartsData;
+
+const {
+  drawXAxis,
+  drawYAxisGrid,
+  drawYAxis,
+  drawLegend,
+  drawGaugeLabel,
+  drawRadarLabel,
+  drawColumnDataPoints,
+  drawBarDataPoints,
+  drawMountDataPoints,
+  drawLineDataPoints,
+  drawAreaDataPoints,
+  drawCandleDataPoints,
+  drawPieDataPoints,
+  drawRadarDataPoints,
+  drawMapDataPoints,
+  drawFunnelDataPoints,
+  drawWordCloudDataPoints,
+  drawMixDataPoints,
+  drawScatterDataPoints,
+  drawBubbleDataPoints
+} = renderers;
+
+// 简化函数调用别名
+const getTouches = getTouchesUtil;
+
+/**
+ * Canvas 上下文接口
+ * 使用 ExtendedCanvasContext,兼容 Taro 跨平台环境
+ */
+export type CanvasContext = import('../../types').ExtendedCanvasContext;
+
+/**
+ * 图表标题配置
+ */
+export interface ChartsTitle {
+  name?: string;
+  fontSize?: number;
+  color?: string;
+  offsetX?: number;
+  offsetY?: number;
+  [key: string]: any;
+}
+
+/**
+ * Y轴配置
+ */
+export interface YAxisConfig {
+  data?: any[];
+  showTitle?: boolean;
+  disabled?: boolean;
+  disableGrid?: boolean;
+  gridSet?: string;
+  splitNumber?: number;
+  gridType?: string;
+  dashLength?: number;
+  gridColor?: string;
+  padding?: number;
+  fontColor?: string;
+  min?: number;
+  max?: number;
+  format?: (val: number) => string | number;
+  [key: string]: any;
+}
+
+/**
+ * X轴配置
+ */
+export interface XAxisConfig {
+  rotateLabel?: boolean;
+  rotateAngle?: number;
+  disabled?: boolean;
+  disableGrid?: boolean;
+  splitNumber?: number;
+  calibration?: boolean;
+  fontColor?: string;
+  fontSize?: number;
+  lineHeight?: number;
+  marginTop?: number;
+  gridType?: string;
+  dashLength?: number;
+  scrollAlign?: string;
+  scrollPosition?: string;
+  boundaryGap?: string;
+  axisLine?: boolean;
+  axisLineColor?: string;
+  titleFontSize?: number;
+  titleOffsetY?: number;
+  titleOffsetX?: number;
+  titleFontColor?: string;
+  itemCount?: number;
+  enableScroll?: boolean;
+  [key: string]: any;
+}
+
+/**
+ * 图例配置
+ */
+export interface LegendConfig {
+  show?: boolean;
+  position?: string;
+  float?: string;
+  backgroundColor?: string;
+  borderColor?: string;
+  borderWidth?: number;
+  padding?: number;
+  margin?: number;
+  itemGap?: number;
+  fontSize?: number;
+  lineHeight?: number;
+  fontColor?: string;
+  formatter?: any;
+  hiddenColor?: string;
+  [key: string]: any;
+}
+
+/**
+ * 额外配置
+ */
+export interface ExtraConfig {
+  tooltip?: {
+    legendShape?: string;
+    [key: string]: any;
+  };
+  pie?: {
+    labelWidth?: number;
+    [key: string]: any;
+  };
+  ring?: {
+    labelWidth?: number;
+    [key: string]: any;
+  };
+  rose?: {
+    labelWidth?: number;
+    [key: string]: any;
+  };
+  candle?: {
+    [key: string]: any;
+  };
+  [key: string]: any;
+}
+
+/**
+ * 图表配置项
+ */
+export interface ChartsConfig {
+  pixelRatio?: number;
+  fontSize?: number;
+  fontColor?: string;
+  background?: string;
+  title?: ChartsTitle;
+  subtitle?: ChartsTitle;
+  duration?: number;
+  yAxis?: YAxisConfig;
+  xAxis?: XAxisConfig;
+  legend?: LegendConfig;
+  extra?: ExtraConfig;
+  rotate?: boolean;
+  animation?: boolean;
+  canvas2d?: boolean;
+  width?: number;
+  height?: number;
+  padding?: number[];
+  context?: CanvasContext;
+  type?: string;
+  categories?: any[];
+  series?: any[];
+  seriesMA?: any[];
+  color?: any[];
+  dataLabel?: boolean;
+  enableScroll?: boolean;
+  touchMoveLimit?: number;
+  area?: number[];
+  chartData?: any;
+  updateData?: boolean;
+  _scrollDistance_?: number;
+  _series_?: any[];
+  pix?: number;
+  [key: string]: any;
+}
+
+/**
+ * 滚动选项
+ */
+export interface ScrollOption {
+  currentOffset: number;
+  startTouchX: number;
+  distance: number;
+  lastMoveTime: number;
+  moveCount?: number;
+  moveCurrent1?: number;
+  moveCurrent2?: number;
+}
+
+/**
+ * 触摸事件接口
+ */
+export interface TouchEvent {
+  changedTouches?: TouchPoint[];
+  mp?: {
+    changedTouches?: TouchPoint[];
+  };
+  [key: string]: any;
+}
+
+/**
+ * 触摸点接口
+ */
+export interface TouchPoint {
+  x?: number;
+  y?: number;
+  clientX?: number;
+  clientY?: number;
+  [key: string]: any;
+}
+
+/**
+ * 提示框选项
+ */
+export interface ToolTipOption {
+  textList?: any[];
+  offset?: {
+    x: number;
+    y: number;
+  };
+  index?: number | number[];
+  group?: string[];
+  formatter?: (item: any, category: any, index: number, opts: any) => string;
+  [key: string]: any;
+}
+
+/**
+ * uCharts 主类
+ *
+ * 核心图表类,提供所有图表操作方法
+ */
+export class uCharts extends uChartsEvent {
+  /** 图表配置 */
+  opts: ChartsConfig;
+
+  /** Canvas 上下文 */
+  context: CanvasContext;
+
+  /** 内部配置 */
+  config: any;
+
+  /** 事件处理器 */
+  uevent: uChartsEvent;
+
+  /** 滚动选项 */
+  scrollOption: ScrollOption;
+
+  /** 动画实例 */
+  animationInstance?: any;
+
+  /**
+   * 构造函数
+   * @param opts - 图表配置项
+   */
+  constructor(opts: ChartsConfig) {
+    super();
+
+    // 确保必需的属性存在
+    if (!opts.height) opts.height = 300;
+    if (!opts.width) opts.width = 300;
+    if (!opts.pix) opts.pix = opts.pixelRatio ? opts.pixelRatio : 1;
+    opts.fontSize = opts.fontSize ? opts.fontSize : 13;
+    opts.fontColor = opts.fontColor ? opts.fontColor : defaultConfig.fontColor;
+    if (opts.background == "" || opts.background == "none") {
+      opts.background = "#FFFFFF"
+    }
+    opts.title = assign({}, opts.title || {});
+    opts.subtitle = assign({}, opts.subtitle || {});
+    opts.duration = opts.duration ? opts.duration : 1000;
+    opts.yAxis = assign({}, {
+      data: [],
+      showTitle: false,
+      disabled: false,
+      disableGrid: false,
+      gridSet: 'number',
+      splitNumber: 5,
+      gridType: 'solid',
+      dashLength: 4 * opts.pix,
+      gridColor: '#cccccc',
+      padding: 10,
+      fontColor: '#666666'
+    }, opts.yAxis || {});
+    opts.xAxis = assign({}, {
+      rotateLabel: false,
+      rotateAngle: 45,
+      disabled: false,
+      disableGrid: false,
+      splitNumber: 5,
+      calibration: false,
+      fontColor: '#666666',
+      fontSize: 13,
+      lineHeight: 20,
+      marginTop: 0,
+      gridType: 'solid',
+      dashLength: 4,
+      scrollAlign: 'left',
+      boundaryGap: 'center',
+      axisLine: true,
+      axisLineColor: '#cccccc',
+      titleFontSize: 13,
+      titleOffsetY: 0,
+      titleOffsetX: 0,
+      titleFontColor: '#666666'
+    }, opts.xAxis || {});
+    opts.xAxis.scrollPosition = opts.xAxis.scrollAlign;
+    opts.legend = assign({}, {
+      show: true,
+      position: 'bottom',
+      float: 'center',
+      backgroundColor: 'rgba(0,0,0,0)',
+      borderColor: 'rgba(0,0,0,0)',
+      borderWidth: 0,
+      padding: 5,
+      margin: 5,
+      itemGap: 10,
+      fontSize: opts.fontSize,
+      lineHeight: opts.fontSize,
+      fontColor: opts.fontColor,
+      formatter: {},
+      hiddenColor: '#CECECE'
+    }, opts.legend || {});
+    opts.extra = {
+      tooltip: {
+        legendShape: 'auto'
+      },
+      ...(opts.extra || {})
+    } as ExtraConfig;
+    opts.rotate = opts.rotate ? true : false;
+    opts.animation = opts.animation ? true : false;
+    opts.rotate = opts.rotate ? true : false;
+    opts.canvas2d = opts.canvas2d ? true : false;
+
+    let config: UChartsConfig = { ...defaultConfig };
+    config.color = opts.color ? opts.color : config.color;
+    if (opts.type == 'pie') {
+      const pieLabelWidth = opts.extra?.pie?.labelWidth ?? 0;
+      config.pieChartLinePadding = opts.dataLabel === false ? 0 : pieLabelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+    }
+    if (opts.type == 'ring') {
+      const ringLabelWidth = opts.extra?.ring?.labelWidth ?? 0;
+      config.pieChartLinePadding = opts.dataLabel === false ? 0 : ringLabelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+    }
+    if (opts.type == 'rose') {
+      const roseLabelWidth = opts.extra?.rose?.labelWidth ?? 0;
+      config.pieChartLinePadding = opts.dataLabel === false ? 0 : roseLabelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+    }
+    config.pieChartTextPadding = opts.dataLabel === false ? 0 : config.pieChartTextPadding * opts.pix;
+
+    // 屏幕旋转
+    config.rotate = opts.rotate;
+    if (opts.rotate) {
+      let tempWidth = opts.width;
+      let tempHeight = opts.height;
+      opts.width = tempHeight;
+      opts.height = tempWidth;
+    }
+
+    // 适配高分屏
+    opts.padding = opts.padding ? opts.padding : config.padding;
+    config.yAxisWidth = config.yAxisWidth * opts.pix;
+    config.fontSize = opts.fontSize * opts.pix;
+    config.titleFontSize = config.titleFontSize * opts.pix;
+    config.subtitleFontSize = config.subtitleFontSize * opts.pix;
+    if (!opts.context) {
+      throw new Error('[uCharts] 未获取到context!注意:v2.0版本后,需要自行获取canvas的绘图上下文并传入opts.context!');
+    }
+    this.context = opts.context;
+    if (!this.context.setTextAlign) {
+      this.context.setStrokeStyle = function(e: string) {
+        return this.strokeStyle = e;
+      }
+      this.context.setLineWidth = function(e: number) {
+        return this.lineWidth = e;
+      }
+      this.context.setLineCap = function(e: string) {
+        return this.lineCap = e;
+      }
+      this.context.setFontSize = function(e: number) {
+        return this.font = e + "px sans-serif";
+      }
+      this.context.setFillStyle = function(e: string) {
+        return this.fillStyle = e;
+      }
+      this.context.setTextAlign = function(e: string) {
+        return this.textAlign = e;
+      }
+      this.context.setTextBaseline = function(e: string) {
+        return this.textBaseline = e;
+      }
+      this.context.setShadow = function(offsetX: number, offsetY: number, blur: number, color: string) {
+        this.shadowColor = color;
+        this.shadowOffsetX = offsetX;
+        this.shadowOffsetY = offsetY;
+        this.shadowBlur = blur;
+      }
+      this.context.draw = function() {}
+    }
+    // 兼容NVUEsetLineDash
+    if (!this.context.setLineDash) {
+      this.context.setLineDash = function(e: number[]) {}
+    }
+    opts.chartData = {};
+    this.uevent = new uChartsEvent();
+    this.scrollOption = {
+      currentOffset: 0,
+      startTouchX: 0,
+      distance: 0,
+      lastMoveTime: 0
+    };
+    this.opts = opts;
+    this.config = config;
+    drawCharts.call(this, opts.type || '', opts as any, config, this.context);
+  }
+
+  /**
+   * 更新图表数据
+   * @param data - 新的数据
+   */
+  updateData(data: any = {}): void {
+    this.opts = assign({}, this.opts, data);
+    this.opts.updateData = true;
+    let scrollPosition = data.scrollPosition || 'current';
+    switch (scrollPosition) {
+      case 'current':
+        this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+        break;
+      case 'left':
+        this.opts._scrollDistance_ = 0;
+        this.scrollOption = {
+          currentOffset: 0,
+          startTouchX: 0,
+          distance: 0,
+          lastMoveTime: 0
+        };
+        break;
+      case 'right':
+        let _calYAxisData = calYAxisData(this.opts.series || [], this.opts as any, this.config, this.context);
+        let yAxisWidth = _calYAxisData.yAxisWidth;
+        this.config.yAxisWidth = yAxisWidth;
+        let offsetLeft = 0;
+        let _getXAxisPoints0 = getXAxisPoints(this.opts.categories || [], this.opts as any, this.config);
+        let xAxisPoints = _getXAxisPoints0.xAxisPoints;
+        let startX = _getXAxisPoints0.startX;
+        let endX = _getXAxisPoints0.endX;
+        let eachSpacing = _getXAxisPoints0.eachSpacing;
+        let totalWidth = eachSpacing * (xAxisPoints.length - 1);
+        let screenWidth = endX - startX;
+        offsetLeft = screenWidth - totalWidth;
+        this.scrollOption = {
+          currentOffset: offsetLeft,
+          startTouchX: offsetLeft,
+          distance: 0,
+          lastMoveTime: 0
+        };
+        this.opts._scrollDistance_ = offsetLeft;
+        break;
+    }
+    drawCharts.call(this, this.opts.type || '', this.opts as any, this.config, this.context);
+  }
+
+  /**
+   * 缩放图表
+   * @param val - 缩放配置
+   */
+  zoom(val: any = this.opts.xAxis?.itemCount): void {
+    if (this.opts.enableScroll !== true) {
+      console.log('[uCharts] 请启用滚动条后使用')
+      return;
+    }
+    // 当前屏幕中间点
+    if (!this.opts.chartData || !this.opts.chartData.eachSpacing) {
+      console.log('[uCharts] 图表数据未初始化')
+      return;
+    }
+    let centerPoint = Math.round(Math.abs(this.scrollOption.currentOffset) / this.opts.chartData.eachSpacing) + Math.round((this.opts.xAxis?.itemCount || 0) / 2);
+    this.opts.animation = false;
+    if (!this.opts.xAxis) return;
+    this.opts.xAxis.itemCount = val.itemCount;
+    // 重新计算x轴偏移距离
+    let _calYAxisData = calYAxisData(this.opts.series || [], this.opts as any, this.config, this.context);
+    let yAxisWidth = _calYAxisData.yAxisWidth;
+    this.config.yAxisWidth = yAxisWidth;
+    let offsetLeft = 0;
+    let _getXAxisPoints0 = getXAxisPoints(this.opts.categories || [], this.opts as any, this.config);
+    let xAxisPoints = _getXAxisPoints0.xAxisPoints;
+    let startX = _getXAxisPoints0.startX;
+    let endX = _getXAxisPoints0.endX;
+    let eachSpacing = _getXAxisPoints0.eachSpacing;
+    let centerLeft = eachSpacing * centerPoint;
+    let screenWidth = endX - startX;
+    let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+    offsetLeft = screenWidth / 2 - centerLeft;
+    if (offsetLeft > 0) {
+      offsetLeft = 0;
+    }
+    if (offsetLeft < MaxLeft) {
+      offsetLeft = MaxLeft;
+    }
+    this.scrollOption = {
+      currentOffset: offsetLeft,
+      startTouchX: 0,
+      distance: 0,
+      lastMoveTime: 0
+    };
+    const self = this as unknown as CoordinateUChartInstance;
+    self.scrollOption = { position: this.scrollOption.currentOffset };
+    calValidDistance(self, offsetLeft, this.opts.chartData || { eachSpacing: 0 }, this.config as any, this.opts as any);
+    this.opts._scrollDistance_ = offsetLeft;
+    drawCharts.call(this, this.opts.type || '', this.opts as any, this.config, this.context);
+  }
+
+  /**
+   * 双指缩放
+   * @param e - 触摸事件
+   */
+  dobuleZoom(e: TouchEvent): void {
+    if (this.opts.enableScroll !== true) {
+      console.log('[uCharts] 请启用滚动条后使用')
+      return;
+    }
+    const tcs = e.changedTouches;
+    if (!tcs || tcs.length < 2) {
+      return;
+    }
+    for (let i = 0; i < tcs.length; i++) {
+      tcs[i].x = tcs[i].x ? tcs[i].x : tcs[i].clientX;
+      tcs[i].y = tcs[i].y ? tcs[i].y : tcs[i].clientY;
+    }
+    const ntcs = [getTouches(tcs[0], this.opts as any, e as any), getTouches(tcs[1], this.opts as any, e as any)];
+    const xlength = Math.abs((ntcs[0].x || 0) - (ntcs[1].x || 0));
+    // 记录初始的两指之间的数据
+    if (!this.scrollOption.moveCount) {
+      if (!this.opts.area || !this.opts.height) return;
+      let cts0 = { changedTouches: [{ x: tcs[0].x || 0, y: this.opts.area[0] / (this.opts.pix || 1) + 2 }] };
+      let cts1 = { changedTouches: [{ x: tcs[1].x || 0, y: this.opts.area[0] / (this.opts.pix || 1) + 2 }] };
+      if (this.opts.rotate) {
+        cts0 = { changedTouches: [{ x: this.opts.height / (this.opts.pix || 1) - this.opts.area[0] / (this.opts.pix || 1) - 2, y: tcs[0].y || 0 }] };
+        cts1 = { changedTouches: [{ x: this.opts.height / (this.opts.pix || 1) - this.opts.area[0] / (this.opts.pix || 1) - 2, y: tcs[1].y || 0 }] };
+      }
+      const moveCurrent1 = this.getCurrentDataIndex(cts0).index;
+      const moveCurrent2 = this.getCurrentDataIndex(cts1).index;
+      const moveCount = Math.abs(moveCurrent1 - moveCurrent2);
+      this.scrollOption.moveCount = moveCount;
+      this.scrollOption.moveCurrent1 = Math.min(moveCurrent1, moveCurrent2);
+      this.scrollOption.moveCurrent2 = Math.max(moveCurrent1, moveCurrent2);
+      return;
+    }
+
+    let currentEachSpacing = xlength / (this.scrollOption.moveCount || 1);
+    if (!this.opts.area || !this.opts.width || !this.opts.xAxis) return;
+    let itemCount = (this.opts.width - this.opts.area[1] - this.opts.area[3]) / currentEachSpacing;
+    itemCount = itemCount <= 2 ? 2 : itemCount;
+    if (!this.opts.categories) return;
+    itemCount = itemCount >= this.opts.categories.length ? this.opts.categories.length : itemCount;
+    this.opts.animation = false;
+    this.opts.xAxis.itemCount = itemCount;
+    // 重新计算滚动条偏移距离
+    let offsetLeft = 0;
+    let _getXAxisPoints0 = getXAxisPoints(this.opts.categories || [], this.opts as any, this.config);
+    let xAxisPoints = _getXAxisPoints0.xAxisPoints;
+    let startX = _getXAxisPoints0.startX;
+    let endX = _getXAxisPoints0.endX;
+    let eachSpacing = _getXAxisPoints0.eachSpacing;
+    let currentLeft = eachSpacing * (this.scrollOption.moveCurrent1 || 0);
+    let screenWidth = endX - startX;
+    let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+    offsetLeft = -currentLeft + Math.min(ntcs[0].x || 0, ntcs[1].x || 0) - (this.opts.area[3] || 0) - eachSpacing;
+    if (offsetLeft > 0) {
+      offsetLeft = 0;
+    }
+    if (offsetLeft < MaxLeft) {
+      offsetLeft = MaxLeft;
+    }
+    this.scrollOption.currentOffset = offsetLeft;
+    this.scrollOption.startTouchX = 0;
+    this.scrollOption.distance = 0;
+    const self = this as unknown as CoordinateUChartInstance;
+    self.scrollOption = { position: this.scrollOption.currentOffset };
+    calValidDistance(self, offsetLeft, this.opts.chartData || { eachSpacing: 0 }, this.config as any, this.opts as any);
+    this.opts._scrollDistance_ = offsetLeft;
+    drawCharts.call(this, this.opts.type || '', this.opts as any, this.config, this.context);
+  }
+
+  /**
+   * 停止动画
+   */
+  stopAnimation(): void {
+    this.animationInstance && this.animationInstance.stop();
+  }
+
+  /**
+   * 获取当前数据索引
+   * @param e - 触摸事件
+   * @returns 当前数据索引
+   */
+  getCurrentDataIndex(e: TouchEvent): any {
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (touches) {
+      let _touches$ = getTouches(touches, this.opts as any, e as any);
+      if (!this.opts.chartData) return { index: -1 };
+      if (this.opts.type === 'pie' || this.opts.type === 'ring') {
+        return findPieChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.pieData || [], this.opts as any);
+      } else if (this.opts.type === 'rose') {
+        return findRoseChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.pieData || [], this.opts as any);
+      } else if (this.opts.type === 'radar') {
+        return findRadarChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.radarData || [], this.opts.categories?.length || 0);
+      } else if (this.opts.type === 'funnel') {
+        return findFunnelChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.funnelData || []);
+      } else if (this.opts.type === 'map') {
+        return findMapChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts as any);
+      } else if (this.opts.type === 'word') {
+        return findWordChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.wordCloudData || []);
+      } else if (this.opts.type === 'bar') {
+        return findBarChartCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.calPoints || [], this.opts as any, this.config, Math.abs(this.scrollOption.currentOffset));
+      } else {
+        return findCurrentIndex({
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        }, this.opts.chartData.calPoints || [], this.opts as any, this.config, Math.abs(this.scrollOption.currentOffset));
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * 获取图例数据索引
+   * @param e - 触摸事件
+   * @returns 图例索引
+   */
+  getLegendDataIndex(e: TouchEvent): number {
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (touches) {
+      let _touches$ = getTouches(touches, this.opts as any, e as any);
+      return findLegendIndex({
+        x: _touches$.x || 0,
+        y: _touches$.y || 0
+      }, this.opts.chartData?.legendData || [], this.opts as any);
+    }
+    return -1;
+  }
+
+  /**
+   * 触摸图例事件
+   * @param e - 触摸事件
+   * @param option - 选项
+   */
+  touchLegend(e: TouchEvent, option: any = {}): void {
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (touches) {
+      let _touches$ = getTouches(touches, this.opts as any, e as any);
+      let index = this.getLegendDataIndex(e);
+      if (index >= 0) {
+        if (!this.opts.series) return;
+        if (this.opts.type == 'candle') {
+          if (!this.opts.seriesMA) return;
+          this.opts.seriesMA[index].show = !this.opts.seriesMA[index].show;
+        } else {
+          this.opts.series[index].show = !this.opts.series[index].show;
+        }
+        this.opts.animation = option.animation ? true : false;
+        this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+        drawCharts.call(this, this.opts.type || '', this.opts as any, this.config, this.context);
+      }
+    }
+  }
+
+  /**
+   * 显示提示框
+   * @param e - 触摸事件
+   * @param option - 提示框选项
+   */
+  showToolTip(e: TouchEvent, option: ToolTipOption = {}): void {
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (!touches) {
+      console.log("[uCharts] 未获取到event坐标信息");
+      return;
+    }
+    let _touches$ = getTouches(touches, this.opts as any, e as any);
+    let currentOffset = this.scrollOption.currentOffset;
+    let opts = {
+      ...this.opts,
+      _scrollDistance_: currentOffset,
+      animation: false
+    } as ChartsConfig;
+    if (this.opts.type === 'line' || this.opts.type === 'area' || this.opts.type === 'column' || this.opts.type === 'scatter' || this.opts.type === 'bubble') {
+      let current = this.getCurrentDataIndex(e);
+      let index = option.index == undefined ? current.index : option.index;
+      if (index > -1 || index.length > 0) {
+        let seriesData = getSeriesDataItem(this.opts.series || [], index, current.group);
+        if (seriesData.length !== 0) {
+          let _getToolTipData = getToolTipData(seriesData as any, this.opts as any, index, current.group, this.opts.categories || [], option);
+          let textList = _getToolTipData.textList;
+          let offset = _getToolTipData.offset;
+          offset.y = _touches$.y || 0;
+          (opts as any).tooltip = {
+            textList: option.textList !== undefined ? option.textList : textList,
+            offset: option.offset !== undefined ? option.offset : offset,
+            option: option,
+            index: index,
+            group: current.group
+          };
+        }
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'mount') {
+      let index = option.index == undefined ? this.getCurrentDataIndex(e).index : option.index;
+      if (index > -1) {
+        let opts = { ...this.opts, animation: false } as ChartsConfig;
+        let seriesData = { ...opts._series_?.[index] || {} };
+        let textList = [{
+          text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : (seriesData.name || '') + ': ' + (seriesData.data || ''),
+          color: seriesData.color || '',
+          legendShape: this.opts.extra?.tooltip?.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra?.tooltip?.legendShape
+        }];
+        let offset = {
+          x: opts.chartData?.calPoints?.[index]?.x || 0,
+          y: _touches$.y || 0
+        };
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'bar') {
+      let current = this.getCurrentDataIndex(e);
+      let index = option.index == undefined ? current.index : option.index;
+      if (index > -1 || index.length > 0) {
+        let seriesData = getSeriesDataItem(this.opts.series || [], index, current.group);
+        if (seriesData.length !== 0) {
+          let _getToolTipData = getToolTipData(seriesData as any, this.opts as any, index, current.group, this.opts.categories || [], option);
+          let textList = _getToolTipData.textList;
+          let offset = _getToolTipData.offset;
+          offset.x = _touches$.x || 0;
+          (opts as any).tooltip = {
+            textList: option.textList !== undefined ? option.textList : textList,
+            offset: option.offset !== undefined ? option.offset : offset,
+            option: option,
+            index: index
+          };
+        }
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'mix') {
+      let current = this.getCurrentDataIndex(e);
+      let index = option.index == undefined ? current.index : option.index;
+      if (index > -1) {
+        let currentOffset = this.scrollOption.currentOffset;
+        let opts = {
+          ...this.opts,
+          _scrollDistance_: currentOffset,
+          animation: false
+        } as ChartsConfig;
+        let seriesData = getSeriesDataItem(this.opts.series || [], index, []);
+        if (seriesData.length !== 0) {
+          let _getMixToolTipData = getMixToolTipData(seriesData as any, this.opts as any, index, this.opts.categories || [], option);
+          let textList = _getMixToolTipData.textList;
+          let offset = _getMixToolTipData.offset;
+          offset.y = _touches$.y || 0;
+          (opts as any).tooltip = {
+            textList: option.textList ? option.textList : textList,
+            offset: option.offset !== undefined ? option.offset : offset,
+            option: option,
+            index: index
+          };
+        }
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'candle') {
+      let current = this.getCurrentDataIndex(e);
+      let index = option.index == undefined ? current.index : option.index;
+      if (index > -1) {
+        let currentOffset = this.scrollOption.currentOffset;
+        let opts = {
+          ...this.opts,
+          _scrollDistance_: currentOffset,
+          animation: false
+        } as ChartsConfig;
+        let seriesData = getSeriesDataItem(this.opts.series || [], index, []);
+        if (seriesData.length !== 0) {
+          let _getToolTipData = getCandleToolTipData(this.opts.series?.[0]?.data || [], seriesData, this.opts as any, index, this.opts.categories || [], this.opts.extra?.candle, option);
+          let textList = _getToolTipData.textList;
+          let offset = _getToolTipData.offset;
+          offset.y = _touches$.y || 0;
+          (opts as any).tooltip = {
+            textList: option.textList ? option.textList : textList,
+            offset: option.offset !== undefined ? option.offset : offset,
+            option: option,
+            index: index
+          };
+        }
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'pie' || this.opts.type === 'ring' || this.opts.type === 'rose' || this.opts.type === 'funnel') {
+      let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+      if (index > -1) {
+        let opts = { ...this.opts, animation: false } as ChartsConfig;
+        let seriesData = { ...opts._series_?.[index] || {} };
+        let textList = [{
+          text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : (seriesData.name || '') + ': ' + (seriesData.data || ''),
+          color: seriesData.color || '',
+          legendShape: this.opts.extra?.tooltip?.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra?.tooltip?.legendShape
+        }];
+        let offset = {
+          x: _touches$.x,
+          y: _touches$.y
+        };
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'map') {
+      let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+      if (index > -1) {
+        let opts = { ...this.opts, animation: false } as ChartsConfig;
+        if (!this.opts.series) return;
+        let seriesData = { ...this.opts.series[index] };
+        seriesData.name = (seriesData as any).properties?.name || seriesData.name || '';
+        let textList = [{
+          text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : (seriesData.name || ''),
+          color: seriesData.color || '',
+          legendShape: this.opts.extra?.tooltip?.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra?.tooltip?.legendShape
+        }];
+        let offset = {
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        };
+        (opts as any).tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+      opts.updateData = false;
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'word') {
+      let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+      if (index > -1) {
+        let opts = { ...this.opts, animation: false } as ChartsConfig;
+        if (!this.opts.series) return;
+        let seriesData = { ...this.opts.series[index] };
+        let textList = [{
+          text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : (seriesData.name || ''),
+          color: seriesData.color || '',
+          legendShape: this.opts.extra?.tooltip?.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra?.tooltip?.legendShape
+        }];
+        let offset = {
+          x: _touches$.x || 0,
+          y: _touches$.y || 0
+        };
+        (opts as any).tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+      opts.updateData = false;
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+    if (this.opts.type === 'radar') {
+      let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+      if (index > -1) {
+        let opts = { ...this.opts, animation: false } as ChartsConfig;
+        let seriesData = getSeriesDataItem(this.opts.series || [], index, []);
+        if (seriesData.length !== 0) {
+          let textList = seriesData.map((item) => {
+            return {
+              text: option.formatter ? option.formatter(item, this.opts.categories?.[index], index, this.opts) : (item.name || '') + ': ' + (item.data || ''),
+              color: item.color || '',
+              legendShape: this.opts.extra?.tooltip?.legendShape == 'auto' ? item.legendShape : this.opts.extra?.tooltip?.legendShape
+            };
+          });
+          let offset = {
+            x: _touches$.x || 0,
+            y: _touches$.y || 0
+          };
+          (opts as any).tooltip = {
+            textList: option.textList ? option.textList : textList,
+            offset: option.offset !== undefined ? option.offset : offset,
+            option: option,
+            index: index
+          };
+        }
+      }
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+    }
+  }
+
+  /**
+   * 平移图表
+   * @param distance - 平移距离
+   */
+  translate(distance: number): void {
+    this.scrollOption = {
+      currentOffset: distance,
+      startTouchX: distance,
+      distance: 0,
+      lastMoveTime: 0
+    };
+    let opts = {
+      ...this.opts,
+      _scrollDistance_: distance,
+      animation: false
+    } as ChartsConfig;
+    drawCharts.call(this, this.opts.type || '', opts as any, this.config, this.context);
+  }
+
+  /**
+   * 滚动开始事件
+   * @param e - 触摸事件
+   */
+  scrollStart(e: TouchEvent): void {
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (touches && this.opts.enableScroll === true) {
+      let _touches$ = getTouches(touches, this.opts as any, e as any);
+      this.scrollOption.startTouchX = _touches$.x || 0;
+    }
+  }
+
+  /**
+   * 滚动事件
+   * @param e - 触摸事件
+   * @returns 当前偏移量
+   */
+  scroll(e: TouchEvent): number {
+    if (this.scrollOption.lastMoveTime === 0) {
+      this.scrollOption.lastMoveTime = Date.now();
+    }
+    let Limit = this.opts.touchMoveLimit || 60;
+    let currMoveTime = Date.now();
+    let duration = currMoveTime - this.scrollOption.lastMoveTime;
+    if (duration < Math.floor(1000 / Limit)) return 0;
+    if (this.scrollOption.startTouchX == 0) return 0;
+    this.scrollOption.lastMoveTime = currMoveTime;
+    let touches = null;
+    if (e.changedTouches) {
+      touches = e.changedTouches[0];
+    } else if (e.mp && e.mp.changedTouches) {
+      touches = e.mp.changedTouches[0];
+    }
+    if (touches && this.opts.enableScroll === true) {
+      let _touches$ = getTouches(touches, this.opts as any, e as any);
+      let _distance;
+      _distance = (_touches$.x || 0) - this.scrollOption.startTouchX;
+      let currentOffset = this.scrollOption.currentOffset;
+      const self = this as unknown as CoordinateUChartInstance;
+      self.scrollOption = { position: this.scrollOption.currentOffset };
+      let validDistance = calValidDistance(self, currentOffset + _distance, this.opts.chartData || { eachSpacing: 0 }, this.config as any, this.opts as any);
+      this.scrollOption.distance = _distance = validDistance - currentOffset;
+      let opts = {
+        ...this.opts,
+        _scrollDistance_: currentOffset + _distance,
+        animation: false
+      } as ChartsConfig;
+      this.opts = opts;
+      drawCharts.call(this, opts.type || '', opts as any, this.config, this.context);
+      return currentOffset + _distance;
+    }
+    return 0;
+  }
+
+  /**
+   * 滚动结束事件
+   * @param e - 触摸事件
+   */
+  scrollEnd(e: TouchEvent): void {
+    if (this.opts.enableScroll === true) {
+      let _scrollOption = this.scrollOption;
+      let currentOffset = _scrollOption.currentOffset;
+      let distance = _scrollOption.distance;
+      this.scrollOption.currentOffset = currentOffset + distance;
+      this.scrollOption.distance = 0;
+      this.scrollOption.moveCount = 0;
+    }
+  }
+}
+
+export default uCharts;

+ 165 - 0
mini-ui-packages/mini-charts/src/lib/config.ts

@@ -0,0 +1,165 @@
+// 配置对象和工具函数
+// 来源: u-charts.ts
+
+/**
+ * uCharts 默认配置接口
+ */
+export interface UChartsConfig {
+  /** 版本号 */
+  version: string;
+  /** Y 轴宽度 */
+  yAxisWidth: number;
+  /** X 轴高度 */
+  xAxisHeight: number;
+  /** 内边距 [上, 右, 下, 左] */
+  padding: [number, number, number, number];
+  /** 是否旋转图表 */
+  rotate: boolean;
+  /** 默认字体大小 */
+  fontSize: number;
+  /** 默认字体颜色 */
+  fontColor: string;
+  /** 数据点形状列表 */
+  dataPointShape: string[];
+  /** 主题颜色列表 */
+  color: string[];
+  /** 渐变色列表 */
+  linearColor: string[];
+  /** 饼图折线间距 */
+  pieChartLinePadding: number;
+  /** 饼图文字间距 */
+  pieChartTextPadding: number;
+  /** 标题字体大小 */
+  titleFontSize: number;
+  /** 副标题字体大小 */
+  subtitleFontSize: number;
+  /** 雷达图标签文字边距 */
+  radarLabelTextMargin: number;
+}
+
+/**
+ * uCharts 默认配置对象
+ */
+export const config: UChartsConfig = {
+  version: 'v2.5.0-20230101',
+  yAxisWidth: 15,
+  xAxisHeight: 22,
+  padding: [10, 10, 10, 10],
+  rotate: false,
+  fontSize: 13,
+  fontColor: '#666666',
+  dataPointShape: ['circle', 'circle', 'circle', 'circle'],
+  color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],
+  linearColor: ['#0EE2F8', '#2BDCA8', '#FA7D8D', '#EB88E2', '#2AE3A0', '#0EE2F8', '#EB88E2', '#6773E3', '#F78A85'],
+  pieChartLinePadding: 15,
+  pieChartTextPadding: 5,
+  titleFontSize: 20,
+  subtitleFontSize: 15,
+  radarLabelTextMargin: 13,
+};
+
+/**
+ * 深度合并对象
+ * @param target - 目标对象
+ * @param varArgs - 源对象数组
+ * @returns 合并后的对象
+ * @throws TypeError 如果 target 为 null 或 undefined
+ */
+export function assign<T>(target: T, ...varArgs: Partial<T>[]): T {
+  if (target == null) {
+    throw new TypeError('[uCharts] Cannot convert undefined or null to object');
+  }
+  if (!varArgs || varArgs.length <= 0) {
+    return target;
+  }
+  // 深度合并对象
+  function deepAssign(obj1: any, obj2: any): any {
+    for (let key in obj2) {
+      obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" ?
+        deepAssign(obj1[key], obj2[key]) : obj1[key] = obj2[key];
+    }
+    return obj1;
+  }
+  varArgs.forEach(val => {
+    target = deepAssign(target, val);
+  });
+  return target;
+}
+
+/**
+ * 坐标点接口
+ */
+export interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * 碰撞对象接口
+ */
+export interface CollisionObject {
+  start: Point;
+  end?: Point;
+  width: number;
+  height: number;
+  center?: Point;
+  area?: {
+    start: Point;
+    end: Point;
+    width: number;
+    height: number;
+  };
+}
+
+/**
+ * 工具函数集合接口
+ */
+export interface Util {
+  /** 保留小数位数 */
+  toFixed(num: number, limit?: number): number | string;
+  /** 判断是否为浮点数 */
+  isFloat(num: number): boolean;
+  /** 判断两个数是否近似相等 */
+  approximatelyEqual(num1: number, num2: number): boolean;
+  /** 判断两个数是否同号 */
+  isSameSign(num1: number, num2: number): boolean;
+  /** 判断两个点是否在相同的 X 坐标区域 */
+  isSameXCoordinateArea(p1: Point, p2: Point): boolean;
+  /** 检测两个对象是否碰撞 */
+  isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean;
+}
+
+/**
+ * 工具函数集合
+ */
+export const util: Util = {
+  toFixed: function toFixed(num: number, limit: number = 2): number | string {
+    limit = limit || 2;
+    if (this.isFloat(num)) {
+      num = Number(num.toFixed(limit));
+    }
+    return num;
+  },
+  isFloat: function isFloat(num: number): boolean {
+    return num % 1 !== 0;
+  },
+  approximatelyEqual: function approximatelyEqual(num1: number, num2: number): boolean {
+    return Math.abs(num1 - num2) < 1e-10;
+  },
+  isSameSign: function isSameSign(num1: number, num2: number): boolean {
+    return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2;
+  },
+  isSameXCoordinateArea: function isSameXCoordinateArea(p1: Point, p2: Point): boolean {
+    return this.isSameSign(p1.x, p2.x);
+  },
+  isCollision: function isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean {
+    obj1.end = {} as Point;
+    obj1.end.x = obj1.start.x + obj1.width;
+    obj1.end.y = obj1.start.y - obj1.height;
+    obj2.end = {} as Point;
+    obj2.end.x = obj2.start.x + obj2.width;
+    obj2.end.y = obj2.start.y - obj2.height;
+    let flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y;
+    return !flag;
+  }
+};

+ 556 - 0
mini-ui-packages/mini-charts/src/lib/data-processing/axis-calculator.ts

@@ -0,0 +1,556 @@
+/**
+ * 坐标轴计算函数
+ *
+ * 从 u-charts 核心库搬迁的坐标轴计算相关函数
+ * 用于计算X轴、Y轴的数据和刻度
+ */
+
+import {
+  dataCombine,
+  dataCombineStack,
+  getDataRange,
+  type SeriesItem,
+  type ChartOptions,
+  type ChartExtraOptions,
+  type BarOptions,
+  type ColumnOptions,
+  type TooltipOptions,
+  type MountOptions,
+  type XAxisOptions,
+  type YAxisOptions,
+  type YAxisDataItem,
+  type ChartData,
+  type UChartsConfig
+} from './series-calculator';
+
+// 重新导出类型以保持向后兼容
+export type {
+  SeriesItem,
+  ChartOptions,
+  ChartExtraOptions,
+  BarOptions,
+  ColumnOptions,
+  TooltipOptions,
+  MountOptions,
+  XAxisOptions,
+  YAxisOptions,
+  YAxisDataItem,
+  ChartData,
+  UChartsConfig
+};
+
+// 坐标轴计算结果类型
+export interface XAxisDataResult {
+  angle: number;
+  xAxisHeight: number;
+  ranges: number[];
+  rangesFormat: string[];
+  xAxisPoints: number[];
+  startX: number;
+  endX: number;
+  eachSpacing: number;
+}
+
+export interface YAxisDataResult {
+  rangesFormat: string[][];
+  ranges: (number[] | string[])[];
+  yAxisWidth: {
+    position: string;
+    width: number;
+  }[];
+}
+
+export interface AxisPointsResult {
+  xAxisPoints: number[];
+  startX: number;
+  endX: number;
+  eachSpacing: number;
+}
+
+// 工具函数
+const util = {
+  toFixed: function toFixed(num: number, limit: number = 2): number | string {
+    if (this.isFloat(num)) {
+      num = Number(num.toFixed(limit));
+    }
+    return num;
+  },
+  isFloat: function isFloat(num: number): boolean {
+    return num % 1 !== 0;
+  }
+};
+
+function assign(target: any, ...sources: any[]): any {
+  if (target == null) {
+    throw new TypeError('[uCharts] Cannot convert undefined or null to object');
+  }
+  const result = { ...target };
+  sources.forEach(val => {
+    if (val != null) {
+      Object.keys(val).forEach(key => {
+        if (val[key] !== undefined) {
+          result[key] = val[key];
+        }
+      });
+    }
+  });
+  return result;
+}
+
+function measureText(text: string | number, fontSize: number, context?: any): number {
+  let width = 0;
+  text = String(text);
+  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {
+    context.setFontSize(fontSize);
+    return context.measureText(text).width;
+  } else {
+    const textArray = text.split('');
+    for (let i = 0; i < textArray.length; i++) {
+      const item = textArray[i];
+      if (/[a-zA-Z]/.test(item)) {
+        width += 7;
+      } else if (/[0-9]/.test(item)) {
+        width += 5.5;
+      } else if (/\./.test(item)) {
+        width += 2.7;
+      } else if (/-/.test(item)) {
+        width += 3.25;
+      } else if (/:/.test(item)) {
+        width += 2.5;
+      } else if (/[\u4e00-\u9fa5]/.test(item)) {
+        width += 10;
+      } else if (/\(|\)/.test(item)) {
+        width += 3.73;
+      } else if (/\s/.test(item)) {
+        width += 2.5;
+      } else if (/%/.test(item)) {
+        width += 8;
+      } else {
+        width += 10;
+      }
+    }
+    return width * fontSize / 10;
+  }
+}
+
+/**
+ * 计算X轴数据
+ * 包括刻度、标签等
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas上下文
+ * @returns X轴数据结果
+ */
+export function calXAxisData(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context?: any
+): XAxisDataResult {
+  const columnstyle = assign({}, {
+    type: ""
+  }, opts.extra?.column || {});
+  const result: XAxisDataResult = {
+    angle: 0,
+    xAxisHeight: (opts.xAxis?.lineHeight || 0) * (opts.pix || 1) + (opts.xAxis?.marginTop || 0) * (opts.pix || 1),
+    ranges: [],
+    rangesFormat: [],
+    xAxisPoints: [],
+    startX: 0,
+    endX: 0,
+    eachSpacing: 0
+  };
+  result.ranges = getXAxisTextList(series, opts, config, columnstyle.type || '');
+  result.rangesFormat = result.ranges.map(function(item) {
+    const numItem = Number(item);
+    return String(util.toFixed(numItem, 2));
+  });
+  const xAxisScaleValues = result.ranges.map(function(item) {
+    const numItem = Number(item);
+    return util.toFixed(numItem, 2);
+  });
+  Object.assign(result, getXAxisPoints(xAxisScaleValues as any, opts, config));
+  const eachSpacing = result.eachSpacing;
+  const textLength = xAxisScaleValues.map(function(item) {
+    return measureText(String(item), (opts.xAxis?.fontSize || 13) * (opts.pix || 1), context);
+  });
+  if (opts.xAxis?.disabled === true) {
+    result.xAxisHeight = 0;
+  }
+  return result;
+}
+
+/**
+ * 获取X轴文本列表
+ * 计算X轴刻度值范围
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param stack - 堆叠类型
+ * @param index - Y轴索引
+ * @returns 刻度值范围数组
+ */
+function getXAxisTextList(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  stack: string = '',
+  index: number = -1
+): number[] {
+  let data: (number | null)[];
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories?.length || 0);
+  } else {
+    data = dataCombine(series);
+  }
+  const sorted: number[] = [];
+  data = data.filter(function(item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        return item !== null;
+      } else {
+        return (item as any).value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.forEach(function(item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        if (opts.type == 'candle') {
+          (item as any).forEach(function(subitem: any) {
+            sorted.push(subitem as number);
+          });
+        } else {
+          sorted.push((item as any)[0] as number);
+        }
+      } else {
+        sorted.push((item as any).value);
+      }
+    } else if (item !== null) {
+      sorted.push(item);
+    }
+  });
+
+  let minData = 0;
+  let maxData = 0;
+  if (sorted.length > 0) {
+    minData = Math.min(...sorted);
+    maxData = Math.max(...sorted);
+  }
+  if (index > -1) {
+    const xAxisData = (opts.xAxis as any)?.data;
+    if (xAxisData && typeof xAxisData[index]?.min === 'number') {
+      minData = Math.min(xAxisData[index].min, minData);
+    }
+    if (xAxisData && typeof xAxisData[index]?.max === 'number') {
+      maxData = Math.max(xAxisData[index].max, maxData);
+    }
+  } else {
+    if (typeof opts.xAxis?.min === 'number') {
+      minData = Math.min(opts.xAxis.min, minData);
+    }
+    if (typeof opts.xAxis?.max === 'number') {
+      maxData = Math.max(opts.xAxis.max, maxData);
+    }
+  }
+  if (minData === maxData) {
+    const rangeSpan = maxData || 10;
+    maxData += rangeSpan;
+  }
+  const minRange = minData;
+  const maxRange = maxData;
+  const splitNumber = opts.xAxis?.splitNumber || 5;
+  const range: number[] = [];
+  const eachRange = (maxRange - minRange) / splitNumber;
+  for (let i = 0; i <= splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range;
+}
+
+/**
+ * 获取X轴数据点坐标
+ *
+ * @param categories - 分类数据
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @returns X轴数据点结果
+ */
+export function getXAxisPoints(
+  categories: (string | number)[],
+  opts: ChartOptions,
+  config: UChartsConfig
+): AxisPointsResult {
+  const spacingValid = (opts.width || 0) - (opts.area?.[1] || 0) - (opts.area?.[3] || 0);
+  const dataCount = opts.enableScroll
+    ? Math.min(opts.xAxis?.itemCount || categories.length, categories.length)
+    : categories.length;
+  const type = opts.type;
+  let modifiedDataCount = dataCount;
+  if ((type == 'line' || type == 'area' || type == 'scatter' || type == 'bubble' || type == 'bar') &&
+      dataCount > 1 &&
+      opts.xAxis?.boundaryGap == 'justify') {
+    modifiedDataCount -= 1;
+  }
+  let widthRatio = 0;
+  if (type == 'mount' &&
+      opts.extra?.mount &&
+      opts.extra.mount.widthRatio &&
+      opts.extra.mount.widthRatio > 1) {
+    if (opts.extra.mount.widthRatio > 2) {
+      (opts.extra.mount.widthRatio as any) = 2;
+    }
+    widthRatio = opts.extra.mount.widthRatio - 1;
+    modifiedDataCount += widthRatio;
+  }
+  const eachSpacing = spacingValid / modifiedDataCount;
+  const xAxisPoints: number[] = [];
+  const startX = opts.area?.[3] || 0;
+  const endX = (opts.width || 0) - (opts.area?.[1] || 0);
+  categories.forEach(function(_item, index) {
+    xAxisPoints.push(startX + widthRatio / 2 * eachSpacing + index * eachSpacing);
+  });
+  if (opts.xAxis?.boundaryGap !== 'justify') {
+    if (opts.enableScroll === true) {
+      xAxisPoints.push(startX + widthRatio * eachSpacing + categories.length * eachSpacing);
+    } else {
+      xAxisPoints.push(endX);
+    }
+  }
+  return {
+    xAxisPoints,
+    startX,
+    endX,
+    eachSpacing
+  };
+}
+
+/**
+ * 计算Y轴数据
+ * 包括刻度、标签等,支持多Y轴
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas上下文
+ * @returns Y轴数据结果
+ */
+export function calYAxisData(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context?: any
+): YAxisDataResult {
+  const columnstyle = assign({}, {
+    type: ""
+  }, opts.extra?.column || {});
+  const YLength = opts.yAxis?.data?.length || 0;
+  const newSeries: SeriesItem[][] = new Array(YLength);
+  if (YLength > 0) {
+    for (let i = 0; i < YLength; i++) {
+      newSeries[i] = [];
+      for (let j = 0; j < series.length; j++) {
+        if (series[j].index == i) {
+          newSeries[i].push(series[j]);
+        }
+      }
+    }
+  }
+
+  const rangesArr: (number[] | string[])[] = new Array(YLength);
+  const rangesFormatArr: string[][] = new Array(YLength);
+  const yAxisWidthArr: { position: string; width: number }[] = new Array(YLength);
+
+  if (YLength > 0) {
+    for (let i = 0; i < YLength; i++) {
+      let yData = opts.yAxis?.data?.[i] as YAxisDataItem | undefined;
+      if (!yData) {
+        yData = {} as YAxisDataItem;
+      }
+      if (opts.yAxis?.disabled == true) {
+        yData.disabled = true;
+      }
+      if (yData.type === 'categories') {
+        if (!yData.formatter) {
+          yData.formatter = (val: any, _index: number, _opts: ChartOptions) => {
+            return String(val) + (yData?.unit || '');
+          };
+        }
+        yData.categories = yData.categories || opts.categories;
+        rangesArr[i] = yData.categories || [];
+      } else {
+        if (!yData.formatter) {
+          yData.formatter = (val: any, _index: number, _opts: ChartOptions) => {
+            return String(util.toFixed(Number(val), yData?.tofix || 0)) + (yData?.unit || '');
+          };
+        }
+        rangesArr[i] = getYAxisTextList(newSeries[i] || [], opts, config, columnstyle.type || '', yData, i);
+      }
+      const yAxisFontSizes = (yData.fontSize || opts.yAxis?.fontSize || config.fontSize) * (opts.pix || 1);
+      yAxisWidthArr[i] = {
+        position: yData.position ? yData.position : 'left',
+        width: 0
+      };
+      rangesFormatArr[i] = (rangesArr[i] as number[]).map(function(items, index) {
+        const formatted = yData.formatter!(items, index, opts);
+        yAxisWidthArr[i].width = Math.max(yAxisWidthArr[i].width, measureText(formatted, yAxisFontSizes, context) + 5);
+        return formatted;
+      });
+      const calibration = yData.calibration ? 4 * (opts.pix || 1) : 0;
+      yAxisWidthArr[i].width += calibration + 3 * (opts.pix || 1);
+      if (yData.disabled === true) {
+        yAxisWidthArr[i].width = 0;
+      }
+    }
+  } else {
+    // 当没有配置多Y轴时,使用默认配置
+    rangesArr.length = 1;
+    rangesFormatArr.length = 1;
+    yAxisWidthArr.length = 1;
+
+    if (opts.type === 'bar') {
+      rangesArr[0] = opts.categories || [];
+      if (!opts.yAxis?.formatter) {
+        if (!opts.yAxis) {
+          opts.yAxis = {} as any;
+        }
+        (opts.yAxis as any).formatter = (val: any, _index: number, _opts: ChartOptions) => {
+          return String(val) + (opts.yAxis?.unit || '');
+        };
+      }
+    } else {
+      if (!opts.yAxis?.formatter) {
+        if (!opts.yAxis) {
+          opts.yAxis = {} as any;
+        }
+        (opts.yAxis as any).formatter = (val: any, _index: number, _opts: ChartOptions) => {
+          return String(util.toFixed(Number(val), (opts.yAxis as any)?.tofix || 0)) + (opts.yAxis?.unit || '');
+        };
+      }
+      rangesArr[0] = getYAxisTextList(series, opts, config, columnstyle.type || '', {});
+    }
+    yAxisWidthArr[0] = {
+      position: 'left',
+      width: 0
+    };
+    const yAxisFontSize = ((opts.yAxis as any)?.fontSize || config.fontSize) * (opts.pix || 1);
+    rangesFormatArr[0] = rangesArr[0].map(function(item, index) {
+      const formatted = (opts.yAxis as any)?.formatter!(item, index, opts);
+      yAxisWidthArr[0].width = Math.max(yAxisWidthArr[0].width, measureText(formatted, yAxisFontSize, context) + 5);
+      return formatted as string;
+    });
+    yAxisWidthArr[0].width += 3 * (opts.pix || 1);
+    if ((opts.yAxis as any)?.disabled === true) {
+      yAxisWidthArr[0] = {
+        position: 'left',
+        width: 0
+      };
+      (opts.yAxis as any).data = [{
+        disabled: true
+      }];
+    } else {
+      (opts.yAxis as any).data = [{
+        disabled: false,
+        position: 'left',
+        max: (opts.yAxis as any)?.max,
+        min: (opts.yAxis as any)?.min,
+        formatter: (opts.yAxis as any)?.formatter
+      }];
+      if (opts.type === 'bar') {
+        (opts.yAxis as any).data[0].categories = opts.categories;
+        (opts.yAxis as any).data[0].type = 'categories';
+      }
+    }
+  }
+
+  return {
+    rangesFormat: rangesFormatArr,
+    ranges: rangesArr,
+    yAxisWidth: yAxisWidthArr
+  };
+}
+
+/**
+ * 获取Y轴文本列表
+ * 计算Y轴刻度值范围
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param stack - 堆叠类型
+ * @param yData - Y轴数据配置
+ * @param index - Y轴索引
+ * @returns 刻度值范围数组
+ */
+function getYAxisTextList(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  stack: string = '',
+  yData: YAxisDataItem = {},
+  index: number = -1
+): number[] {
+  let data: (number | null)[];
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories?.length || 0);
+  } else {
+    data = dataCombine(series);
+  }
+  const sorted: number[] = [];
+  data = data.filter(function(item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        return item !== null;
+      } else {
+        return (item as any).value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.forEach(function(item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        if (opts.type == 'candle') {
+          (item as any).forEach(function(subitem: any) {
+            sorted.push(subitem as number);
+          });
+        } else {
+          sorted.push((item as any)[1] as number);
+        }
+      } else {
+        sorted.push((item as any).value);
+      }
+    } else if (item !== null) {
+      sorted.push(item);
+    }
+  });
+  let minData = yData.min || 0;
+  let maxData = yData.max || 0;
+  if (sorted.length > 0) {
+    minData = Math.min(...sorted);
+    maxData = Math.max(...sorted);
+  }
+  if (minData === maxData) {
+    if (maxData == 0) {
+      maxData = 10;
+    } else {
+      minData = 0;
+    }
+  }
+  const dataRange = getDataRange(minData, maxData);
+  const minRange = (yData.min === undefined || yData.min === null) ? dataRange.minRange : yData.min;
+  const maxRange = (yData.max === undefined || yData.max === null) ? dataRange.maxRange : yData.max;
+  const splitNumber = opts.yAxis?.splitNumber || 5;
+  const eachRange = (maxRange - minRange) / splitNumber;
+  const range: number[] = [];
+  for (let i = 0; i <= splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range.reverse();
+}

+ 132 - 0
mini-ui-packages/mini-charts/src/lib/data-processing/categories-calculator.ts

@@ -0,0 +1,132 @@
+/**
+ * 分类数据处理函数
+ *
+ * 从 u-charts 核心库搬迁的分类数据处理相关函数
+ * 用于处理分类轴的数据计算
+ */
+
+// 类型定义
+export interface ChartOptions {
+  categories?: string[];
+  xAxis?: XAxisOptions;
+  width?: number;
+  height?: number;
+  pix?: number;
+  area?: number[];
+  [key: string]: any;
+}
+
+export interface XAxisOptions {
+  lineHeight?: number;
+  marginTop?: number;
+  fontSize?: number;
+  disabled?: boolean;
+  rotateLabel?: boolean;
+  rotateAngle?: number;
+  scrollShow?: boolean;
+  enableScroll?: boolean;
+  formatter?: (item: any, index: number, opts: ChartOptions) => string;
+  [key: string]: any;
+}
+
+export interface UChartsConfig {
+  version: string;
+  color: string[];
+  linearColor: string[];
+  yAxisWidth: number;
+  xAxisHeight: number;
+  padding: number[];
+  rotate: boolean;
+  fontSize: number;
+  fontColor: string;
+  dataPointShape: string[];
+  pieChartLinePadding: number;
+  pieChartTextPadding: number;
+  titleFontSize: number;
+  subtitleFontSize: number;
+  radarLabelTextMargin: number;
+}
+
+export interface CategoriesDataResult {
+  angle: number;
+  xAxisHeight: number;
+}
+
+function measureText(text: string | number, fontSize: number, context?: any): number {
+  let width = 0;
+  text = String(text);
+  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {
+    context.setFontSize(fontSize);
+    return context.measureText(text).width;
+  } else {
+    const textArray = text.split('');
+    for (let i = 0; i < textArray.length; i++) {
+      const item = textArray[i];
+      if (/[a-zA-Z]/.test(item)) {
+        width += 7;
+      } else if (/[0-9]/.test(item)) {
+        width += 5.5;
+      } else if (/\./.test(item)) {
+        width += 2.7;
+      } else if (/-/.test(item)) {
+        width += 3.25;
+      } else if (/:/.test(item)) {
+        width += 2.5;
+      } else if (/[\u4e00-\u9fa5]/.test(item)) {
+        width += 10;
+      } else if (/\(|\)/.test(item)) {
+        width += 3.73;
+      } else if (/\s/.test(item)) {
+        width += 2.5;
+      } else if (/%/.test(item)) {
+        width += 8;
+      } else {
+        width += 10;
+      }
+    }
+    return width * fontSize / 10;
+  }
+}
+
+/**
+ * 计算分类数据
+ * 处理分类轴的数据
+ *
+ * @param categories - 分类数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param eachSpacing - 每个刻度的间距
+ * @param context - Canvas上下文
+ * @returns 分类数据结果
+ */
+export function calCategoriesData(
+  categories: string[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  eachSpacing: number,
+  context?: any
+): CategoriesDataResult {
+  const result: CategoriesDataResult = {
+    angle: 0,
+    xAxisHeight: (opts.xAxis?.lineHeight || 0) * (opts.pix || 1) + (opts.xAxis?.marginTop || 0) * (opts.pix || 1)
+  };
+  const fontSize = (opts.xAxis?.fontSize || 13) * (opts.pix || 1);
+  const categoriesTextLenth = categories.map(function(item, index) {
+    const xitem = opts.xAxis?.formatter ? opts.xAxis.formatter(item, index, opts) : item;
+    return measureText(String(xitem), fontSize, context);
+  });
+  const maxTextLength = Math.max(...categoriesTextLenth);
+  if (opts.xAxis?.rotateLabel == true) {
+    result.angle = (opts.xAxis.rotateAngle || 0) * Math.PI / 180;
+    const tempHeight = (opts.xAxis.marginTop || 0) * (opts.pix || 1) * 2 + Math.abs(maxTextLength * Math.sin(result.angle));
+    const minHeight = fontSize + (opts.xAxis.marginTop || 0) * (opts.pix || 1) * 2;
+    result.xAxisHeight = tempHeight < minHeight ? minHeight : tempHeight;
+  }
+  if (opts.xAxis?.scrollShow && (opts as any).enableScroll) {
+    result.xAxisHeight += 6 * (opts.pix || 1);
+  }
+  if (opts.xAxis?.disabled) {
+    result.xAxisHeight = 0;
+  }
+  return result;
+}

+ 65 - 0
mini-ui-packages/mini-charts/src/lib/data-processing/index.ts

@@ -0,0 +1,65 @@
+/**
+ * 数据处理模块统一导出
+ *
+ * 从 u-charts 核心库搬迁的数据处理相关函数
+ * 包括系列数据处理、坐标轴计算、分类数据处理、提示框数据计算
+ */
+
+// 系列数据处理函数
+export {
+  fixPieSeries,
+  fillSeries,
+  fillCustomColor,
+  getDataRange,
+  dataCombine,
+  dataCombineStack
+} from './series-calculator';
+
+export type {
+  SeriesItem,
+  ChartOptions,
+  ChartExtraOptions,
+  BarOptions,
+  ColumnOptions,
+  TooltipOptions,
+  MountOptions,
+  XAxisOptions,
+  YAxisOptions,
+  YAxisDataItem,
+  ChartData,
+  UChartsConfig,
+  DataRange
+} from './series-calculator';
+
+// 坐标轴计算函数
+export {
+  calXAxisData,
+  getXAxisPoints,
+  calYAxisData
+} from './axis-calculator';
+
+export type {
+  XAxisDataResult,
+  YAxisDataResult,
+  AxisPointsResult
+} from './axis-calculator';
+
+// 分类数据处理函数
+export {
+  calCategoriesData
+} from './categories-calculator';
+
+export type {
+  CategoriesDataResult
+} from './categories-calculator';
+
+// 提示框数据计算函数
+export {
+  getToolTipData,
+  getMixToolTipData
+} from './tooltip-calculator';
+
+export type {
+  ToolTipOption,
+  ToolTipDataResult
+} from './tooltip-calculator';

+ 411 - 0
mini-ui-packages/mini-charts/src/lib/data-processing/series-calculator.ts

@@ -0,0 +1,411 @@
+/**
+ * 系列数据处理函数
+ *
+ * 从 u-charts 核心库搬迁的系列数据处理相关函数
+ * 用于处理图表系列数据的填充、合并、颜色设置等操作
+ */
+
+// 类型定义
+export interface SeriesItem {
+  data: (number | null)[] | number[] | { value: number }[];
+  name: string;
+  color?: string;
+  index?: number;
+  linearIndex?: number;
+  type?: string;
+  show?: boolean;
+  pointShape?: string;
+  legendShape?: string;
+  formatter?: (item: any, titleText: string, index: number, opts: ChartOptions) => string;
+  style?: string;
+  disableLegend?: boolean;
+  connectNulls?: boolean;
+  [key: string]: any;
+}
+
+export interface ChartOptions {
+  type: string;
+  categories?: string[];
+  extra?: ChartExtraOptions;
+  xAxis?: XAxisOptions;
+  yAxis?: YAxisOptions;
+  area: number[];
+  width: number;
+  height: number;
+  pix: number;
+  enableScroll?: boolean;
+  chartData?: ChartData;
+  background?: string;
+  fontColor?: string;
+  _scrollDistance_?: number;
+  series?: any[];
+  tooltip?: any;
+  dataLabel?: boolean;
+  title?: any;
+  subtitle?: any;
+  dataPointShapeType?: string;
+  legend?: any;
+  [key: string]: any;
+}
+
+export interface ChartExtraOptions {
+  bar?: BarOptions;
+  column?: ColumnOptions;
+  tooltip?: TooltipOptions;
+  mount?: MountOptions;
+  [key: string]: any;
+}
+
+export interface BarOptions {
+  type?: string;
+  [key: string]: any;
+}
+
+export interface ColumnOptions {
+  type?: 'group' | 'stack' | 'meter';
+  width?: number;
+  meterBorder?: number;
+  meterFillColor?: string;
+  barBorderCircle?: boolean;
+  barBorderRadius?: number[];
+  seriesGap?: number;
+  linearType?: string;
+  linearOpacity?: number;
+  customColor?: string[];
+  colorStop?: number;
+  labelPosition?: string;
+  borderWidth?: number;
+  widthRatio?: number;
+  [key: string]: any;
+}
+
+export interface TooltipOptions {
+  legendShape?: string;
+  [key: string]: any;
+}
+
+export interface MountOptions {
+  widthRatio?: number;
+  type?: 'bar' | 'triangle' | 'mount' | 'sharp';
+  [key: string]: any;
+}
+
+export interface XAxisOptions {
+  lineHeight?: number;
+  marginTop?: number;
+  fontSize?: number;
+  disabled?: boolean;
+  rotateLabel?: boolean;
+  rotateAngle?: number;
+  scrollShow?: boolean;
+  boundaryGap?: string;
+  itemCount?: number;
+  splitNumber?: number;
+  formatter?: (item: any, index: number, opts: ChartOptions) => string;
+  [key: string]: any;
+}
+
+export interface YAxisOptions {
+  fontSize?: number;
+  disabled?: boolean;
+  data?: YAxisDataItem[];
+  formatter?: (val: number, index: number, opts: ChartOptions) => string;
+  tofix?: number;
+  unit?: string;
+  min?: number;
+  max?: number;
+  [key: string]: any;
+}
+
+export interface YAxisDataItem {
+  type?: string;
+  disabled?: boolean;
+  formatter?: (val: any, index: number, opts: ChartOptions) => string;
+  categories?: string[];
+  tofix?: number;
+  unit?: string;
+  min?: number;
+  max?: number;
+  position?: string;
+  calibration?: boolean;
+  fontSize?: number;
+  textAlign?: string;
+  axisLineColor?: string;
+  fontColor?: string;
+  axisLine?: boolean;
+  titleFontSize?: number;
+  title?: string;
+  titleFontColor?: string;
+  titleOffsetX?: number;
+  titleOffsetY?: number;
+}
+
+export interface ChartData {
+  calPoints?: any[][];
+  xAxisPoints?: number[];
+  eachSpacing?: number;
+  [key: string]: any;
+}
+
+export interface UChartsConfig {
+  version: string;
+  color: string[];
+  linearColor: string[];
+  yAxisWidth: number;
+  xAxisHeight: number;
+  padding: number[];
+  rotate: boolean;
+  fontSize: number;
+  fontColor: string;
+  dataPointShape: string[];
+  pieChartLinePadding: number;
+  pieChartTextPadding: number;
+  titleFontSize: number;
+  subtitleFontSize: number;
+  radarLabelTextMargin: number;
+  toolTipBackground?: string;
+  toolTipOpacity?: number;
+  _pieTextMaxLength_?: number;
+  _xAxisTextAngle_?: number;
+}
+
+export interface DataRange {
+  minRange: number;
+  maxRange: number;
+}
+
+/**
+ * 修复饼图系列数据
+ * 处理饼图的百分比和累计值
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @returns 修复后的系列数据数组
+ */
+export function fixPieSeries(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig
+): SeriesItem[] {
+  const pieSeriesArr: SeriesItem[] = [];
+  if (series.length > 0 && series[0].data.constructor.toString().indexOf('Array') > -1) {
+    (opts as any)._pieSeries_ = series;
+    const oldseries = series[0].data as any[];
+    for (let i = 0; i < oldseries.length; i++) {
+      oldseries[i].formatter = series[0].formatter;
+      oldseries[i].data = oldseries[i].value;
+      pieSeriesArr.push(oldseries[i]);
+    }
+    opts.series = pieSeriesArr;
+  } else {
+    return series;
+  }
+  return pieSeriesArr;
+}
+
+/**
+ * 填充系列数据
+ * 确保所有系列数据都有完整的属性配置
+ *
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @returns 填充后的系列数据数组
+ */
+export function fillSeries(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig
+): SeriesItem[] {
+  let index = 0;
+  for (let i = 0; i < series.length; i++) {
+    const item = series[i];
+    if (!item.color) {
+      item.color = config.color[index];
+      index = (index + 1) % config.color.length;
+    }
+    if (!item.linearIndex) {
+      item.linearIndex = i;
+    }
+    if (!item.index) {
+      item.index = 0;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (typeof item.show === "undefined") {
+      item.show = true;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (!item.pointShape) {
+      item.pointShape = "circle";
+    }
+    if (!item.legendShape) {
+      switch (item.type) {
+        case 'line':
+          item.legendShape = "line";
+          break;
+        case 'column':
+        case 'bar':
+          item.legendShape = "rect";
+          break;
+        case 'area':
+        case 'mount':
+          item.legendShape = "triangle";
+          break;
+        default:
+          item.legendShape = "circle";
+      }
+    }
+  }
+  return series;
+}
+
+/**
+ * 填充自定义颜色
+ * 为系列数据设置自定义颜色或使用默认渐变色
+ *
+ * @param linearType - 线性类型 ('custom' 或其他)
+ * @param customColor - 自定义颜色数组
+ * @param series - 系列数据数组
+ * @param config - uCharts配置对象
+ * @returns 颜色数组
+ */
+export function fillCustomColor(
+  linearType: string,
+  customColor: string[],
+  series: SeriesItem[],
+  config: UChartsConfig
+): string[] {
+  let newcolor = customColor || [];
+  if (linearType == 'custom' && newcolor.length == 0) {
+    newcolor = config.linearColor;
+  }
+  if (linearType == 'custom' && newcolor.length < series.length) {
+    const chazhi = series.length - newcolor.length;
+    for (let i = 0; i < chazhi; i++) {
+      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);
+    }
+  }
+  return newcolor;
+}
+
+/**
+ * 获取数据范围
+ * 计算最小值和最大值的范围
+ *
+ * @param minData - 最小数据值
+ * @param maxData - 最大数据值
+ * @returns 数据范围对象
+ */
+export function getDataRange(minData: number, maxData: number): DataRange {
+  let limit = 0;
+  const range = maxData - minData;
+  if (range >= 10000) {
+    limit = 1000;
+  } else if (range >= 1000) {
+    limit = 100;
+  } else if (range >= 100) {
+    limit = 10;
+  } else if (range >= 10) {
+    limit = 5;
+  } else if (range >= 1) {
+    limit = 1;
+  } else if (range >= 0.1) {
+    limit = 0.1;
+  } else if (range >= 0.01) {
+    limit = 0.01;
+  } else if (range >= 0.001) {
+    limit = 0.001;
+  } else if (range >= 0.0001) {
+    limit = 0.0001;
+  } else if (range >= 0.00001) {
+    limit = 0.00001;
+  } else {
+    limit = 0.000001;
+  }
+  return {
+    minRange: findRange(minData, 'lower', limit),
+    maxRange: findRange(maxData, 'upper', limit)
+  };
+}
+
+/**
+ * 查找范围边界
+ * 根据类型和限制查找合适的边界值
+ *
+ * @param num - 数值
+ * @param type - 类型 ('upper' 或 'lower')
+ * @param limit - 限制值
+ * @returns 边界值
+ */
+function findRange(num: number, type: 'upper' | 'lower', limit: number): number {
+  if (isNaN(num)) {
+    throw new Error('[uCharts] series数据需为Number格式');
+  }
+  limit = limit || 10;
+  type = type ? type : 'upper';
+  let multiple = 1;
+  while (limit < 1) {
+    limit *= 10;
+    multiple *= 10;
+  }
+  if (type === 'upper') {
+    num = Math.ceil(num * multiple);
+  } else {
+    num = Math.floor(num * multiple);
+  }
+  while (num % limit !== 0) {
+    if (type === 'upper') {
+      if (num == num + 1) {
+        break;
+      }
+      num++;
+    } else {
+      num--;
+    }
+  }
+  return num / multiple;
+}
+
+/**
+ * 数据合并
+ * 合并多个系列的数据
+ *
+ * @param series - 系列数据数组
+ * @returns 合并后的数据数组
+ */
+export function dataCombine(series: SeriesItem[]): (number | null)[] {
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data);
+  }, [] as any);
+}
+
+/**
+ * 堆叠数据合并
+ * 处理堆叠图表的数据合并
+ *
+ * @param series - 系列数据数组
+ * @param len - 数据长度
+ * @returns 合并后的数据数组
+ */
+export function dataCombineStack(
+  series: SeriesItem[],
+  len: number
+): (number | null)[] {
+  const sum = new Array(len);
+  for (let j = 0; j < sum.length; j++) {
+    sum[j] = 0;
+  }
+  for (let i = 0; i < series.length; i++) {
+    for (let j = 0; j < sum.length; j++) {
+      sum[j] += (series[i].data as number[])[j];
+    }
+  }
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data).concat(sum);
+  }, [] as any);
+}

+ 175 - 0
mini-ui-packages/mini-charts/src/lib/data-processing/tooltip-calculator.ts

@@ -0,0 +1,175 @@
+/**
+ * 提示框数据计算函数
+ *
+ * 从 u-charts 核心库搬迁的提示框数据计算相关函数
+ * 用于计算tooltip显示的数据和配置
+ */
+
+// 类型定义
+export interface SeriesItem {
+  data: (number | null)[] | number[] | { value: number }[];
+  name: string;
+  color?: string;
+  index?: number;
+  linearIndex?: number;
+  type?: string;
+  show?: boolean;
+  pointShape?: string;
+  legendShape?: string;
+  disableLegend?: boolean;
+  formatter?: (item: any, titleText: string, index: number, opts: ChartOptions) => string;
+}
+
+export interface ChartOptions {
+  categories?: string[];
+  extra?: ChartExtraOptions;
+  chartData?: ChartData;
+  xAxis?: XAxisOptions;
+  [key: string]: any;
+}
+
+export interface ChartExtraOptions {
+  tooltip?: TooltipOptions;
+  [key: string]: any;
+}
+
+export interface TooltipOptions {
+  legendShape?: string;
+  [key: string]: any;
+}
+
+export interface XAxisOptions {
+  [key: string]: any;
+}
+
+export interface ChartData {
+  calPoints?: any[][][];
+  xAxisPoints?: number[];
+  eachSpacing?: number;
+  [key: string]: any;
+}
+
+export interface ToolTipOption {
+  formatter?: (item: SeriesItem, titleText: string, index: number, opts: ChartOptions) => string;
+}
+
+export interface ToolTipDataResult {
+  textList: {
+    text: string;
+    color: string;
+    legendShape?: string;
+  }[];
+  offset: {
+    x: number;
+    y: number;
+  };
+}
+
+/**
+ * 获取提示框数据
+ * 计算tooltip显示的数据
+ *
+ * @param seriesData - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param index - 数据索引
+ * @param group - 分组索引数组
+ * @param categories - 分类数据
+ * @param option - 提示框选项(可选)
+ * @returns 提示框数据结果
+ */
+export function getToolTipData(
+  seriesData: SeriesItem[],
+  opts: ChartOptions,
+  index: number | number[],
+  group: number[],
+  categories: string[],
+  option?: ToolTipOption
+): ToolTipDataResult {
+  const calPoints = opts.chartData?.calPoints || [];
+  let points: any = {};
+  const indexArray = Array.isArray(index) ? index : [index];
+  if (group.length > 0) {
+    const filterPoints: any[][] = [];
+    for (let i = 0; i < group.length; i++) {
+      filterPoints.push(calPoints[group[i]]);
+    }
+    points = filterPoints[0][indexArray[0]];
+  } else {
+    for (let i = 0; i < calPoints.length; i++) {
+      if (calPoints[i][indexArray[0]]) {
+        points = calPoints[i][indexArray[0]];
+        break;
+      }
+    }
+  }
+  const textList = seriesData.map(function(item) {
+    let titleText: string | null = null;
+    if (opts.categories && opts.categories.length > 0) {
+      titleText = categories[indexArray[0]];
+    }
+    return {
+      text: option?.formatter
+        ? option.formatter(item, titleText || '', indexArray[0], opts)
+        : item.name + ': ' + String(item.data),
+      color: item.color || '',
+      legendShape: opts.extra?.tooltip?.legendShape == 'auto'
+        ? item.legendShape
+        : opts.extra?.tooltip?.legendShape
+    };
+  });
+  const offset = {
+    x: Math.round(points.x),
+    y: Math.round(points.y)
+  };
+  return {
+    textList,
+    offset
+  };
+}
+
+/**
+ * 获取混合图表提示框数据
+ * 计算混合图表tooltip显示的数据
+ *
+ * @param seriesData - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param index - 数据索引
+ * @param categories - 分类数据
+ * @param option - 提示框选项(可选)
+ * @returns 提示框数据结果
+ */
+export function getMixToolTipData(
+  seriesData: SeriesItem[],
+  opts: ChartOptions,
+  index: number,
+  categories: string[],
+  option?: ToolTipOption
+): ToolTipDataResult {
+  const points = (opts.chartData?.xAxisPoints?.[index] || 0) + (opts.chartData?.eachSpacing || 0) / 2;
+  const textList = seriesData.map(function(item) {
+    return {
+      text: option?.formatter
+        ? option.formatter(item, categories[index], index, opts)
+        : item.name + ': ' + String(item.data),
+      color: item.color || '',
+      disableLegend: item.disableLegend ? true : false,
+      legendShape: opts.extra?.tooltip?.legendShape == 'auto'
+        ? item.legendShape
+        : opts.extra?.tooltip?.legendShape
+    };
+  });
+  const filteredTextList = textList.filter(function(item) {
+    if ((item as any).disableLegend !== true) {
+      return item;
+    }
+    return undefined;
+  }).filter(Boolean);
+  const offset = {
+    x: Math.round(points),
+    y: 0
+  };
+  return {
+    textList: filteredTextList,
+    offset
+  };
+}

+ 108 - 0
mini-ui-packages/mini-charts/src/lib/draw-controllers/animation.ts

@@ -0,0 +1,108 @@
+// Types for Animation class
+export interface AnimationOptions {
+  duration?: number;
+  timing?: 'easeIn' | 'easeOut' | 'easeInOut' | 'linear';
+  onProcess?: (process: number) => void;
+  onAnimationFinish?: () => void;
+}
+
+export interface TimingFunction {
+  (pos: number): number;
+}
+
+export interface TimingFunctions {
+  easeIn: TimingFunction;
+  easeOut: TimingFunction;
+  easeInOut: TimingFunction;
+  linear: TimingFunction;
+}
+
+// Timing functions for animation
+const Timing: TimingFunctions = {
+  easeIn: function easeIn(pos) {
+    return Math.pow(pos, 3);
+  },
+  easeOut: function easeOut(pos) {
+    return Math.pow(pos - 1, 3) + 1;
+  },
+  easeInOut: function easeInOut(pos) {
+    if ((pos /= 0.5) < 1) {
+      return 0.5 * Math.pow(pos, 3);
+    } else {
+      return 0.5 * (Math.pow(pos - 2, 3) + 2);
+    }
+  },
+  linear: function linear(pos) {
+    return pos;
+  }
+};
+
+// Animation class for chart animations
+export class Animation {
+  private isStop: boolean = false;
+  private opts: Required<AnimationOptions>;
+
+  constructor(opts: AnimationOptions) {
+    this.isStop = false;
+    this.opts = {
+      duration: typeof opts.duration === 'undefined' ? 1000 : opts.duration,
+      timing: opts.timing || 'easeInOut',
+      onProcess: opts.onProcess || (() => {}),
+      onAnimationFinish: opts.onAnimationFinish || (() => {})
+    };
+
+    const delay = 17;
+
+    function createAnimationFrame() {
+      if (typeof setTimeout !== 'undefined') {
+        return function(step: (timestamp: number | null) => void, delay: number) {
+          setTimeout(function() {
+            const timeStamp = +new Date();
+            step(timeStamp);
+          }, delay);
+        };
+      } else if (typeof requestAnimationFrame !== 'undefined') {
+        return requestAnimationFrame;
+      } else {
+        return function(step: (timestamp: number | null) => void) {
+          step(null);
+        };
+      }
+    }
+
+    const animationFrame = createAnimationFrame();
+    let startTimeStamp: number | null = null;
+
+    const _step = (timestamp: number | null) => {
+      if (timestamp === null || this.isStop === true) {
+        this.opts.onProcess(1);
+        this.opts.onAnimationFinish();
+        return;
+      }
+      if (startTimeStamp === null) {
+        startTimeStamp = timestamp;
+      }
+      if (timestamp - startTimeStamp < this.opts.duration) {
+        let process = (timestamp - startTimeStamp) / this.opts.duration;
+        const timingFunction = Timing[this.opts.timing];
+        process = timingFunction(process);
+        this.opts.onProcess(process);
+        animationFrame(_step.bind(this), delay);
+      } else {
+        this.opts.onProcess(1);
+        this.opts.onAnimationFinish();
+      }
+    };
+
+    animationFrame(_step.bind(this), delay);
+  }
+
+  stop(): void {
+    this.isStop = true;
+  }
+}
+
+// Export the standalone function for backward compatibility
+export function AnimationFunction(opts: AnimationOptions): Animation {
+  return new Animation(opts);
+}

+ 9 - 0
mini-ui-packages/mini-charts/src/lib/draw-controllers/draw-canvas.ts

@@ -0,0 +1,9 @@
+/**
+ * draw-canvas.ts - Canvas绘制函数
+ *
+ * 重新导出 renderers 中的 drawCanvas 函数
+ * 保持模块结构的一致性
+ */
+
+// 直接从 renderers 导出 drawCanvas
+export { drawCanvas } from '../renderers/common-renderer';

+ 751 - 0
mini-ui-packages/mini-charts/src/lib/draw-controllers/draw-charts.ts

@@ -0,0 +1,751 @@
+// @ts-nocheck - 为保持与原始代码逻辑完全一致,跳过类型检查
+/**
+ * draw-charts.ts - 主绘制调度函数
+ *
+ * 功能:主绘制调度函数,负责协调调用各种绘制函数
+ * 从 u-charts.ts 第6352行搬迁
+ *
+ * 注意:这是核心绘制调度函数,协调调用所有 renderers 中的具体绘制函数
+ */
+
+import type { ChartOptions } from '../data-processing/index';
+import type { UChartsConfig } from '../data-processing/index';
+import type { CanvasContext } from '../renderers/index';
+
+// Data processing imports
+import {
+  fixPieSeries,
+  fillSeries,
+  calXAxisData,
+  getXAxisPoints,
+  calYAxisData,
+  calCategoriesData
+} from '../data-processing/index';
+
+// Helper functions imports
+import {
+  filterSeries,
+  getPieTextMaxLength,
+  calLegendData
+} from '../helper-functions/index';
+
+// Math utilities
+import { calCandleMA } from '../utils/math';
+
+// Config imports
+import { assign } from '../config';
+
+// Animation imports
+import { Animation, AnimationOptions } from './animation';
+
+// Renderer imports - 所有需要的绘制函数
+import {
+  drawCanvas,
+  drawXAxis,
+  drawYAxisGrid,
+  drawYAxis,
+  drawLegend,
+  drawToolTipBridge,
+  drawMarkLine,
+  contextRotate,
+  drawColumnDataPoints,
+  drawBarDataPoints,
+  drawMountDataPoints,
+  drawLineDataPoints,
+  drawAreaDataPoints,
+  drawCandleDataPoints,
+  drawPieDataPoints,
+  drawRadarDataPoints,
+  drawMapDataPoints,
+  drawFunnelDataPoints,
+  drawWordCloudDataPoints,
+  drawMixDataPoints,
+  drawScatterDataPoints,
+  drawBubbleDataPoints
+} from '../renderers/index';
+
+// 需要单独导入的 renderers 函数(可能未在 index.ts 中导出)
+import {
+  drawRoseDataPoints,
+  drawGaugeDataPoints,
+  drawArcbarDataPoints
+} from '../renderers/pie-renderer';
+
+/**
+ * DrawChartsContext - drawCharts 函数需要的上下文
+ */
+export interface DrawChartsContext {
+  animationInstance?: Animation;
+  uevent: {
+    trigger(event: string): void;
+  };
+  scrollOption: {
+    currentOffset: number;
+    startTouchX: number;
+    distance: number;
+    lastMoveTime: number;
+  };
+}
+
+/**
+ * drawCharts 函数类型
+ */
+export type DrawChartsFunction = (
+  this: DrawChartsContext,
+  type: string,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+) => void;
+
+/**
+ * drawCharts - 主绘制调度函数
+ *
+ * 从 u-charts.ts 第6352行搬迁的完整实现
+ * @ts-nocheck - 保持与原始代码逻辑完全一致
+ */
+export const drawCharts: DrawChartsFunction = function(
+  type: string,
+  opts: any,
+  config: any,
+  context: any
+): void {
+  const _this = this as DrawChartsContext;
+  let series = opts.series;
+
+  // 兼容ECharts饼图类数据格式
+  if (type === 'pie' || type === 'ring' || type === 'mount' || type === 'rose' || type === 'funnel') {
+    series = fixPieSeries(series, opts, config);
+  }
+
+  let categories = opts.categories;
+  if (type === 'mount') {
+    categories = [];
+    for (let j = 0; j < series.length; j++) {
+      if (series[j].show !== false) {
+        categories.push(series[j].name);
+      }
+    }
+    opts.categories = categories;
+  }
+
+  series = fillSeries(series, opts, config);
+  const duration = opts.animation ? opts.duration : 0;
+  _this.animationInstance && _this.animationInstance.stop();
+
+  let seriesMA = null;
+  if (type === 'candle') {
+    const average = assign({}, opts.extra.candle.average);
+    if (average.show) {
+      seriesMA = calCandleMA(average.day, average.name, average.color, series[0].data);
+      seriesMA = fillSeries(seriesMA, opts, config);
+      opts.seriesMA = seriesMA;
+    } else if (opts.seriesMA) {
+      seriesMA = opts.seriesMA = fillSeries(opts.seriesMA, opts, config);
+    } else {
+      seriesMA = series;
+    }
+  } else {
+    seriesMA = series;
+  }
+
+  /* 过滤掉show=false的series */
+  opts._series_ = series = filterSeries(series);
+
+  // 重新计算图表区域
+  opts.area = new Array(4);
+  // 复位绘图区域
+  for (let j = 0; j < 4; j++) {
+    opts.area[j] = opts.padding[j] * opts.pix;
+  }
+
+  // 通过计算三大区域:图例、X轴、Y轴的大小,确定绘图区域
+  const _calLegendData = calLegendData(seriesMA, opts, config, opts.chartData, context);
+  const legendHeight = _calLegendData.area.wholeHeight;
+  const legendWidth = _calLegendData.area.wholeWidth;
+
+  switch (opts.legend.position) {
+    case 'top':
+      opts.area[0] += legendHeight;
+      break;
+    case 'bottom':
+      opts.area[2] += legendHeight;
+      break;
+    case 'left':
+      opts.area[3] += legendWidth;
+      break;
+    case 'right':
+      opts.area[1] += legendWidth;
+      break;
+  }
+
+  let _calYAxisData = {};
+  let yAxisWidth = 0;
+  if (opts.type === 'line' || opts.type === 'column' || opts.type === 'mount' ||
+      opts.type === 'area' || opts.type === 'mix' || opts.type === 'candle' ||
+      opts.type === 'scatter' || opts.type === 'bubble' || opts.type === 'bar') {
+    _calYAxisData = calYAxisData(series, opts, config, context);
+    yAxisWidth = _calYAxisData.yAxisWidth;
+
+    // 如果显示Y轴标题
+    if (opts.yAxis.showTitle) {
+      let maxTitleHeight = 0;
+      for (let i = 0; i < opts.yAxis.data.length; i++) {
+        maxTitleHeight = Math.max(maxTitleHeight, opts.yAxis.data[i].titleFontSize ? opts.yAxis.data[i].titleFontSize * opts.pix : config.fontSize);
+      }
+      opts.area[0] += maxTitleHeight;
+    }
+
+    let rightIndex = 0;
+    let leftIndex = 0;
+    // 计算主绘图区域左右位置
+    for (let i = 0; i < yAxisWidth.length; i++) {
+      if (yAxisWidth[i].position === 'left') {
+        if (leftIndex > 0) {
+          opts.area[3] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[3] += yAxisWidth[i].width;
+        }
+        leftIndex += 1;
+      } else if (yAxisWidth[i].position === 'right') {
+        if (rightIndex > 0) {
+          opts.area[1] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[1] += yAxisWidth[i].width;
+        }
+        rightIndex += 1;
+      }
+    }
+  } else {
+    config.yAxisWidth = yAxisWidth;
+  }
+  opts.chartData.yAxisData = _calYAxisData;
+
+  if (opts.categories && opts.categories.length && opts.type !== 'radar' &&
+      opts.type !== 'gauge' && opts.type !== 'bar') {
+    opts.chartData.xAxisData = getXAxisPoints(opts.categories, opts, config);
+    const _calCategoriesData = calCategoriesData(
+      opts.categories,
+      opts,
+      config,
+      opts.chartData.xAxisData.eachSpacing,
+      context
+    );
+    const xAxisHeight = _calCategoriesData.xAxisHeight;
+    const angle = _calCategoriesData.angle;
+    config.xAxisHeight = xAxisHeight;
+    config._xAxisTextAngle_ = angle;
+    opts.area[2] += xAxisHeight;
+    opts.chartData.categoriesData = _calCategoriesData;
+  } else {
+    if (opts.type === 'line' || opts.type === 'area' || opts.type === 'scatter' ||
+        opts.type === 'bubble' || opts.type === 'bar') {
+      opts.chartData.xAxisData = calXAxisData(series, opts, config, context);
+      categories = opts.chartData.xAxisData.rangesFormat;
+      const _calCategoriesData = calCategoriesData(
+        categories,
+        opts,
+        config,
+        opts.chartData.xAxisData.eachSpacing,
+        context
+      );
+      const xAxisHeight = _calCategoriesData.xAxisHeight;
+      const angle = _calCategoriesData.angle;
+      config.xAxisHeight = xAxisHeight;
+      config._xAxisTextAngle_ = angle;
+      opts.area[2] += xAxisHeight;
+      opts.chartData.categoriesData = _calCategoriesData;
+    } else {
+      opts.chartData.xAxisData = {
+        xAxisPoints: []
+      };
+    }
+  }
+
+  // 计算右对齐偏移距离
+  if (opts.enableScroll && opts.xAxis.scrollAlign === 'right' && opts._scrollDistance_ === undefined) {
+    let offsetLeft = 0;
+    const xAxisPoints = opts.chartData.xAxisData.xAxisPoints;
+    const startX = opts.chartData.xAxisData.startX;
+    const endX = opts.chartData.xAxisData.endX;
+    const eachSpacing = opts.chartData.xAxisData.eachSpacing;
+    const totalWidth = eachSpacing * (xAxisPoints.length - 1);
+    const screenWidth = endX - startX;
+    offsetLeft = screenWidth - totalWidth;
+    _this.scrollOption.currentOffset = offsetLeft;
+    _this.scrollOption.startTouchX = offsetLeft;
+    _this.scrollOption.distance = 0;
+    _this.scrollOption.lastMoveTime = 0;
+    opts._scrollDistance_ = offsetLeft;
+  }
+
+  if (type === 'pie' || type === 'ring' || type === 'rose') {
+    config._pieTextMaxLength_ = opts.dataLabel === false ? 0 : getPieTextMaxLength(seriesMA, config, context, opts);
+  }
+
+  // 图表进度计算函数
+  function chartProcess(process) {
+    return process;
+  }
+
+  // 根据图表类型执行不同的绘制逻辑
+  // 从 u-charts.js 第6528-6965行搬迁
+  switch (type) {
+    case 'word':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawWordCloudDataPoints(series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'map':
+      context.clearRect(0, 0, opts.width, opts.height);
+      drawMapDataPoints(series, opts, config, context);
+      setTimeout(() => {
+        _this.uevent.trigger('renderComplete');
+      }, 50);
+      break;
+    case 'funnel':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.funnelData = drawFunnelDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'line':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawLineDataPoints = drawLineDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawLineDataPoints.xAxisPoints,
+            calPoints = _drawLineDataPoints.calPoints,
+            eachSpacing = _drawLineDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'scatter':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawScatterDataPoints = drawScatterDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawScatterDataPoints.xAxisPoints,
+            calPoints = _drawScatterDataPoints.calPoints,
+            eachSpacing = _drawScatterDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bubble':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawBubbleDataPoints = drawBubbleDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawBubbleDataPoints.xAxisPoints,
+            calPoints = _drawBubbleDataPoints.calPoints,
+            eachSpacing = _drawBubbleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mix':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawMixDataPoints = drawMixDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawMixDataPoints.xAxisPoints,
+            calPoints = _drawMixDataPoints.calPoints,
+            eachSpacing = _drawMixDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'column':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawColumnDataPoints = drawColumnDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawColumnDataPoints.xAxisPoints,
+            calPoints = _drawColumnDataPoints.calPoints,
+            eachSpacing = _drawColumnDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mount':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawMountDataPoints = drawMountDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawMountDataPoints.xAxisPoints,
+            calPoints = _drawMountDataPoints.calPoints,
+            eachSpacing = _drawMountDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bar':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawXAxis(categories, opts, config, context);
+          var _drawBarDataPoints = drawBarDataPoints(series, opts, config, context, process),
+            yAxisPoints = _drawBarDataPoints.yAxisPoints,
+            calPoints = _drawBarDataPoints.calPoints,
+            eachSpacing = _drawBarDataPoints.eachSpacing;
+          opts.chartData.yAxisPoints = yAxisPoints;
+          opts.chartData.xAxisPoints = opts.chartData.xAxisData.xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, yAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'area':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawAreaDataPoints = drawAreaDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawAreaDataPoints.xAxisPoints,
+            calPoints = _drawAreaDataPoints.calPoints,
+            eachSpacing = _drawAreaDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'ring':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'pie':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'rose':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawRoseDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'radar':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.radarData = drawRadarDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'arcbar':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.arcbarData = drawArcbarDataPoints(series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'gauge':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.gaugeData = drawGaugeDataPoints(categories, series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'candle':
+      _this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawCandleDataPoints = drawCandleDataPoints(series, seriesMA, opts, config, context, process),
+            xAxisPoints = _drawCandleDataPoints.xAxisPoints,
+            calPoints = _drawCandleDataPoints.calPoints,
+            eachSpacing = _drawCandleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          if (seriesMA) {
+            drawLegend(seriesMA, opts, config, context, opts.chartData);
+          } else {
+            drawLegend(opts.series, opts, config, context, opts.chartData);
+          }
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+  }
+};
+
+/**
+ * 辅助函数:创建动画实例
+ */
+export function createAnimationInstance(
+  opts: AnimationOptions,
+  context: DrawChartsContext
+): Animation {
+  return new Animation(opts);
+}
+
+/**
+ * 辅助函数:处理动画完成
+ */
+export function handleAnimationComplete(
+  context: DrawChartsContext,
+  eventName: string = 'renderComplete'
+): void {
+  context.uevent.trigger(eventName);
+}

+ 20 - 0
mini-ui-packages/mini-charts/src/lib/draw-controllers/index.ts

@@ -0,0 +1,20 @@
+/**
+ * draw-controllers 模块统一导出
+ *
+ * 导出核心绘制控制函数:drawCharts, Animation
+ * 注意:drawCanvas 已在 renderers 模块中导出
+ */
+
+export {
+  drawCharts,
+  type DrawChartsContext,
+  type DrawChartsFunction
+} from './draw-charts';
+
+export {
+  Animation,
+  AnimationFunction,
+  type AnimationOptions,
+  type TimingFunction,
+  type TimingFunctions
+} from './animation';

+ 63 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/area-checkers.ts

@@ -0,0 +1,63 @@
+/**
+ * 区域判断函数模块
+ * 用于判断点是否在特定区域内
+ */
+
+import type { ChartOptions, UChartsConfig } from '../data-processing/index';
+
+/**
+ * 判断是否在图例区域内
+ * @param currentPoints 当前触摸点坐标
+ * @param area 图例区域
+ * @returns 是否在图例区域内
+ */
+export function isInExactLegendArea(
+  currentPoints: { x: number; y: number },
+  area: { start: { x: number; y: number }; end: { x: number; y: number } }
+): boolean {
+  return (
+    currentPoints.x > area.start.x &&
+    currentPoints.x < area.end.x &&
+    currentPoints.y > area.start.y &&
+    currentPoints.y < area.end.y
+  );
+}
+
+/**
+ * 判断是否在图表区域内
+ * @param currentPoints 当前触摸点坐标
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @returns 是否在图表区域内
+ */
+export function isInExactChartArea(
+  currentPoints: { x: number; y: number },
+  opts: ChartOptions,
+  config: UChartsConfig
+): boolean {
+  return (
+    currentPoints.x <= opts.width - opts.area[1] + 10 &&
+    currentPoints.x >= opts.area[3] - 10 &&
+    currentPoints.y >= opts.area[0] &&
+    currentPoints.y <= opts.height - opts.area[2]
+  );
+}
+
+/**
+ * 判断是否在饼图区域内
+ * @param currentPoints 当前触摸点坐标
+ * @param center 饼图中心点
+ * @param radius 饼图半径
+ * @returns 是否在饼图区域内
+ */
+export function isInExactPieChartArea(
+  currentPoints: { x: number; y: number },
+  center: { x: number; y: number },
+  radius: number
+): boolean {
+  return (
+    Math.pow(currentPoints.x - center.x, 2) +
+      Math.pow(currentPoints.y - center.y, 2) <=
+    Math.pow(radius, 2)
+  );
+}

+ 216 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/coordinate-helpers.ts

@@ -0,0 +1,216 @@
+/**
+ * 坐标转换函数模块
+ * 用于地图坐标转换和判断
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+
+/**
+ * 经纬度转墨卡托坐标
+ * @param longitude 经度
+ * @param latitude 纬度
+ * @returns 墨卡托坐标 [x, y]
+ */
+export function lonlat2mercator(
+  longitude: number,
+  latitude: number
+): [number, number] {
+  const mercator: [number, number] = [0, 0];
+  const x = (longitude * 20037508.34) / 180;
+  let y =
+    Math.log(Math.tan(((90 + latitude) * Math.PI) / 360)) / (Math.PI / 180);
+  y = (y * 20037508.34) / 180;
+  mercator[0] = x;
+  mercator[1] = y;
+  return mercator;
+}
+
+/**
+ * 墨卡托坐标转经纬度
+ * @param longitude 墨卡托X坐标
+ * @param latitude 墨卡托Y坐标
+ * @returns 经纬度坐标 [x, y]
+ */
+export function mercator2lonlat(
+  longitude: number,
+  latitude: number
+): [number, number] {
+  const lonlat: [number, number] = [0, 0];
+  const x = (longitude / 20037508.34) * 180;
+  let y = (latitude / 20037508.34) * 180;
+  y =
+    (180 / Math.PI) *
+    (2 * Math.atan(Math.exp((y * Math.PI) / 180)) - Math.PI / 2);
+  lonlat[0] = x;
+  lonlat[1] = y;
+  return lonlat;
+}
+
+/**
+ * 获取边界框
+ * @param data GeoJSON数据
+ * @returns 边界框对象
+ */
+export function getBoundingBox(
+  data: Array<{ geometry: { coordinates: any[] } }>
+): {
+  xMin: number;
+  xMax: number;
+  yMin: number;
+  yMax: number;
+} {
+  const bounds: any = {};
+  let coords: any;
+  bounds.xMin = 180;
+  bounds.xMax = 0;
+  bounds.yMin = 90;
+  bounds.yMax = 0;
+
+  for (let i = 0; i < data.length; i++) {
+    const coorda = data[i].geometry.coordinates;
+    for (let k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length === 1) {
+        coords = coords[0];
+      }
+      for (let j = 0; j < coords.length; j++) {
+        const longitude = coords[j][0];
+        const latitude = coords[j][1];
+        const point = {
+          x: longitude,
+          y: latitude,
+        };
+        bounds.xMin = bounds.xMin < point.x ? bounds.xMin : point.x;
+        bounds.xMax = bounds.xMax > point.x ? bounds.xMax : point.x;
+        bounds.yMin = bounds.yMin < point.y ? bounds.yMin : point.y;
+        bounds.yMax = bounds.yMax > point.y ? bounds.yMax : point.y;
+      }
+    }
+  }
+  return bounds;
+}
+
+/**
+ * 坐标转点
+ * @param latitude 纬度
+ * @param longitude 经度
+ * @param bounds 边界框
+ * @param scale 缩放比例
+ * @param xoffset X偏移
+ * @param yoffset Y偏移
+ * @returns 点坐标 {x, y}
+ */
+export function coordinateToPoint(
+  latitude: number,
+  longitude: number,
+  bounds: { xMin: number; xMax: number; yMin: number; yMax: number },
+  scale: number,
+  xoffset: number,
+  yoffset: number
+): { x: number; y: number } {
+  return {
+    x: (longitude - bounds.xMin) * scale + xoffset,
+    y: (bounds.yMax - latitude) * scale + yoffset,
+  };
+}
+
+/**
+ * 点转坐标
+ * @param pointY 点Y坐标
+ * @param pointX 点X坐标
+ * @param bounds 边界框
+ * @param scale 缩放比例
+ * @param xoffset X偏移
+ * @param yoffset Y偏移
+ * @returns 坐标 {x, y}
+ */
+export function pointToCoordinate(
+  pointY: number,
+  pointX: number,
+  bounds: { xMin: number; xMax: number; yMin: number; yMax: number },
+  scale: number,
+  xoffset: number,
+  yoffset: number
+): { x: number; y: number } {
+  return {
+    x: (pointX - xoffset) / scale + bounds.xMin,
+    y: bounds.yMax - (pointY - yoffset) / scale,
+  };
+}
+
+/**
+ * 判断射线是否与线段相交
+ * @param poi 点坐标
+ * @param s_poi 线段起点
+ * @param e_poi 线段终点
+ * @returns 是否相交
+ */
+export function isRayIntersectsSegment(
+  poi: number[],
+  s_poi: number[],
+  e_poi: number[]
+): boolean {
+  if (s_poi[1] === e_poi[1]) {
+    return false;
+  }
+  if (s_poi[1] > poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[1] < poi[1] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  if (s_poi[1] === poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (e_poi[1] === poi[1] && s_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[0] < poi[0] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  const xseg =
+    e_poi[0] -
+    ((e_poi[0] - s_poi[0]) * (e_poi[1] - poi[1])) / (e_poi[1] - s_poi[1]);
+  if (xseg < poi[0]) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+/**
+ * 判断点是否在多边形内
+ * @param poi 点坐标
+ * @param poly 多边形坐标数组
+ * @param mercator 是否使用墨卡托投影
+ * @returns 是否在多边形内
+ */
+export function isPoiWithinPoly(
+  poi: number[],
+  poly: number[][][],
+  mercator: boolean
+): boolean {
+  let sinsc = 0;
+  for (let i = 0; i < poly.length; i++) {
+    let epoly = poly[i][0];
+    if (poly.length === 1) {
+      epoly = poly[i][0];
+    }
+    for (let j = 0; j < epoly.length - 1; j++) {
+      let s_poi = epoly[j];
+      let e_poi = epoly[j + 1];
+      if (mercator) {
+        s_poi = lonlat2mercator(epoly[j][0], epoly[j][1]) as any;
+        e_poi = lonlat2mercator(epoly[j + 1][0], epoly[j + 1][1]) as any;
+      }
+      if (isRayIntersectsSegment(poi, s_poi as number[], e_poi as number[])) {
+        sinsc += 1;
+      }
+    }
+  }
+  if (sinsc % 2 === 1) {
+    return true;
+  } else {
+    return false;
+  }
+}

+ 259 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/data-fixers.ts

@@ -0,0 +1,259 @@
+/**
+ * 数据修正函数模块
+ * 用于修正柱状图和条形图的数据点位置和宽度
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+
+import type { ChartOptions, UChartsConfig } from '../data-processing/index';
+
+// 使用 any 类型来简化与原始 u-charts 代码的兼容性
+type AnyChartOptions = any;
+
+/**
+ * 修正柱状图数据
+ * @param points 数据点数组
+ * @param eachSpacing 每个数据点的间距
+ * @param columnLen 柱状图系列数量
+ * @param index 当前系列索引
+ * @param config 图表配置
+ * @param opts 图表配置选项
+ * @returns 修正后的数据点数组
+ */
+export function fixColumeData(
+  points: Array<{ x: number; width?: number } | null>,
+  eachSpacing: number,
+  columnLen: number,
+  index: number,
+  config: UChartsConfig,
+  opts: AnyChartOptions
+): Array<{ x: number; width: number } | null> {
+  return points.map(function (item, itemIndex) {
+    if (item === null) {
+      return null;
+    }
+    let seriesGap = 0;
+    let categoryGap = 0;
+    if (opts.type === 'mix') {
+      seriesGap = opts.extra?.mix?.column?.seriesGap
+        ? opts.extra.mix.column.seriesGap * opts.pix
+        : 0;
+      categoryGap = opts.extra?.mix?.column?.categoryGap
+        ? opts.extra.mix.column.categoryGap * opts.pix
+        : 0;
+    } else {
+      seriesGap = opts.extra?.column?.seriesGap
+        ? opts.extra.column.seriesGap * opts.pix
+        : 0;
+      categoryGap = opts.extra?.column?.categoryGap
+        ? opts.extra.column.categoryGap * opts.pix
+        : 0;
+    }
+    seriesGap = Math.min(seriesGap, eachSpacing / columnLen);
+    categoryGap = Math.min(categoryGap, eachSpacing / columnLen);
+    (item as any).width = Math.ceil(
+      (eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) /
+        columnLen
+    );
+
+    const widthBeforeLimit = (item as any).width;
+
+    if (
+      opts.extra?.mix?.column?.width &&
+      +opts.extra.mix.column.width > 0
+    ) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.mix.column.width * opts.pix
+      );
+    }
+    if (
+      opts.extra?.column?.width &&
+      +opts.extra.column.width > 0
+    ) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.column.width * opts.pix
+      );
+    }
+    if ((item as any).width <= 0) {
+      (item as any).width = 1;
+    }
+
+    const originalX = item.x;
+    item.x += (index + 0.5 - columnLen / 2) * ((item as any).width + seriesGap);
+
+    return item as { x: number; width: number };
+  });
+}
+
+/**
+ * 修正条形图数据
+ * @param points 数据点数组
+ * @param eachSpacing 每个数据点的间距
+ * @param columnLen 条形图系列数量
+ * @param index 当前系列索引
+ * @param config 图表配置
+ * @param opts 图表配置选项
+ * @returns 修正后的数据点数组
+ */
+export function fixBarData(
+  points: Array<{ y: number; width?: number } | null>,
+  eachSpacing: number,
+  columnLen: number,
+  index: number,
+  config: UChartsConfig,
+  opts: AnyChartOptions
+): Array<{ y: number; width: number } | null> {
+  return points.map(function (item) {
+    if (item === null) {
+      return null;
+    }
+    let seriesGap = 0;
+    let categoryGap = 0;
+    seriesGap = opts.extra?.bar?.seriesGap ? opts.extra.bar.seriesGap * opts.pix : 0;
+    categoryGap = opts.extra?.bar?.categoryGap
+      ? opts.extra.bar.categoryGap * opts.pix
+      : 0;
+    seriesGap = Math.min(seriesGap, eachSpacing / columnLen);
+    categoryGap = Math.min(categoryGap, eachSpacing / columnLen);
+    (item as any).width = Math.ceil(
+      (eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) /
+        columnLen
+    );
+    if (opts.extra?.bar?.width && +opts.extra.bar.width > 0) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.bar.width * opts.pix
+      );
+    }
+    if ((item as any).width <= 0) {
+      (item as any).width = 1;
+    }
+    (item as any).y += (index + 0.5 - columnLen / 2) * ((item as any).width + seriesGap);
+    return item as { y: number; width: number };
+  });
+}
+
+/**
+ * 修正仪表盘柱状图数据
+ * @param points 数据点数组
+ * @param eachSpacing 每个数据点的间距
+ * @param columnLen 柱状图系列数量
+ * @param index 当前系列索引
+ * @param config 图表配置
+ * @param opts 图表配置选项
+ * @param border 边框宽度
+ * @returns 修正后的数据点数组
+ */
+export function fixColumeMeterData(
+  points: Array<{ width?: number } | null>,
+  eachSpacing: number,
+  columnLen: number,
+  index: number,
+  config: UChartsConfig,
+  opts: AnyChartOptions,
+  border: number
+): Array<{ width: number } | null> {
+  const categoryGap = opts.extra?.column?.categoryGap
+    ? opts.extra.column.categoryGap * opts.pix
+    : 0;
+  return points.map(function (item) {
+    if (item === null) {
+      return null;
+    }
+    (item as any).width = eachSpacing - 2 * categoryGap;
+    if (opts.extra?.column?.width && +opts.extra.column.width > 0) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.column.width * opts.pix
+      );
+    }
+    if (index > 0) {
+      (item as any).width -= border;
+    }
+    return item as { width: number };
+  });
+}
+
+/**
+ * 修正堆叠柱状图数据
+ * @param points 数据点数组
+ * @param eachSpacing 每个数据点的间距
+ * @param columnLen 柱状图系列数量
+ * @param index 当前系列索引
+ * @param config 图表配置
+ * @param opts 图表配置选项
+ * @param series 数据系列
+ * @returns 修正后的数据点数组
+ */
+export function fixColumeStackData(
+  points: Array<{ width?: number } | null>,
+  eachSpacing: number,
+  columnLen: number,
+  index: number,
+  config: UChartsConfig,
+  opts: AnyChartOptions,
+  series: any[]
+): Array<{ width: number } | null> {
+  const categoryGap = opts.extra?.column?.categoryGap
+    ? opts.extra.column.categoryGap * opts.pix
+    : 0;
+  return points.map(function (item) {
+    if (item === null) {
+      return null;
+    }
+    (item as any).width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra?.column?.width && +opts.extra.column.width > 0) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.column.width * opts.pix
+      );
+    }
+    if ((item as any).width <= 0) {
+      (item as any).width = 1;
+    }
+    return item as { width: number };
+  });
+}
+
+/**
+ * 修正堆叠条形图数据
+ * @param points 数据点数组
+ * @param eachSpacing 每个数据点的间距
+ * @param columnLen 条形图系列数量
+ * @param index 当前系列索引
+ * @param config 图表配置
+ * @param opts 图表配置选项
+ * @param series 数据系列
+ * @returns 修正后的数据点数组
+ */
+export function fixBarStackData(
+  points: Array<{ width?: number } | null>,
+  eachSpacing: number,
+  columnLen: number,
+  index: number,
+  config: UChartsConfig,
+  opts: AnyChartOptions,
+  series: any[]
+): Array<{ width: number } | null> {
+  const categoryGap = opts.extra?.bar?.categoryGap
+    ? opts.extra.bar.categoryGap * opts.pix
+    : 0;
+  return points.map(function (item) {
+    if (item === null) {
+      return null;
+    }
+    (item as any).width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra?.bar?.width && +opts.extra.bar.width > 0) {
+      (item as any).width = Math.min(
+        (item as any).width,
+        +opts.extra.bar.width * opts.pix
+      );
+    }
+    if ((item as any).width <= 0) {
+      (item as any).width = 1;
+    }
+    return item as { width: number };
+  });
+}

+ 252 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/data-helpers.ts

@@ -0,0 +1,252 @@
+/**
+ * 数据辅助函数模块
+ * 用于处理和计算图表相关数据
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+
+import type { SeriesItem } from '../data-processing/index';
+import { measureText } from '../utils/text';
+
+/**
+ * 获取系列数据项
+ * @param series 数据系列
+ * @param index 索引或索引数组
+ * @param group 分组数组
+ * @returns 数据项数组
+ */
+export function getSeriesDataItem(
+  series: SeriesItem[],
+  index: number | number[],
+  group: number[]
+): Array<{
+  color?: string;
+  type?: string;
+  style?: string;
+  pointShape?: string;
+  disableLegend?: boolean;
+  legendShape?: string;
+  name?: string;
+  show?: boolean;
+  data: any;
+}> {
+  const data: any[] = [];
+  let newSeries: SeriesItem[] = [];
+  const indexIsArr = Array.isArray(index);
+
+  if (indexIsArr) {
+    const tempSeries = filterSeries(series);
+    for (let i = 0; i < group.length; i++) {
+      newSeries.push(tempSeries[group[i]]);
+    }
+  } else {
+    newSeries = series;
+  }
+
+  for (let i = 0; i < newSeries.length; i++) {
+    const item = newSeries[i];
+    let tmpindex = -1;
+    if (indexIsArr) {
+      tmpindex = (index as number[])[i];
+    } else {
+      tmpindex = index as number;
+    }
+    if (
+      item.data[tmpindex] !== null &&
+      typeof item.data[tmpindex] !== 'undefined' &&
+      item.show
+    ) {
+      const seriesItem: any = {};
+      seriesItem.color = item.color;
+      seriesItem.type = item.type;
+      seriesItem.style = item.style;
+      seriesItem.pointShape = item.pointShape;
+      seriesItem.disableLegend = item.disableLegend;
+      seriesItem.legendShape = item.legendShape;
+      seriesItem.name = item.name;
+      seriesItem.show = item.show;
+      seriesItem.data = item.formatter
+        ? item.formatter(item.data[tmpindex], tmpindex, item)
+        : item.data[tmpindex];
+      data.push(seriesItem);
+    }
+  }
+  return data;
+}
+
+/**
+ * 过滤系列数据
+ * @param series 数据系列
+ * @returns 过滤后的系列数据
+ */
+export function filterSeries(series: SeriesItem[]): SeriesItem[] {
+  const tempSeries: SeriesItem[] = [];
+  for (let i = 0; i < series.length; i++) {
+    if (series[i].show == true) {
+      tempSeries.push(series[i]);
+    }
+  }
+  return tempSeries;
+}
+
+/**
+ * 分割点数据
+ * @param points 点数据数组
+ * @param eachSeries 系列配置
+ * @returns 分割后的点数组
+ */
+export function splitPoints(
+  points: any[],
+  eachSeries: { connectNulls?: boolean }
+): any[][] {
+  const newPoints: any[][] = [];
+  const items: any[] = [];
+
+  points.forEach(function (item) {
+    if (eachSeries.connectNulls) {
+      if (item !== null) {
+        items.push(item);
+      }
+    } else {
+      if (item !== null) {
+        items.push(item);
+      } else {
+        if (items.length) {
+          newPoints.push(items);
+        }
+        items.length = 0;
+      }
+    }
+  });
+
+  if (items.length) {
+    newPoints.push(items);
+  }
+  return newPoints;
+}
+
+/**
+ * 获取文本列表最大长度
+ * @param list 文本列表
+ * @param fontSize 字体大小
+ * @param context Canvas上下文
+ * @returns 最大长度
+ */
+export function getMaxTextListLength(
+  list: string[],
+  fontSize: number,
+  context: any
+): number {
+  const lengthList = list.map(function (item) {
+    return measureText(item, fontSize, context);
+  });
+  return Math.max.apply(null, lengthList);
+}
+
+/**
+ * 获取雷达图坐标系列
+ * @param length 数据长度
+ * @returns 坐标角度数组
+ */
+export function getRadarCoordinateSeries(length: number): number[] {
+  const eachAngle = (2 * Math.PI) / length;
+  const CoordinateSeries: number[] = [];
+  for (let i = 0; i < length; i++) {
+    CoordinateSeries.push(eachAngle * i);
+  }
+  return CoordinateSeries.map(function (item) {
+    return -1 * item + Math.PI / 2;
+  });
+}
+
+/**
+ * 获取K线图提示框数据
+ * @param series K线数据系列
+ * @param seriesData 系列数据
+ * @param opts 图表配置
+ * @param index 当前索引
+ * @param categories 分类数据
+ * @param extra 额外配置
+ * @param option 提示框选项
+ * @returns 提示框数据对象
+ */
+export function getCandleToolTipData(
+  series: any[],
+  seriesData: any[],
+  opts: any,
+  index: number,
+  categories: any[],
+  extra: any,
+  option: any = {}
+): { textList: any[]; offset: { x: number; y: number } } {
+  const calPoints = opts.chartData.calPoints;
+  const upColor = extra.color.upFill;
+  const downColor = extra.color.downFill;
+  //颜色顺序为开盘,收盘,最低,最高
+  const color = [upColor, upColor, downColor, upColor];
+  const textList: any[] = [];
+
+  seriesData.map(function (item) {
+    if (index == 0) {
+      if (item.data[1] - item.data[0] < 0) {
+        color[1] = downColor;
+      } else {
+        color[1] = upColor;
+      }
+    } else {
+      if (item.data[0] < series[index - 1][1]) {
+        color[0] = downColor;
+      }
+      if (item.data[1] < item.data[0]) {
+        color[1] = downColor;
+      }
+      if (item.data[2] > series[index - 1][1]) {
+        color[2] = upColor;
+      }
+      if (item.data[3] < series[index - 1][1]) {
+        color[3] = downColor;
+      }
+    }
+    const text1 = {
+      text: '开盘:' + item.data[0],
+      color: color[0],
+      legendShape: opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    const text2 = {
+      text: '收盘:' + item.data[1],
+      color: color[1],
+      legendShape: opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    const text3 = {
+      text: '最低:' + item.data[2],
+      color: color[2],
+      legendShape: opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    const text4 = {
+      text: '最高:' + item.data[3],
+      color: color[3],
+      legendShape: opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    textList.push(text1, text2, text3, text4);
+  });
+
+  const validCalPoints: any[] = [];
+  const offset = {
+    x: 0,
+    y: 0
+  };
+
+  for (let i = 0; i < calPoints.length; i++) {
+    const points = calPoints[i];
+    if (typeof points[index] !== 'undefined' && points[index] !== null) {
+      validCalPoints.push(points[index]);
+    }
+  }
+
+  offset.x = Math.round(validCalPoints[0][0].x);
+
+  return {
+    textList: textList,
+    offset: offset
+  };
+}

+ 405 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/index-finders.ts

@@ -0,0 +1,405 @@
+/**
+ * 索引查找函数模块
+ * 用于查找不同类型图表的当前数据索引
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+// 这些函数直接从 u-charts.ts 迁移,保持原有逻辑不变
+
+import type { ChartOptions, UChartsConfig } from '../data-processing/index';
+import type { PieData, RadarData, LegendData } from './types';
+import { getPieDataPoints, getRoseDataPoints } from '../charts-data/pie-charts';
+import { isInAngleRange } from '../utils/coordinate';
+import {
+  isInExactLegendArea,
+  isInExactChartArea,
+  isInExactPieChartArea,
+} from './area-checkers';
+import { pointToCoordinate, isPoiWithinPoly } from './coordinate-helpers';
+
+/**
+ * 查找当前数据索引
+ * @param currentPoints 当前触摸点坐标
+ * @param calPoints 计算后的数据点
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param offset 偏移量
+ * @returns 当前索引和分组信息
+ */
+export function findCurrentIndex(
+  currentPoints: { x: number; y: number },
+  calPoints: Array<Array<{ x: number; y: number }>>,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  offset: number = 0
+): { index: number | number[]; group: number[] } {
+  const current: { index: number | number[]; group: number[] } = {
+    index: -1,
+    group: [],
+  };
+  let spacing = opts.chartData.eachSpacing / 2;
+  const xAxisPoints: number[] = [];
+
+  if (calPoints && calPoints.length > 0) {
+    if (!opts.categories) {
+      spacing = 0;
+    } else {
+      for (let i = 1; i < opts.chartData.xAxisPoints.length; i++) {
+        xAxisPoints.push(opts.chartData.xAxisPoints[i] - spacing);
+      }
+      if (
+        (opts.type === 'line' || opts.type === 'area') &&
+        opts.xAxis.boundaryGap === 'justify'
+      ) {
+        xAxisPoints.length = 0;
+        opts.chartData.xAxisPoints.forEach((p: number) => xAxisPoints.push(p));
+      }
+    }
+
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      if (!opts.categories) {
+        const timePoints = Array(calPoints.length);
+        for (let i = 0; i < calPoints.length; i++) {
+          timePoints[i] = Array(calPoints[i].length);
+          for (let j = 0; j < calPoints[i].length; j++) {
+            timePoints[i][j] = Math.abs(calPoints[i][j].x - currentPoints.x);
+          }
+        }
+        const pointValue = Array(timePoints.length);
+        const pointIndex = Array(timePoints.length);
+        for (let i = 0; i < timePoints.length; i++) {
+          pointValue[i] = Math.min.apply(null, timePoints[i]);
+          pointIndex[i] = timePoints[i].indexOf(pointValue[i] as number);
+        }
+        const minValue = Math.min.apply(null, pointValue);
+        current.index = [];
+        for (let i = 0; i < pointValue.length; i++) {
+          if (pointValue[i] === minValue) {
+            current.group.push(i);
+            (current.index as number[]).push(pointIndex[i] as number);
+          }
+        }
+      } else {
+        xAxisPoints.forEach(function (item, index) {
+          if (currentPoints.x + offset + spacing > item) {
+            current.index = index;
+          }
+        });
+      }
+    }
+  }
+  return current;
+}
+
+/**
+ * 查找柱状图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param calPoints 计算后的数据点
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param offset 偏移量
+ * @returns 当前索引和分组信息
+ */
+export function findBarChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  calPoints: Array<Array<{ x: number; y: number }>>,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  offset: number = 0
+): { index: number; group: number[] } {
+  const current: { index: number; group: number[] } = { index: -1, group: [] };
+  const spacing = opts.chartData.eachSpacing / 2;
+  const yAxisPoints = opts.chartData.yAxisPoints;
+
+  if (calPoints && calPoints.length > 0) {
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      yAxisPoints.forEach(function (item, index) {
+        if (currentPoints.y + offset + spacing > item) {
+          current.index = index;
+        }
+      });
+    }
+  }
+  return current;
+}
+
+/**
+ * 查找图例索引
+ * @param currentPoints 当前触摸点坐标
+ * @param legendData 图例数据
+ * @param opts 图表配置选项
+ * @returns 图例索引
+ */
+export function findLegendIndex(
+  currentPoints: { x: number; y: number },
+  legendData: LegendData,
+  opts: ChartOptions
+): number {
+  let currentIndex = -1;
+  const gap = 0;
+
+  if (isInExactLegendArea(currentPoints, legendData.area)) {
+    const points = legendData.points;
+    let index = -1;
+    for (let i = 0, len = points.length; i < len; i++) {
+      const item = points[i];
+      for (let j = 0; j < item.length; j++) {
+        index += 1;
+        const area = item[j]['area'];
+        if (
+          area &&
+          currentPoints.x > area[0] - gap &&
+          currentPoints.x < area[2] + gap &&
+          currentPoints.y > area[1] - gap &&
+          currentPoints.y < area[3] + gap
+        ) {
+          currentIndex = index;
+          break;
+        }
+      }
+    }
+    return currentIndex;
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找雷达图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param radarData 雷达图数据
+ * @param count 数据数量
+ * @returns 当前索引
+ */
+export function findRadarChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  radarData: RadarData,
+  count: number
+): number {
+  const eachAngleArea = (2 * Math.PI) / count;
+  let currentIndex = -1;
+
+  if (
+    isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)
+  ) {
+    const fixAngle = function (angle: number): number {
+      if (angle < 0) {
+        angle += 2 * Math.PI;
+      }
+      if (angle > 2 * Math.PI) {
+        angle -= 2 * Math.PI;
+      }
+      return angle;
+    };
+
+    let angle = Math.atan2(
+      radarData.center.y - currentPoints.y,
+      currentPoints.x - radarData.center.x
+    );
+    angle = -1 * angle;
+    if (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+
+    const angleList = radarData.angleList.map(function (item) {
+      return fixAngle(-1 * item);
+    });
+
+    angleList.forEach(function (item, index) {
+      const rangeStart = fixAngle(item - eachAngleArea / 2);
+      const rangeEnd = fixAngle(item + eachAngleArea / 2);
+      let rangeEndAdjusted = rangeEnd;
+      if (rangeEndAdjusted < rangeStart) {
+        rangeEndAdjusted += 2 * Math.PI;
+      }
+      if (
+        (angle >= rangeStart && angle <= rangeEndAdjusted) ||
+        (angle + 2 * Math.PI >= rangeStart &&
+          angle + 2 * Math.PI <= rangeEndAdjusted)
+      ) {
+        currentIndex = index;
+      }
+    });
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找漏斗图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param funnelData 漏斗图数据
+ * @returns 当前索引
+ */
+export function findFunnelChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  funnelData: { series: Array<{ funnelArea: number[] }> }
+): number {
+  let currentIndex = -1;
+  for (let i = 0, len = funnelData.series.length; i < len; i++) {
+    const item = funnelData.series[i];
+    if (
+      currentPoints.x > item.funnelArea[0] &&
+      currentPoints.x < item.funnelArea[2] &&
+      currentPoints.y > item.funnelArea[1] &&
+      currentPoints.y < item.funnelArea[3]
+    ) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找词云图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param wordData 词云图数据
+ * @returns 当前索引
+ */
+export function findWordChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  wordData: Array<{ area: number[] }>
+): number {
+  let currentIndex = -1;
+  for (let i = 0, len = wordData.length; i < len; i++) {
+    const item = wordData[i];
+    if (
+      currentPoints.x > item.area[0] &&
+      currentPoints.x < item.area[2] &&
+      currentPoints.y > item.area[1] &&
+      currentPoints.y < item.area[3]
+    ) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找地图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param opts 图表配置选项
+ * @returns 当前索引
+ */
+export function findMapChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  opts: ChartOptions
+): number {
+  let currentIndex = -1;
+  const cData = opts.chartData.mapData;
+  const data = opts.series;
+  const tmp = pointToCoordinate(
+    currentPoints.y,
+    currentPoints.x,
+    cData.bounds,
+    cData.scale,
+    cData.xoffset,
+    cData.yoffset
+  );
+  const poi = [tmp.x, tmp.y];
+
+  for (let i = 0, len = data.length; i < len; i++) {
+    const item = data[i].geometry.coordinates;
+    if (isPoiWithinPoly(poi, item, opts.chartData.mapData.mercator)) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找玫瑰图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param pieData 饼图数据
+ * @param opts 图表配置选项
+ * @returns 当前索引
+ */
+export function findRoseChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  pieData: PieData,
+  opts: ChartOptions
+): number {
+  let currentIndex = -1;
+  const series = getRoseDataPoints(
+    opts._series_,
+    opts.extra.rose?.type || 'area',
+    pieData.radius,
+    pieData.radius
+  );
+
+  if (
+    pieData &&
+    pieData.center &&
+    isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)
+  ) {
+    let angle = Math.atan2(
+      pieData.center.y - currentPoints.y,
+      currentPoints.x - pieData.center.x
+    );
+    angle = -angle;
+    if (opts.extra.rose && opts.extra.rose.offsetAngle) {
+      angle = angle - (opts.extra.rose.offsetAngle * Math.PI) / 180;
+    }
+    for (let i = 0, len = series.length; i < len; i++) {
+      if (
+        isInAngleRange(
+          angle,
+          series[i]._start_,
+          series[i]._start_ + series[i]._rose_proportion_ * 2 * Math.PI
+        )
+      ) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}
+
+/**
+ * 查找饼图当前索引
+ * @param currentPoints 当前触摸点坐标
+ * @param pieData 饼图数据
+ * @param opts 图表配置选项
+ * @returns 当前索引
+ */
+export function findPieChartCurrentIndex(
+  currentPoints: { x: number; y: number },
+  pieData: PieData,
+  opts: ChartOptions
+): number {
+  let currentIndex = -1;
+  const series = getPieDataPoints(pieData.series);
+
+  if (
+    pieData &&
+    pieData.center &&
+    isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)
+  ) {
+    let angle = Math.atan2(
+      pieData.center.y - currentPoints.y,
+      currentPoints.x - pieData.center.x
+    );
+    angle = -angle;
+    if (opts.extra.pie && opts.extra.pie.offsetAngle) {
+      angle = angle - (opts.extra.pie.offsetAngle * Math.PI) / 180;
+    }
+    if (opts.extra.ring && opts.extra.ring.offsetAngle) {
+      angle = angle - (opts.extra.ring.offsetAngle * Math.PI) / 180;
+    }
+    for (let i = 0, len = series.length; i < len; i++) {
+      if (
+        isInAngleRange(
+          angle,
+          series[i]._start_,
+          series[i]._start_ + series[i]._proportion_ * 2 * Math.PI
+        )
+      ) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}

+ 16 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/index.ts

@@ -0,0 +1,16 @@
+/**
+ * 辅助函数模块统一导出
+ * 提供所有辅助函数的统一访问入口
+ */
+
+// 导出所有函数
+export * from './index-finders';
+export * from './area-checkers';
+export * from './data-helpers';
+export * from './legend-helpers';
+export * from './coordinate-helpers';
+export * from './data-fixers';
+export * from './misc-helpers';
+
+// 导出类型
+export * from './types';

+ 231 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/legend-helpers.ts

@@ -0,0 +1,231 @@
+/**
+ * 图例相关函数模块
+ * 用于计算图例数据和饼图相关文本长度
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/index';
+import { measureText } from '../utils/text';
+import { getPieDataPoints } from '../charts-data/pie-charts';
+import type { LegendData, LegendArea } from './types';
+import { util } from '../config';
+
+// 使用 any 类型来简化与原始 u-charts 代码的兼容性
+type AnyChartOptions = any;
+
+/**
+ * 计算图例数据
+ * @param series 数据系列
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param chartData 图表数据
+ * @param context Canvas上下文
+ * @returns 图例数据
+ */
+export function calLegendData(
+  series: SeriesItem[],
+  opts: AnyChartOptions,
+  config: UChartsConfig,
+  chartData: any,
+  context: any
+): LegendData {
+  const legendData: LegendData = {
+    area: {
+      start: { x: 0, y: 0 },
+      end: { x: 0, y: 0 },
+    } as LegendArea,
+    points: [],
+    widthArr: [],
+    heightArr: [],
+  };
+
+  if (opts.legend.show === false) {
+    chartData.legendData = legendData;
+    return legendData;
+  }
+
+  const padding = opts.legend.padding * opts.pix;
+  const margin = opts.legend.margin * opts.pix;
+  const fontSize = opts.legend.fontSize
+    ? opts.legend.fontSize * opts.pix
+    : config.fontSize;
+  const shapeWidth = 15 * opts.pix;
+  const shapeRight = 5 * opts.pix;
+  const lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);
+
+  if (opts.legend.position === 'top' || opts.legend.position === 'bottom') {
+    const legendList: SeriesItem[][] = [];
+    let widthCount = 0;
+    const widthCountArr: number[] = [];
+    const currentRow: SeriesItem[] = [];
+
+    for (let i = 0; i < series.length; i++) {
+      const item = series[i];
+      const legendText = item.legendText ? item.legendText : item.name;
+      const itemWidth =
+        shapeWidth +
+        shapeRight +
+        measureText(legendText || 'undefined', fontSize, context) +
+        opts.legend.itemGap * opts.pix;
+
+      if (widthCount + itemWidth > opts.width - opts.area[1] - opts.area[3]) {
+        legendList.push(currentRow.slice());
+        widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+        widthCount = itemWidth;
+        currentRow.length = 0;
+        currentRow.push(item);
+      } else {
+        widthCount += itemWidth;
+        currentRow.push(item);
+      }
+    }
+
+    if (currentRow.length) {
+      legendList.push(currentRow.slice());
+      widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+      legendData.widthArr = widthCountArr;
+      const legendWidth = Math.max.apply(null, widthCountArr);
+
+      switch (opts.legend.float) {
+        case 'left':
+          legendData.area.start.x = opts.area[3];
+          legendData.area.end.x = opts.area[3] + legendWidth + 2 * padding;
+          break;
+        case 'right':
+          legendData.area.start.x =
+            opts.width - opts.area[1] - legendWidth - 2 * padding;
+          legendData.area.end.x = opts.width - opts.area[1];
+          break;
+        default:
+          legendData.area.start.x = opts.width / 2 - legendWidth / 2 - padding;
+          legendData.area.end.x = opts.width / 2 + legendWidth / 2 + padding;
+      }
+
+      legendData.area.width = legendWidth + 2 * padding;
+      legendData.area.wholeWidth = legendWidth + 2 * padding;
+      legendData.area.height = legendList.length * lineHeight + 2 * padding;
+      legendData.area.wholeHeight =
+        legendList.length * lineHeight + 2 * padding + 2 * margin;
+      legendData.points = legendList;
+    }
+  } else {
+    const len = series.length;
+    const maxHeight =
+      opts.height - opts.area[0] - opts.area[2] - 2 * margin - 2 * padding;
+    const maxLength = Math.min(Math.floor(maxHeight / lineHeight), len);
+    legendData.area.height = maxLength * lineHeight + padding * 2;
+    legendData.area.wholeHeight = maxLength * lineHeight + padding * 2;
+
+    switch (opts.legend.float) {
+      case 'top':
+        legendData.area.start.y = opts.area[0] + margin;
+        legendData.area.end.y =
+          opts.area[0] + margin + legendData.area.height;
+        break;
+      case 'bottom':
+        legendData.area.start.y =
+          opts.height - opts.area[2] - margin - legendData.area.height;
+        legendData.area.end.y = opts.height - opts.area[2] - margin;
+        break;
+      default:
+        legendData.area.start.y =
+          (opts.height - legendData.area.height) / 2;
+        legendData.area.end.y =
+          (opts.height + legendData.area.height) / 2;
+    }
+
+    const lineNum =
+      len % maxLength === 0
+        ? len / maxLength
+        : Math.floor(len / maxLength + 1);
+    const currentRow: SeriesItem[][] = [];
+
+    for (let i = 0; i < lineNum; i++) {
+      const temp = series.slice(i * maxLength, i * maxLength + maxLength);
+      currentRow.push(temp);
+    }
+
+    legendData.points = currentRow;
+
+    if (currentRow.length) {
+      for (let i = 0; i < currentRow.length; i++) {
+        const item = currentRow[i];
+        let maxWidth = 0;
+        for (let j = 0; j < item.length; j++) {
+          const itemWidth =
+            shapeWidth +
+            shapeRight +
+            measureText(item[j].name || 'undefined', fontSize, context) +
+            opts.legend.itemGap * opts.pix;
+          if (itemWidth > maxWidth) {
+            maxWidth = itemWidth;
+          }
+        }
+        legendData.widthArr.push(maxWidth);
+        legendData.heightArr.push(item.length * lineHeight + padding * 2);
+      }
+
+      let legendWidth = 0;
+      for (let i = 0; i < legendData.widthArr.length; i++) {
+        legendWidth += legendData.widthArr[i];
+      }
+      legendData.area.width =
+        legendWidth - opts.legend.itemGap * opts.pix + 2 * padding;
+      legendData.area.wholeWidth = legendData.area.width + padding;
+    }
+  }
+
+  switch (opts.legend.position) {
+    case 'top':
+      legendData.area.start.y = opts.area[0] + margin;
+      legendData.area.end.y = opts.area[0] + margin + legendData.area.height;
+      break;
+    case 'bottom':
+      legendData.area.start.y =
+        opts.height - opts.area[2] - legendData.area.height - margin;
+      legendData.area.end.y = opts.height - opts.area[2] - margin;
+      break;
+    case 'left':
+      legendData.area.start.x = opts.area[3];
+      legendData.area.end.x = opts.area[3] + legendData.area.width;
+      break;
+    case 'right':
+      legendData.area.start.x =
+        opts.width - opts.area[1] - legendData.area.width;
+      legendData.area.end.x = opts.width - opts.area[1];
+      break;
+  }
+
+  chartData.legendData = legendData;
+  return legendData;
+}
+
+/**
+ * 获取饼图文本最大长度
+ * @param series 数据系列
+ * @param config 图表配置
+ * @param context Canvas上下文
+ * @param opts 图表配置选项
+ * @returns 最大长度
+ */
+export function getPieTextMaxLength(
+  series: SeriesItem[],
+  config: UChartsConfig,
+  context: any,
+  opts: AnyChartOptions
+): number {
+  const processedSeries = getPieDataPoints(series);
+  let maxLength = 0;
+  for (let i = 0; i < processedSeries.length; i++) {
+    const item = processedSeries[i];
+    const text = item.formatter
+      ? item.formatter(+item._proportion_.toFixed(2))
+      : util.toFixed(item._proportion_ * 100) + '%';
+    maxLength = Math.max(
+      maxLength,
+      measureText(text, item.textSize * opts.pix || config.fontSize, context)
+    );
+  }
+  return maxLength;
+}

+ 462 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/misc-helpers.ts

@@ -0,0 +1,462 @@
+/**
+ * 其他辅助函数模块
+ * 包含轴文本列表、tooltip数据、标记线数据、上下文旋转、随机数、碰撞检测和词云点计算等辅助函数
+ */
+
+// @ts-nocheck - 为了与原始 u-charts 代码保持兼容性,跳过类型检查
+
+import type { ChartOptions, SeriesItem } from '../data-processing/index';
+import { measureText } from '../utils/text';
+import { dataCombine, dataCombineStack, getDataRange } from '../data-processing/index';
+
+// 使用 any 类型来简化与原始 u-charts 代码的兼容性
+type AnyChartOptions = any;
+
+/**
+ * 获取X轴文本列表
+ * @param series 数据系列
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param stack 是否堆叠
+ * @param index Y轴索引
+ * @returns 文本列表
+ */
+export function getXAxisTextList(
+  series: SeriesItem[],
+  opts: AnyChartOptions,
+  config: any,
+  stack: string,
+  index: number = -1
+): number[] {
+  let data: any[];
+  if (stack === 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+
+  const sorted: any[] = [];
+  // remove null from data
+  const filteredData = data.filter(function (item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+
+  filteredData.forEach(function (item) {
+    if (typeof item === 'object') {
+      if (Array.isArray(item)) {
+        if (opts.type === 'candle') {
+          item.forEach(function (subitem: any) {
+            sorted.push(subitem);
+          });
+        } else {
+          sorted.push(item[0]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  });
+
+  let minData = 0;
+  let maxData = 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(null, sorted);
+    maxData = Math.max.apply(null, sorted);
+  }
+
+  // 为了兼容v1.9.0之前的项目
+  if (index > -1) {
+    if (typeof opts.xAxis.data[index].min === 'number') {
+      minData = Math.min(opts.xAxis.data[index].min, minData);
+    }
+    if (typeof opts.xAxis.data[index].max === 'number') {
+      maxData = Math.max(opts.xAxis.data[index].max, maxData);
+    }
+  } else {
+    if (typeof opts.xAxis.min === 'number') {
+      minData = Math.min(opts.xAxis.min, minData);
+    }
+    if (typeof opts.xAxis.max === 'number') {
+      maxData = Math.max(opts.xAxis.max, maxData);
+    }
+  }
+
+  if (minData === maxData) {
+    const rangeSpan = maxData || 10;
+    maxData += rangeSpan;
+  }
+
+  const minRange = minData;
+  const maxRange = maxData;
+  const range: number[] = [];
+  const eachRange = (maxRange - minRange) / opts.xAxis.splitNumber;
+  for (let i = 0; i <= opts.xAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range;
+}
+
+/**
+ * 获取Y轴文本列表
+ * @param series 数据系列
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param stack 是否堆叠
+ * @param yData Y轴数据
+ * @param index Y轴索引
+ * @returns 文本列表
+ */
+export function getYAxisTextList(
+  series: SeriesItem[],
+  opts: AnyChartOptions,
+  config: any,
+  stack: string,
+  yData: { min?: number; max?: number },
+  index: number = -1
+): number[] {
+  let data: any[];
+  if (stack === 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+
+  const sorted: any[] = [];
+  // remove null from data
+  const filteredData = data.filter(function (item) {
+    if (typeof item === 'object' && item !== null) {
+      if (Array.isArray(item)) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+
+  filteredData.forEach(function (item) {
+    if (typeof item === 'object') {
+      if (Array.isArray(item)) {
+        if (opts.type === 'candle') {
+          item.forEach(function (subitem: any) {
+            sorted.push(subitem);
+          });
+        } else {
+          sorted.push(item[1]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  });
+
+  let minData = yData.min || 0;
+  let maxData = yData.max || 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(null, sorted);
+    maxData = Math.max.apply(null, sorted);
+  }
+
+  if (minData === maxData) {
+    if (maxData === 0) {
+      maxData = 10;
+    } else {
+      minData = 0;
+    }
+  }
+
+  const dataRange = getDataRange(minData, maxData);
+  const minRange =
+    yData.min === undefined || yData.min === null
+      ? dataRange.minRange
+      : yData.min;
+  const maxRange =
+    yData.max === undefined || yData.max === null
+      ? dataRange.maxRange
+      : yData.max;
+  const eachRange = (maxRange - minRange) / opts.yAxis.splitNumber;
+  const range: number[] = [];
+  for (let i = 0; i <= opts.yAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range.reverse();
+}
+
+/**
+ * 计算提示框Y轴数据
+ * @param point 当前触摸点坐标
+ * @param series 数据系列
+ * @param opts 图表配置选项
+ * @param config 图表配置
+ * @param eachSpacing 每个数据点的间距
+ * @returns Y轴数据数组
+ */
+export function calTooltipYAxisData(
+  point: { x: number; y: number },
+  series: SeriesItem[],
+  opts: AnyChartOptions,
+  config: any,
+  eachSpacing: number
+): string[] {
+  const ranges: number[][] = [].concat(opts.chartData.yAxisData.ranges);
+  const spacingValid = opts.height - opts.area[0] - opts.area[2];
+  const minAxis = opts.area[0];
+  const items: string[] = [];
+
+  for (let i = 0; i < ranges.length; i++) {
+    const maxVal = Math.max.apply(null, ranges[i]);
+    const minVal = Math.min.apply(null, ranges[i]);
+    let item = maxVal - ((maxVal - minVal) * (point.y - minAxis)) / spacingValid;
+    item =
+      opts.yAxis.data && opts.yAxis.data[i].formatter
+        ? opts.yAxis.data[i].formatter(item, i, opts)
+        : item.toFixed(0);
+    items.push(String(item));
+  }
+  return items;
+}
+
+/**
+ * 计算标记线数据
+ * @param points 标记线点数组
+ * @param opts 图表配置选项
+ * @returns 标记线数据数组
+ */
+export function calMarkLineData(
+  points: Array<{ value: number; yAxisIndex?: number; y?: number }>,
+  opts: AnyChartOptions
+): Array<{ value: number; yAxisIndex?: number; y?: number }> {
+  let minRange: number, maxRange: number;
+  const spacingValid = opts.height - opts.area[0] - opts.area[2];
+
+  for (let i = 0; i < points.length; i++) {
+    points[i].yAxisIndex = points[i].yAxisIndex ? points[i].yAxisIndex : 0;
+    const range = [].concat(opts.chartData.yAxisData.ranges[points[i].yAxisIndex!]);
+    minRange = range.pop() as number;
+    maxRange = range.shift() as number;
+    const height =
+      (spacingValid * (points[i].value - minRange)) / (maxRange - minRange);
+    points[i].y = opts.height - Math.round(height) - opts.area[2];
+  }
+  return points;
+}
+
+/**
+ * 上下文旋转
+ * @param context Canvas上下文
+ * @param opts 图表配置选项
+ */
+export function contextRotate(
+  context: any,
+  opts: AnyChartOptions
+): void {
+  if (opts.rotateLock !== true) {
+    context.translate(opts.height, 0);
+    context.rotate((90 * Math.PI) / 180);
+  } else if (opts._rotate_ !== true) {
+    context.translate(opts.height, 0);
+    context.rotate((90 * Math.PI) / 180);
+    opts._rotate_ = true;
+  }
+}
+
+/**
+ * 生成标准化整数随机数
+ * @param min 最小值
+ * @param max 最大值
+ * @param iter 迭代次数
+ * @returns 随机整数
+ */
+export function normalInt(min: number, max: number, iter: number): number {
+  iter = iter === 0 ? 1 : iter;
+  const arr: number[] = [];
+  for (let i = 0; i < iter; i++) {
+    arr[i] = Math.random();
+  }
+  return (
+    Math.floor((arr.reduce((i, j) => i + j) / iter) * (max - min)) + min
+  );
+}
+
+/**
+ * 新碰撞检测
+ * @param area 区域 [x1, y1, x2, y2]
+ * @param points 点数组
+ * @param width 宽度
+ * @param height 高度
+ * @returns 是否碰撞
+ */
+export function collisionNew(
+  area: number[],
+  points: Array<{ area?: number[] }>,
+  width: number,
+  height: number
+): boolean {
+  let isIn = false;
+  for (let i = 0; i < points.length; i++) {
+    if (points[i].area) {
+      if (
+        area[3] < points[i].area![1] ||
+        area[0] > points[i].area![2] ||
+        area[1] > points[i].area![3] ||
+        area[2] < points[i].area![0]
+      ) {
+        if (
+          area[0] < 0 ||
+          area[1] < 0 ||
+          area[2] > width ||
+          area[3] > height
+        ) {
+          isIn = true;
+          break;
+        } else {
+          isIn = false;
+        }
+      } else {
+        isIn = true;
+        break;
+      }
+    }
+  }
+  return isIn;
+}
+
+/**
+ * 获取词云点
+ * @param opts 图表配置选项
+ * @param type 类型 ('normal' | 'vertical')
+ * @param context Canvas上下文
+ * @returns 词云数据点数组
+ */
+export function getWordCloudPoint(
+  opts: AnyChartOptions,
+  type: 'normal' | 'vertical',
+  context: any
+): Array<{
+  name: string;
+  textSize: number;
+  area: number[];
+  areav?: number[];
+  rotate?: boolean;
+}> {
+  const points = opts.series;
+
+  switch (type) {
+    case 'normal':
+      for (let i = 0; i < points.length; i++) {
+        const text = points[i].name;
+        const tHeight = points[i].textSize * opts.pix;
+        const tWidth = measureText(text, tHeight, context);
+        let x: number, y: number;
+        let area: number[];
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+          y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+          area = [
+            x - 5 + opts.width / 2,
+            y - 5 - tHeight + opts.height / 2,
+            x + tWidth + 5 + opts.width / 2,
+            y + 5 + opts.height / 2,
+          ];
+          const isCollision = collisionNew(area, points, opts.width, opts.height);
+          if (!isCollision) break;
+          if (breaknum === 1000) {
+            area = [-100, -100, -100, -100];
+            break;
+          }
+        }
+        (points[i] as any).area = area;
+      }
+      break;
+
+    case 'vertical':
+      function Spin(): boolean {
+        // 获取均匀随机值,是否旋转,旋转的概率为(1-0.5)
+        if (Math.random() > 0.7) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+      for (let i = 0; i < points.length; i++) {
+        const text = points[i].name;
+        const tHeight = points[i].textSize * opts.pix;
+        const tWidth = measureText(text, tHeight, context);
+        const isSpin = Spin();
+        let x: number, y: number, area: number[], areav: number[] | undefined;
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          let isCollision: boolean;
+          if (isSpin) {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [
+              y - 5 - tWidth + opts.width / 2,
+              -x - 5 + opts.height / 2,
+              y + 5 + opts.width / 2,
+              -x + tHeight + 5 + opts.height / 2,
+            ];
+            areav = [
+              opts.width -
+                (opts.width / 2 - opts.height / 2) -
+                (-x + tHeight + 5 + opts.height / 2) -
+                5,
+              (opts.height / 2 - opts.width / 2) +
+                (y - 5 - tWidth + opts.width / 2) -
+                5,
+              opts.width -
+                (opts.width / 2 - opts.height / 2) -
+                (-x + tHeight + 5 + opts.height / 2) +
+                tHeight,
+              (opts.height / 2 - opts.width / 2) +
+                (y - 5 - tWidth + opts.width / 2) +
+                tWidth +
+                5,
+            ];
+            isCollision = collisionNew(areav, points, opts.height, opts.width);
+          } else {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [
+              x - 5 + opts.width / 2,
+              y - 5 - tHeight + opts.height / 2,
+              x + tWidth + 5 + opts.width / 2,
+              y + 5 + opts.height / 2,
+            ];
+            isCollision = collisionNew(area, points, opts.width, opts.height);
+          }
+          if (!isCollision) break;
+          if (breaknum === 1000) {
+            area = [-1000, -1000, -1000, -1000];
+            break;
+          }
+        }
+        if (isSpin) {
+          (points[i] as any).area = areav;
+          (points[i] as any).areav = area;
+        } else {
+          (points[i] as any).area = area;
+        }
+        (points[i] as any).rotate = isSpin;
+      }
+      break;
+  }
+
+  return points as any;
+}

+ 43 - 0
mini-ui-packages/mini-charts/src/lib/helper-functions/types.ts

@@ -0,0 +1,43 @@
+/**
+ * 辅助函数相关的类型定义
+ */
+
+/**
+ * 图例区域
+ */
+export interface LegendArea {
+  start: { x: number; y: number };
+  end: { x: number; y: number };
+  width?: number;
+  height?: number;
+  wholeWidth?: number;
+  wholeHeight?: number;
+}
+
+/**
+ * 图例数据
+ */
+export interface LegendData {
+  area: LegendArea;
+  points: any[][];
+  widthArr?: number[];
+  heightArr?: number[];
+}
+
+/**
+ * 饼图数据
+ */
+export interface PieData {
+  center: { x: number; y: number };
+  radius: number;
+  series: any[];
+}
+
+/**
+ * 雷达图数据
+ */
+export interface RadarData {
+  center: { x: number; y: number };
+  radius: number;
+  angleList: number[];
+}

+ 781 - 0
mini-ui-packages/mini-charts/src/lib/renderers/axis-renderer.ts

@@ -0,0 +1,781 @@
+/**
+ * 坐标轴和图例绘制函数
+ *
+ * 从 u-charts 核心库搬迁的坐标轴和图例绘制相关函数
+ * 用于处理X轴、Y轴、网格线和图例的绘制操作
+ */
+
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { measureText } from '../utils/text';
+import { convertCoordinateOrigin } from '../utils/coordinate';
+import { hexToRgb } from '../utils/color';
+import { assign } from '../config';
+
+// Canvas 上下文类型(使用 any 以兼容小程序环境)
+export type CanvasContext = any;
+
+/**
+ * 坐标点接口
+ */
+export interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * 仪表盘选项接口
+ */
+export interface GaugeOption {
+  width?: number;
+  labelOffset?: number;
+  endAngle?: number;
+  startAngle?: number;
+  splitLine?: {
+    splitNumber?: number;
+  };
+  endNumber?: number;
+  startNumber?: number;
+  formatter?: (val: number, index: number, opts: ChartOptions) => string;
+  labelColor?: string;
+}
+
+/**
+ * 雷达图选项接口
+ */
+export interface RadarOption {
+  labelPointShow?: boolean;
+  labelPointColor?: string;
+  labelPointRadius?: number;
+  labelShow?: boolean;
+  labelColor?: string;
+}
+
+/**
+ * 图例数据项接口
+ */
+export interface LegendItem {
+  name: string;
+  color: string;
+  show?: boolean;
+  legendShape?: string;
+  legendText?: string;
+  area?: number[];
+  [key: string]: any;
+}
+
+/**
+ * 图例数据接口
+ */
+export interface LegendData {
+  points: LegendItem[][];
+  area: {
+    start: Point;
+    width: number;
+    height: number;
+  };
+  widthArr: number[];
+  heightArr: number[];
+}
+
+/**
+ * 绘制X轴
+ * @param categories - X轴分类数据
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawXAxis(
+  categories: string[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let xAxisData = opts.chartData?.xAxisData;
+  if (!xAxisData) return;
+
+  let xAxisPoints = xAxisData.xAxisPoints;
+  let startX = xAxisData.startX;
+  let endX = xAxisData.endX;
+  let eachSpacing = xAxisData.eachSpacing;
+  let boundaryGap = 'center';
+  if (opts.type == 'bar' || opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble') {
+    boundaryGap = opts.xAxis?.boundaryGap || 'center';
+  }
+  let startY = opts.height! - opts.area![2];
+  let endY = opts.area![0];
+
+  // 绘制滚动条
+  if (opts.enableScroll && opts.xAxis?.scrollShow) {
+    let scrollY = opts.height! - opts.area![2] + config.xAxisHeight;
+    let scrollScreenWidth = endX - startX;
+    let scrollTotalWidth = eachSpacing * (xAxisPoints.length - 1);
+    if (opts.type == 'mount' && opts.extra?.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1) {
+      let widthRatio = opts.extra.mount.widthRatio > 2 ? 2 : opts.extra.mount.widthRatio;
+      scrollTotalWidth += (widthRatio - 1) * eachSpacing;
+    }
+    let scrollWidth = scrollScreenWidth * scrollScreenWidth / scrollTotalWidth;
+    let scrollLeft = 0;
+    if (opts._scrollDistance_) {
+      scrollLeft = -opts._scrollDistance_ * (scrollScreenWidth) / scrollTotalWidth;
+    }
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollBackgroundColor || "#EFEBEF");
+    context.moveTo(startX, scrollY);
+    context.lineTo(endX, scrollY);
+    context.stroke();
+    context.closePath();
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollColor || "#A6A6A6");
+    context.moveTo(startX + scrollLeft, scrollY);
+    context.lineTo(startX + scrollLeft + scrollWidth, scrollY);
+    context.stroke();
+    context.closePath();
+    context.setLineCap('butt');
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  // 绘制X轴刻度线
+  if (opts.xAxis?.calibration === true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    xAxisPoints.forEach(function (item: number, index: number) {
+      if (index > 0) {
+        context.beginPath();
+        context.moveTo(item - eachSpacing / 2, startY);
+        context.lineTo(item - eachSpacing / 2, startY + 3 * opts.pix);
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+  // 绘制X轴网格
+  if (opts.xAxis && opts.xAxis.disableGrid !== true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    if (opts.xAxis.gridType == 'dash') {
+      const dashLength = opts.xAxis.dashLength || 4;
+      context.setLineDash([dashLength * opts.pix, dashLength * opts.pix]);
+    }
+    let gridEval = opts.xAxis.gridEval || 1;
+    xAxisPoints.forEach(function (item: number, index: number) {
+      if (index % gridEval == 0) {
+        context.beginPath();
+        context.moveTo(item, startY);
+        context.lineTo(item, endY);
+        context.stroke();
+      }
+    });
+    context.setLineDash([]);
+  }
+  // 绘制X轴文案
+  if (opts.xAxis && opts.xAxis.disabled !== true) {
+    // 对X轴列表做抽稀处理
+    // 默认全部显示X轴标签
+    let maxXAxisListLength = categories.length;
+    // 如果设置了X轴单屏数量
+    if (opts.xAxis.labelCount) {
+      // 如果设置X轴密度
+      if (opts.xAxis.itemCount) {
+        maxXAxisListLength = Math.ceil(categories.length / opts.xAxis.itemCount * opts.xAxis.labelCount);
+      } else {
+        maxXAxisListLength = opts.xAxis.labelCount;
+      }
+      maxXAxisListLength -= 1;
+    }
+
+    let ratio = Math.ceil(categories.length / maxXAxisListLength);
+
+    let newCategories: string[] = [];
+    let cgLength = categories.length;
+    for (let i = 0; i < cgLength; i++) {
+      if (i % ratio !== 0) {
+        newCategories.push("");
+      } else {
+        newCategories.push(categories[i]);
+      }
+    }
+    newCategories[cgLength - 1] = categories[cgLength - 1];
+    let xAxisFontSize = (opts.xAxis!.fontSize || config.fontSize) * opts.pix;
+    if (config._xAxisTextAngle_ === 0) {
+      newCategories.forEach(function (item, index) {
+        let xitem = opts.xAxis!.formatter ? opts.xAxis!.formatter!(item, index, opts) : item;
+        let offset = -measureText(String(xitem), xAxisFontSize, context) / 2;
+        if (boundaryGap == 'center') {
+          offset += eachSpacing / 2;
+        }
+        let scrollHeight = 0;
+        if (opts.xAxis!.scrollShow) {
+          scrollHeight = 6 * opts.pix;
+        }
+        // 如果在主视图区域内
+        let _scrollDistance_ = opts._scrollDistance_ || 0;
+        let truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if ((truePoints - Math.abs(_scrollDistance_)) >= (opts.area![3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width! - opts.area![1] + 1)) {
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis!.fontColor || opts.fontColor);
+          const marginTop = opts.xAxis!.marginTop || 0;
+          const lineHeight = opts.xAxis!.lineHeight || config.fontSize;
+          const xAxisFontSizeVal = opts.xAxis!.fontSize || config.fontSize;
+          context.fillText(String(xitem), xAxisPoints[index] + offset, startY + marginTop * opts.pix + (lineHeight - xAxisFontSizeVal) * opts.pix / 2 + xAxisFontSizeVal * opts.pix);
+          context.closePath();
+          context.stroke();
+        }
+      });
+    } else {
+      newCategories.forEach(function (item, index) {
+        let xitem = opts.xAxis!.formatter ? opts.xAxis!.formatter!(item, index, opts) : item;
+        // 如果在主视图区域内
+        let _scrollDistance_ = opts._scrollDistance_ || 0;
+        let truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if ((truePoints - Math.abs(_scrollDistance_)) >= (opts.area![3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width! - opts.area![1] + 1)) {
+          context.save();
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis!.fontColor || opts.fontColor);
+          let textWidth = measureText(String(xitem), xAxisFontSize, context);
+          let offsetX = xAxisPoints[index];
+          if (boundaryGap == 'center') {
+            offsetX = xAxisPoints[index] + eachSpacing / 2;
+          }
+          let scrollHeight = 0;
+          if (opts.xAxis!.scrollShow) {
+            scrollHeight = 6 * opts.pix;
+          }
+          const marginTop = opts.xAxis!.marginTop || 0;
+          let offsetY = startY + marginTop * opts.pix + xAxisFontSize - xAxisFontSize * Math.abs(Math.sin(config._xAxisTextAngle_!));
+          const rotateAngle = opts.xAxis!.rotateAngle || 0;
+          if (rotateAngle < 0) {
+            offsetX -= xAxisFontSize / 2;
+            textWidth = 0;
+          } else {
+            offsetX += xAxisFontSize / 2;
+            textWidth = -textWidth;
+          }
+          context.translate(offsetX, offsetY);
+          context.rotate(-1 * config._xAxisTextAngle_!);
+          context.fillText(String(xitem), textWidth, 0);
+          context.closePath();
+          context.stroke();
+          context.restore();
+        }
+      });
+    }
+  }
+  context.restore();
+
+  // 画X轴标题
+  if (opts.xAxis && opts.xAxis.title) {
+    context.beginPath();
+    const titleFontSize = opts.xAxis!.titleFontSize || config.fontSize;
+    context.setFontSize(titleFontSize * opts.pix);
+    context.setFillStyle(opts.xAxis!.titleFontColor!);
+    const titleOffsetX = opts.xAxis!.titleOffsetX || 0;
+    const marginTop = opts.xAxis!.marginTop || 0;
+    const lineHeight = opts.xAxis!.lineHeight || titleFontSize;
+    const titleOffsetY = opts.xAxis!.titleOffsetY || 0;
+    context.fillText(String(opts.xAxis.title), opts.width! - opts.area![1] + titleOffsetX * opts.pix, opts.height! - opts.area![2] + marginTop * opts.pix + (lineHeight - titleFontSize) * opts.pix / 2 + (titleFontSize + titleOffsetY) * opts.pix);
+    context.closePath();
+    context.stroke();
+  }
+
+  // 绘制X轴轴线
+  if (opts.xAxis && opts.xAxis.axisLine) {
+    context.beginPath();
+    context.setStrokeStyle(opts.xAxis!.axisLineColor!);
+    context.setLineWidth(1 * opts.pix);
+    context.moveTo(startX, opts.height! - opts.area![2]);
+    context.lineTo(endX, opts.height! - opts.area![2]);
+    context.stroke();
+  }
+}
+
+/**
+ * 绘制Y轴网格
+ * @param categories - Y轴分类数据
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawYAxisGrid(
+  categories: string[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  if (opts.yAxis?.disableGrid === true) {
+    return;
+  }
+  let spacingValid = opts.height! - opts.area![0] - opts.area![2];
+  let eachSpacing = spacingValid / (opts.yAxis!.splitNumber || 5);
+  let startX = opts.area![3];
+  let xAxisPoints = opts.chartData?.xAxisData?.xAxisPoints || [];
+  let xAxiseachSpacing = opts.chartData?.xAxisData?.eachSpacing || 0;
+  let TotalWidth = xAxiseachSpacing * (xAxisPoints.length - 1);
+  if (opts.type == 'mount' && opts.extra?.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1) {
+    let widthRatio = opts.extra.mount.widthRatio > 2 ? 2 : opts.extra.mount.widthRatio;
+    TotalWidth += (widthRatio - 1) * xAxiseachSpacing;
+  }
+  let endX = startX + TotalWidth;
+  let points: number[] = [];
+  let startY = 1;
+  if (opts.xAxis!.axisLine === false) {
+    startY = 0;
+  }
+  for (let i = startY; i < (opts.yAxis!.splitNumber || 5) + 1; i++) {
+    points.push(opts.height! - opts.area![2] - eachSpacing * i);
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.yAxis!.gridType == 'dash') {
+    context.setLineDash([(opts.yAxis!.dashLength || 4) * opts.pix, (opts.yAxis!.dashLength || 4) * opts.pix]);
+  }
+  context.setStrokeStyle(opts.yAxis!.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  points.forEach(function (item, index) {
+    context.beginPath();
+    context.moveTo(startX, item);
+    context.lineTo(endX, item);
+    context.stroke();
+  });
+  context.setLineDash([]);
+  context.restore();
+}
+
+/**
+ * 绘制Y轴
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawYAxis(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  if (opts.yAxis?.disabled === true) {
+    return;
+  }
+  let spacingValid = opts.height! - opts.area![0] - opts.area![2];
+  let eachSpacing = spacingValid / (opts.yAxis!.splitNumber || 5);
+  let startX = opts.area![3];
+  let endX = opts.width! - opts.area![1];
+  let endY = opts.height! - opts.area![2];
+  // set YAxis background
+  context.beginPath();
+  context.setFillStyle(opts.background || '#ffffff');
+  if (opts.enableScroll == true && opts.xAxis!.scrollPosition && opts.xAxis!.scrollPosition !== 'left') {
+    context.fillRect(0, 0, startX, endY + 2 * opts.pix);
+  }
+  if (opts.enableScroll == true && opts.xAxis!.scrollPosition && opts.xAxis!.scrollPosition !== 'right') {
+    context.fillRect(endX, 0, opts.width!, endY + 2 * opts.pix);
+  }
+  context.closePath();
+  context.stroke();
+
+  let tStartLeft = opts.area![3];
+  let tStartRight = opts.width! - opts.area![1];
+  let tStartCenter = opts.area![3] + (opts.width! - opts.area![1] - opts.area![3]) / 2;
+  if (opts.yAxis!.data) {
+    for (let i = 0; i < opts.yAxis!.data.length; i++) {
+      let yData = opts.yAxis!.data[i];
+      let points: number[] = [];
+      if (yData.type === 'categories') {
+        for (let j = 0; j <= (yData.categories?.length || 0); j++) {
+          points.push(opts.area![0] + spacingValid / (yData.categories!.length) / 2 + spacingValid / (yData.categories!.length) * j);
+        }
+      } else {
+        for (let j = 0; j <= (opts.yAxis!.splitNumber || 5); j++) {
+          points.push(opts.area![0] + eachSpacing * j);
+        }
+      }
+      if (yData.disabled !== true) {
+        let rangesFormat = opts.chartData?.yAxisData?.rangesFormat?.[i] || [];
+        let yAxisFontSize = yData.fontSize ? yData.fontSize * opts.pix : config.fontSize;
+        let yAxisWidth = opts.chartData?.yAxisData?.yAxisWidth?.[i];
+        if (!yAxisWidth) continue;
+
+        let textAlign = yData.textAlign || "right";
+        // 画Y轴刻度及文案
+        rangesFormat.forEach(function (item: any, index: any) {
+          let pos = points[index];
+          context.beginPath();
+          context.setFontSize(yAxisFontSize);
+          context.setLineWidth(1 * opts.pix);
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setFillStyle(yData.fontColor || opts.fontColor);
+          let tmpstrat = 0;
+          let gapwidth = 4 * opts.pix;
+          if (yAxisWidth.position == 'left') {
+            // 画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartLeft, pos);
+              context.lineTo(tStartLeft - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            // 画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartLeft - yAxisWidth.width;
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartLeft - gapwidth;
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartLeft - yAxisWidth.width / 2;
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+
+          } else if (yAxisWidth.position == 'right') {
+            // 画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartRight, pos);
+              context.lineTo(tStartRight + 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartRight + gapwidth;
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartRight + yAxisWidth.width;
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartRight + yAxisWidth.width / 2;
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            // 画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartCenter, pos);
+              context.lineTo(tStartCenter - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            // 画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartCenter - yAxisWidth.width;
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartCenter - gapwidth;
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartCenter - yAxisWidth.width / 2;
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+          context.setTextAlign('left');
+        });
+        // 画Y轴轴线
+        if (yData.axisLine !== false) {
+          context.beginPath();
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setLineWidth(1 * opts.pix);
+          if (yAxisWidth.position == 'left') {
+            context.moveTo(tStartLeft, opts.height! - opts.area![2]);
+            context.lineTo(tStartLeft, opts.area![0]);
+          } else if (yAxisWidth.position == 'right') {
+            context.moveTo(tStartRight, opts.height! - opts.area![2]);
+            context.lineTo(tStartRight, opts.area![0]);
+          } else if (yAxisWidth.position == 'center') {
+            context.moveTo(tStartCenter, opts.height! - opts.area![2]);
+            context.lineTo(tStartCenter, opts.area![0]);
+          }
+          context.stroke();
+        }
+        // 画Y轴标题
+        if (opts.yAxis!.showTitle) {
+          let titleFontSize = (yData.titleFontSize || config.fontSize) * opts.pix;
+          let title = yData.title || '';
+          context.beginPath();
+          context.setFontSize(titleFontSize);
+          context.setFillStyle(yData.titleFontColor || opts.fontColor);
+          if (yAxisWidth.position == 'left') {
+            context.fillText(title, tStartLeft - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area![0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'right') {
+            context.fillText(title, tStartRight - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area![0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            context.fillText(title, tStartCenter - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area![0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+        }
+        if (yAxisWidth.position == 'left') {
+          tStartLeft -= (yAxisWidth.width + (opts.yAxis!.padding || 0) * opts.pix);
+        } else {
+          tStartRight += yAxisWidth.width + (opts.yAxis!.padding || 0) * opts.pix;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * 绘制图例
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param chartData - 图表数据对象
+ */
+export function drawLegend(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  chartData: any
+): void {
+  if (opts.legend?.show === false) {
+    return;
+  }
+  let legendData = chartData.legendData as LegendData;
+  let legendList = legendData.points;
+  let legendArea = legendData.area;
+  let padding = (opts.legend.padding || 5) * opts.pix;
+  let fontSize = (opts.legend.fontSize || 12) * opts.pix;
+  let shapeWidth = 15 * opts.pix;
+  let shapeRight = 5 * opts.pix;
+  let itemGap = (opts.legend.itemGap || 10) * opts.pix;
+  let lineHeight = Math.max((opts.legend.lineHeight || 15) * opts.pix, fontSize);
+  // 画背景及边框
+  context.beginPath();
+  context.setLineWidth((opts.legend.borderWidth || 0) * opts.pix);
+  context.setStrokeStyle(opts.legend.borderColor || '#cccccc');
+  context.setFillStyle(opts.legend.backgroundColor || '#ffffff');
+  context.moveTo(legendArea.start.x, legendArea.start.y);
+  context.rect(legendArea.start.x, legendArea.start.y, legendArea.width, legendArea.height);
+  context.closePath();
+  context.fill();
+  context.stroke();
+  legendList.forEach(function (itemList, listIndex) {
+    let width = 0;
+    let height = 0;
+    width = legendData.widthArr[listIndex];
+    height = legendData.heightArr[listIndex];
+    let startX = 0;
+    let startY = 0;
+    if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+      switch (opts.legend.float) {
+        case 'left':
+          startX = legendArea.start.x + padding;
+          break;
+        case 'right':
+          startX = legendArea.start.x + legendArea.width - width;
+          break;
+        default:
+          startX = legendArea.start.x + (legendArea.width - width) / 2;
+      }
+      startY = legendArea.start.y + padding + listIndex * lineHeight;
+    } else {
+      if (listIndex == 0) {
+        width = 0;
+      } else {
+        width = legendData.widthArr[listIndex - 1];
+      }
+      startX = legendArea.start.x + padding + width;
+      startY = legendArea.start.y + padding + (legendArea.height - height) / 2;
+    }
+    context.setFontSize(config.fontSize);
+    for (let i = 0; i < itemList.length; i++) {
+      let item = itemList[i];
+      item.area = [0, 0, 0, 0];
+      item.area[0] = startX;
+      item.area[1] = startY;
+      item.area[3] = startY + lineHeight;
+      context.beginPath();
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.show !== false ? item.color : (opts.legend.hiddenColor || '#999999'));
+      context.setFillStyle(item.show !== false ? item.color : (opts.legend.hiddenColor || '#999999'));
+      switch (item.legendShape) {
+        case 'line':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 2 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 2 * opts.pix, 15 * opts.pix, 4 * opts.pix);
+          break;
+        case 'triangle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'diamond':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'circle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.arc(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight, 5 * opts.pix, 0, 2 * Math.PI);
+          break;
+        case 'rect':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+          break;
+        case 'square':
+          context.moveTo(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+          break;
+        case 'none':
+          break;
+        default:
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+      }
+      context.closePath();
+      context.fill();
+      context.stroke();
+      startX += shapeWidth + shapeRight;
+      let fontTrans = 0.5 * lineHeight + 0.5 * fontSize - 2;
+      const legendText = item.legendText ? item.legendText : item.name;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.show !== false ? (opts.legend.fontColor || '#666666') : (opts.legend.hiddenColor || '#999999'));
+      context.fillText(legendText, startX, startY + fontTrans);
+      context.closePath();
+      context.stroke();
+      if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+        startX += measureText(legendText, fontSize, context) + itemGap;
+        item.area[2] = startX;
+      } else {
+        item.area[2] = startX + measureText(legendText, fontSize, context) + itemGap;
+        ;
+        startX -= shapeWidth + shapeRight;
+        startY += lineHeight;
+      }
+    }
+  });
+}
+
+/**
+ * 绘制仪表盘标签
+ * @param gaugeOption - 仪表盘选项
+ * @param radius - 半径
+ * @param centerPosition - 中心点坐标
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawGaugeLabel(
+  gaugeOption: GaugeOption,
+  radius: number,
+  centerPosition: Point,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  radius -= (gaugeOption.width || 0) / 2 + (gaugeOption.labelOffset || 0) * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  let totalAngle: number;
+  if ((gaugeOption.endAngle || 0) < (gaugeOption.startAngle || 0)) {
+    totalAngle = 2 + (gaugeOption.endAngle || 0) - (gaugeOption.startAngle || 0);
+  } else {
+    totalAngle = (gaugeOption.startAngle || 0) - (gaugeOption.endAngle || 0);
+  }
+  let splitAngle = totalAngle / (gaugeOption.splitLine?.splitNumber || 5);
+  let totalNumber = (gaugeOption.endNumber || 10) - (gaugeOption.startNumber || 0);
+  let splitNumber = totalNumber / (gaugeOption.splitLine?.splitNumber || 5);
+  let nowAngle = gaugeOption.startAngle || 0;
+  let nowNumber = gaugeOption.startNumber || 0;
+  for (let i = 0; i < (gaugeOption.splitLine?.splitNumber || 5) + 1; i++) {
+    let pos = {
+      x: radius * Math.cos(nowAngle * Math.PI),
+      y: radius * Math.sin(nowAngle * Math.PI)
+    };
+    let labelText = gaugeOption.formatter ? gaugeOption.formatter(nowNumber, i, opts) : String(nowNumber);
+    pos.x += centerPosition.x - measureText(labelText, config.fontSize, context) / 2;
+    pos.y += centerPosition.y;
+    let startX = pos.x;
+    let startY = pos.y;
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(gaugeOption.labelColor || opts.fontColor);
+    context.fillText(labelText, startX, startY + config.fontSize / 2);
+    context.closePath();
+    context.stroke();
+    nowAngle += splitAngle;
+    if (nowAngle >= 2) {
+      nowAngle = nowAngle % 2;
+    }
+    nowNumber += splitNumber;
+  }
+}
+
+/**
+ * 绘制雷达图标签
+ * @param angleList - 角度列表
+ * @param radius - 半径
+ * @param centerPosition - 中心点坐标
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawRadarLabel(
+  angleList: number[],
+  radius: number,
+  centerPosition: Point,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let radarOption = opts.extra?.radar || {};
+  angleList.forEach(function (angle, index) {
+    if (radarOption.labelPointShow === true && opts.categories && opts.categories[index] !== '') {
+      let posPoint = {
+        x: radius * Math.cos(angle),
+        y: radius * Math.sin(angle)
+      };
+      let posPointAxis = convertCoordinateOrigin(posPoint.x, posPoint.y, centerPosition);
+      context.setFillStyle(radarOption.labelPointColor || opts.fontColor);
+      context.beginPath();
+      context.arc(posPointAxis.x, posPointAxis.y, (radarOption.labelPointRadius || 3) * opts.pix, 0, 2 * Math.PI, false);
+      context.closePath();
+      context.fill();
+    }
+    if (radarOption.labelShow === true && opts.categories) {
+      let pos = {
+        x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),
+        y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)
+      };
+      let posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);
+      let startX = posRelativeCanvas.x;
+      let startY = posRelativeCanvas.y;
+      if (Math.abs(pos.x) < 1e-10) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context) / 2;
+      } else if (pos.x < 0) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context);
+      }
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(radarOption.labelColor || opts.fontColor);
+      context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2);
+      context.closePath();
+      context.stroke();
+    }
+  });
+}

+ 178 - 0
mini-ui-packages/mini-charts/src/lib/renderers/candle-renderer.ts

@@ -0,0 +1,178 @@
+/**
+ * K线图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的K线图绘制相关函数
+ */
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { getDataPoints, getCandleDataPoints } from '../charts-data/basic-charts';
+import { splitPoints, createCurveControlPoints } from '../utils/misc';
+import { assign } from '../config';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface CandleOption {
+  color: {
+    upLine: string;
+    upFill: string;
+    downLine: string;
+    downFill: string;
+  };
+  average: {
+    show: boolean;
+    name: string[];
+    day: number[];
+    color: string[];
+  };
+}
+
+export function drawCandleDataPoints(
+  series: SeriesItem[],
+  seriesMA: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { xAxisPoints: number[]; calPoints: any[]; eachSpacing: number } {
+  let candleOption = assign({}, {
+    color: {
+      upLine: '#f04864',
+      upFill: '#f04864',
+      downLine: '#2fc25b',
+      downFill: '#2fc25b'
+    },
+    average: {
+      show: false,
+      name: [],
+      day: [],
+      color: config.color
+    }
+  }, opts.extra?.candle || {}) as CandleOption;
+  opts.extra = opts.extra || {};
+  opts.extra.candle = candleOption;
+
+  let xAxisData = opts.chartData!.xAxisData;
+  let xAxisPoints = xAxisData.xAxisPoints;
+  let eachSpacing = xAxisData.eachSpacing;
+  let calPoints: any[] = [];
+
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width! + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + (opts.xAxis?.itemCount || 0) + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area![3];
+    rightSpace = leftSpace + ((opts.xAxis?.itemCount || 0) + 4) * eachSpacing;
+  }
+
+  // 画均线
+  if (candleOption.average.show || seriesMA) {
+    seriesMA.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData!.yAxisData!.ranges[(eachSeries.index || 0) as number]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data as any, minRange!, maxRange!, xAxisPoints, eachSpacing, opts, config);
+      let splitPointList = splitPoints(points, eachSeries as any);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(1);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              let ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x,
+                item.y);
+            }
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+
+  // 画K线
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData!.yAxisData!.ranges[(eachSeries.index || 0) as number]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getCandleDataPoints(data as any, minRange!, maxRange!, xAxisPoints, eachSpacing, opts, config);
+    calPoints.push(points);
+    let splitPointList = splitPoints(points, eachSeries as any);
+    for (let i = 0; i < splitPointList[0].length; i++) {
+      if (i > leftNum && i < rightNum) {
+        let item = splitPointList[0][i];
+        context.beginPath();
+        // 如果上涨
+        if ((data as any)![i]![1] - (data as any)![i]![0] > 0) {
+          context.setStrokeStyle(candleOption.color.upLine);
+          context.setFillStyle(candleOption.color.upFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); // 顶点
+          context.lineTo(item[1].x, item[1].y); // 收盘中间点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); // 收盘左侧点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); // 开盘左侧点
+          context.lineTo(item[0].x, item[0].y); // 开盘中间点
+          context.lineTo(item[2].x, item[2].y); // 底点
+          context.lineTo(item[0].x, item[0].y); // 开盘中间点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); // 开盘右侧点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); // 收盘右侧点
+          context.lineTo(item[1].x, item[1].y); // 收盘中间点
+          context.moveTo(item[3].x, item[3].y); // 顶点
+        } else {
+          context.setStrokeStyle(candleOption.color.downLine);
+          context.setFillStyle(candleOption.color.downFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); // 顶点
+          context.lineTo(item[0].x, item[0].y); // 开盘中间点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); // 开盘左侧点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); // 收盘左侧点
+          context.lineTo(item[1].x, item[1].y); // 收盘中间点
+          context.lineTo(item[2].x, item[2].y); // 底点
+          context.lineTo(item[1].x, item[1].y); // 收盘中间点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); // 收盘右侧点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); // 开盘右侧点
+          context.lineTo(item[0].x, item[0].y); // 开盘中间点
+          context.moveTo(item[3].x, item[3].y); // 顶点
+        }
+        context.closePath();
+        context.fill();
+        context.stroke();
+      }
+    }
+  });
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}

+ 1133 - 0
mini-ui-packages/mini-charts/src/lib/renderers/column-renderer.ts

@@ -0,0 +1,1133 @@
+/**
+ * 柱状图和条形图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的柱状图和条形图绘制相关函数
+ */
+
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem, ColumnOptions, MountOptions } from '../data-processing/series-calculator';
+import {
+  getColumnDataPoints,
+  getStackDataPoints,
+  getDataPoints,
+  getBarDataPoints,
+  getBarStackDataPoints,
+  getMountDataPoints,
+} from '../charts-data/basic-charts';
+import {
+  fixColumeData,
+  fixColumeStackData,
+  fixColumeMeterData,
+  fixBarData,
+} from '../helper-functions/data-fixers';
+import { fillCustomColor } from '../data-processing/series-calculator';
+import { drawToolTipSplitArea, drawBarToolTipSplitArea } from './common-renderer';
+import { assign } from '../config';
+import { hexToRgb } from '../utils/color';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface DataPoint extends Point {
+  width?: number;
+  height?: number;
+  x0?: number;
+  y0?: number;
+  color?: string;
+  zeroPoints?: number;
+  value?: number;
+}
+
+export interface ColumnOption {
+  type?: 'group' | 'stack' | 'meter';
+  width?: number;
+  meterBorder?: number;
+  meterFillColor?: string;
+  barBorderCircle?: boolean;
+  barBorderRadius?: number[];
+  seriesGap?: number;
+  linearType?: string;
+  linearOpacity?: number;
+  customColor?: string[];
+  colorStop?: number;
+  labelPosition?: string;
+  borderWidth?: number;
+  widthRatio?: number;
+}
+
+export interface ColumnResult {
+  xAxisPoints?: number[];
+  yAxisPoints?: number[];
+  calPoints: any[];
+  eachSpacing: number;
+}
+
+/**
+ * 绘制柱状图数据点
+ * @param series 系列数据
+ * @param opts 图表配置
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param process 动画进度(默认为1)
+ * @returns 计算结果
+ */
+export function drawColumnDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  process = 1
+): ColumnResult {
+  const xAxisData = opts.chartData?.xAxisData;
+  if (!xAxisData) {
+    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
+  }
+
+  const xAxisPoints = xAxisData.xAxisPoints;
+  const eachSpacing = xAxisData.eachSpacing;
+
+  const columnOption = assign(
+    {},
+    {
+      type: 'group',
+      width: eachSpacing / 2,
+      meterBorder: 4,
+      meterFillColor: '#FFFFFF',
+      barBorderCircle: false,
+      barBorderRadius: [] as number[],
+      seriesGap: 2,
+      linearType: 'none',
+      linearOpacity: 1,
+      customColor: [] as string[],
+      colorStop: 0,
+      labelPosition: 'outside',
+    },
+    opts.extra?.column || {}
+  ) as ColumnOption;
+
+  const calPoints: any[] = [];
+
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + (opts.xAxis?.itemCount || 0) + 4;
+  }
+
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTipSplitArea(opts.tooltip.offset.x, opts, config, context, eachSpacing);
+  }
+
+  columnOption.customColor = fillCustomColor(
+    columnOption.linearType || 'none',
+    columnOption.customColor || [],
+    series,
+    config
+  );
+
+  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+    const ranges = opts.chartData?.yAxisData?.ranges
+      ? [].concat(opts.chartData!.yAxisData!.ranges[(eachSeries.index || 0) as number])
+      : [];
+
+    const minRange = ranges.pop();
+    const maxRange = ranges.shift();
+
+    // 计算0轴坐标
+    const spacingValid = opts.height! - opts.area![0] - opts.area![2];
+    const zeroHeight = (spacingValid * (0 - (minRange || 0))) / ((maxRange || 0) - (minRange || 0));
+    const zeroPoints = opts.height! - Math.round(zeroHeight) - opts.area![2];
+    (eachSeries as any).zeroPoints = zeroPoints;
+
+    const data = eachSeries.data;
+
+    switch ((columnOption.type as any)) {
+      case 'group':
+        let points = getColumnDataPoints(
+          data as number[],
+          minRange!,
+          maxRange!,
+          xAxisPoints,
+          eachSpacing,
+          opts,
+          config,
+          zeroPoints,
+          process
+        );
+        const tooltipPoints = getStackDataPoints(
+          data as number[],
+          minRange!,
+          maxRange!,
+          xAxisPoints,
+          eachSpacing,
+          opts,
+          config,
+          seriesIndex,
+          series,
+          process
+        );
+        calPoints.push(tooltipPoints);
+        points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+
+        for (let i = 0; i < points.length; i++) {
+          const item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            const startX = item.x - item.width || 0 / 2;
+            const height = opts.height! - item.y - opts.area![2];
+
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color;
+            const strokeColor = item.color || eachSeries.color;
+
+            if (columnOption.linearType !== 'none') {
+              const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              // 透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity || 1));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(
+                  0,
+                  hexToRgb(
+                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
+                    columnOption.linearOpacity || 1
+                  )
+                );
+                grd.addColorStop(
+                  columnOption.colorStop || 0,
+                  hexToRgb(
+                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
+                    columnOption.linearOpacity || 1
+                  )
+                );
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd;
+            }
+
+            // 圆角边框
+            if (
+              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
+              columnOption.barBorderCircle === true
+            ) {
+              const left = startX;
+              const top = item.y > zeroPoints ? zeroPoints : item.y;
+              const width = item.width || 0;
+              const height = Math.abs(zeroPoints - item.y);
+
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              if (item.y > zeroPoints) {
+                columnOption.barBorderRadius = [0, 0, width / 2, width / 2];
+              }
+
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              const minRadius = Math.min(width / 2, height / 2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width || 0, item.y);
+              context.lineTo(startX + item.width || 0, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+              context.setLineWidth(1);
+              context.setStrokeStyle(strokeColor);
+            }
+
+            context.setFillStyle(fillColor);
+            context.closePath();
+            context.fill();
+          }
+        }
+        break;
+
+      case 'stack':
+        // 绘制堆叠数据图
+        points = getStackDataPoints(
+          data as number[],
+          minRange,
+          maxRange,
+          xAxisPoints,
+          eachSpacing,
+          opts,
+          config,
+          seriesIndex,
+          series,
+          process
+        );
+        calPoints.push(points);
+        points = fixColumeStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);
+
+        for (let i = 0; i < points.length; i++) {
+          const item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            context.beginPath();
+            const fillColor = item.color || eachSeries.color;
+            const startX = item.x - item.width || 0 / 2 + 1;
+            let height = opts.height! - item.y - opts.area![2];
+            const height0 = opts.height! - item.y0! - opts.area![2];
+
+            if (seriesIndex > 0) {
+              height -= height0;
+            }
+
+            context.setFillStyle(fillColor);
+            context.moveTo(startX, item.y);
+            context.fillRect(startX, item.y, item.width || 0, height);
+            context.closePath();
+            context.fill();
+          }
+        }
+        break;
+
+      case 'meter':
+        // 绘制温度计数据图
+        points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
+        calPoints.push(points);
+        points = fixColumeMeterData(
+          points,
+          eachSpacing,
+          series.length,
+          seriesIndex,
+          config,
+          opts,
+          columnOption.meterBorder || 4
+        );
+
+        for (let i = 0; i < points.length; i++) {
+          const item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            // 画背景颜色
+            context.beginPath();
+
+            if (seriesIndex == 0 && (columnOption.meterBorder || 0) > 0) {
+              context.setStrokeStyle(eachSeries.color || '');
+              context.setLineWidth((columnOption.meterBorder || 0) * opts.pix);
+            }
+
+            if (seriesIndex == 0) {
+              context.setFillStyle(columnOption.meterFillColor || '#FFFFFF');
+            } else {
+              context.setFillStyle(item.color || eachSeries.color || '');
+            }
+
+            const startX = item.x - item.width || 0 / 2;
+            const height = opts.height! - item.y - opts.area![2];
+
+            if (
+              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
+              columnOption.barBorderCircle === true
+            ) {
+              const left = startX;
+              const top = item.y;
+              const width = item.width || 0;
+              const height = zeroPoints - item.y;
+
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              const minRadius = Math.min(width / 2, height / 2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+              context.fill();
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width || 0, item.y);
+              context.lineTo(startX + item.width || 0, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+              context.fill();
+            }
+
+            if (seriesIndex == 0 && (columnOption.meterBorder || 0) > 0) {
+              context.closePath();
+              context.stroke();
+            }
+          }
+        }
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+      const ranges = opts.chartData?.yAxisData?.ranges
+        ? [].concat(opts.chartData!.yAxisData!.ranges[(eachSeries.index || 0) as number])
+        : [];
+      const minRange = ranges.pop();
+      const maxRange = ranges.shift();
+
+      // 计算0轴坐标
+      const spacingValid = opts.height! - opts.area![0] - opts.area![2];
+      const zeroHeight = (spacingValid * (0 - (minRange || 0))) / ((maxRange || 0) - (minRange || 0));
+      const zeroPoints = opts.height! - Math.round(zeroHeight) - opts.area![2];
+
+      const data = eachSeries.data;
+
+      switch (columnOption.type) {
+        case 'group': {
+          const points = getColumnDataPoints(
+            data as number[],
+            minRange,
+            maxRange,
+            xAxisPoints,
+            eachSpacing,
+            opts,
+            config,
+            zeroPoints,
+            process
+          );
+          const fixedPoints = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+          drawColumePointText(fixedPoints, eachSeries, config, context, opts);
+          break;
+        }
+
+        case 'stack': {
+          const points = getStackDataPoints(
+            data as number[],
+            minRange,
+            maxRange,
+            xAxisPoints,
+            eachSpacing,
+            opts,
+            config,
+            seriesIndex,
+            series,
+            process
+          );
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        }
+
+        case 'meter': {
+          const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        }
+      }
+    });
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  };
+}
+
+/**
+ * 绘制堆叠柱状图/山峰图数据点
+ * @param series 系列数据
+ * @param opts 图表配置
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @returns 计算结果
+ */
+export function drawMountDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): ColumnResult {
+  const xAxisData = opts.chartData?.xAxisData;
+  if (!xAxisData) {
+    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
+  }
+
+  const xAxisPoints = xAxisData.xAxisPoints;
+  const eachSpacing = xAxisData.eachSpacing;
+
+  const mountOption = assign(
+    {},
+    {
+      type: 'mount',
+      widthRatio: 1,
+      borderWidth: 1,
+      barBorderCircle: false,
+      barBorderRadius: [],
+      linearType: 'none',
+      linearOpacity: 1,
+      customColor: [],
+      colorStop: 0,
+    },
+    opts.extra?.mount
+  );
+
+  (mountOption as any).widthRatio = (mountOption.widthRatio || 0 || 0) <= 0 ? 0 : mountOption.widthRatio || 0;
+  (mountOption as any).widthRatio = (mountOption as any).widthRatio >= 2 ? 2 : (mountOption as any).widthRatio;
+
+  const calPoints: any[] = [];
+
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + (opts.xAxis?.itemCount || 0) + 4;
+  }
+
+  (mountOption as any).customColor = fillCustomColor(
+    mountOption.linearType || 'none',
+    mountOption.customColor || [],
+    series,
+    config
+  );
+
+  const ranges = opts.chartData?.yAxisData?.ranges ? [].concat(opts.chartData.yAxisData.ranges[0]) : [];
+  const minRange = ranges.pop();
+  const maxRange = ranges.shift();
+
+  // 计算0轴坐标
+  const spacingValid = opts.height - opts.area![0] - opts.area![2];
+  const zeroHeight = (spacingValid * (0 - minRange)) / (maxRange - minRange);
+  const zeroPoints = opts.height - Math.round(zeroHeight) - opts.area![2];
+
+  let points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints);
+
+  switch (mountOption.type) {
+    case 'bar':
+      for (let i = 0; i < points.length; i++) {
+        const item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
+          const height = opts.height! - item.y - opts.area![2];
+          context.beginPath();
+          let fillColor = item.color || series[i].color;
+          const strokeColor = item.color || series[i].color;
+
+          if (mountOption.linearType !== 'none') {
+            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+            // 透明渐变
+            if (mountOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(
+                0,
+                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
+              );
+              grd.addColorStop(
+                mountOption.colorStop || 0,
+                hexToRgb(
+                  (mountOption.customColor || [])[series[i].linearIndex || 0],
+                  mountOption.linearOpacity || 1
+                )
+              );
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd;
+          }
+
+          // 圆角边框
+          if (
+            (mountOption.barBorderRadius && mountOption.barBorderRadius.length === 4) ||
+            mountOption.barBorderCircle === true
+          ) {
+            const left = startX;
+            const top = item.y > zeroPoints ? zeroPoints : item.y;
+            const width = item.width || 0;
+            const height = Math.abs(zeroPoints - item.y);
+
+            if (mountOption.barBorderCircle) {
+              mountOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+            }
+            if (item.y > zeroPoints) {
+              mountOption.barBorderRadius = [0, 0, width / 2, width / 2];
+            }
+
+            let [r0, r1, r2, r3] = mountOption.barBorderRadius;
+            const minRadius = Math.min(width / 2, height / 2);
+            r0 = r0 > minRadius ? minRadius : r0;
+            r1 = r1 > minRadius ? minRadius : r1;
+            r2 = r2 > minRadius ? minRadius : r2;
+            r3 = r3 > minRadius ? minRadius : r3;
+            r0 = r0 < 0 ? 0 : r0;
+            r1 = r1 < 0 ? 0 : r1;
+            r2 = r2 < 0 ? 0 : r2;
+            r3 = r3 < 0 ? 0 : r3;
+
+            context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+            context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+            context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+            context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+          } else {
+            context.moveTo(startX, item.y);
+            context.lineTo(startX + item.width || 0, item.y);
+            context.lineTo(startX + item.width || 0, zeroPoints);
+            context.lineTo(startX, zeroPoints);
+            context.lineTo(startX, item.y);
+          }
+
+          context.setStrokeStyle(strokeColor);
+          context.setFillStyle(fillColor);
+
+          if ((mountOption.borderWidth || 0) > 0) {
+            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
+            context.closePath();
+            context.stroke();
+          }
+          context.fill();
+        }
+      }
+      break;
+
+    case 'triangle':
+      for (let i = 0; i < points.length; i++) {
+        const item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
+          const height = opts.height! - item.y - opts.area![2];
+          context.beginPath();
+          let fillColor = item.color || series[i].color;
+          const strokeColor = item.color || series[i].color;
+
+          if (mountOption.linearType !== 'none') {
+            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+            // 透明渐变
+            if (mountOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(
+                0,
+                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
+              );
+              grd.addColorStop(
+                mountOption.colorStop || 0,
+                hexToRgb(
+                  (mountOption.customColor || [])[series[i].linearIndex || 0],
+                  mountOption.linearOpacity || 1
+                )
+              );
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd;
+          }
+
+          context.moveTo(startX, zeroPoints);
+          context.lineTo(item.x, item.y);
+          context.lineTo(startX + item.width || 0, zeroPoints);
+          context.setStrokeStyle(strokeColor);
+          context.setFillStyle(fillColor);
+
+          if ((mountOption.borderWidth || 0) > 0) {
+            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
+            context.stroke();
+          }
+          context.fill();
+        }
+      }
+      break;
+
+    case 'mount':
+      for (let i = 0; i < points.length; i++) {
+        const item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
+          const height = opts.height! - item.y - opts.area![2];
+          context.beginPath();
+          let fillColor = item.color || series[i].color;
+          const strokeColor = item.color || series[i].color;
+
+          if (mountOption.linearType !== 'none') {
+            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+            // 透明渐变
+            if (mountOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(
+                0,
+                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
+              );
+              grd.addColorStop(
+                mountOption.colorStop || 0,
+                hexToRgb(
+                  (mountOption.customColor || [])[series[i].linearIndex || 0],
+                  mountOption.linearOpacity || 1
+                )
+              );
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd;
+          }
+
+          context.moveTo(startX, zeroPoints);
+          context.bezierCurveTo(item.x - item.width || 0 / 4, zeroPoints, item.x - item.width || 0 / 4, item.y, item.x, item.y);
+          context.bezierCurveTo(
+            item.x + item.width || 0 / 4,
+            item.y,
+            item.x + item.width || 0 / 4,
+            zeroPoints,
+            startX + item.width || 0,
+            zeroPoints
+          );
+          context.setStrokeStyle(strokeColor);
+          context.setFillStyle(fillColor);
+
+          if ((mountOption.borderWidth || 0) > 0) {
+            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
+            context.stroke();
+          }
+          context.fill();
+        }
+      }
+      break;
+
+    case 'sharp':
+      for (let i = 0; i < points.length; i++) {
+        const item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
+          const height = opts.height! - item.y - opts.area![2];
+          context.beginPath();
+          let fillColor = item.color || series[i].color;
+          const strokeColor = item.color || series[i].color;
+
+          if (mountOption.linearType !== 'none') {
+            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+            // 透明渐变
+            if (mountOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(
+                0,
+                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
+              );
+              grd.addColorStop(
+                mountOption.colorStop || 0,
+                hexToRgb(
+                  (mountOption.customColor || [])[series[i].linearIndex || 0],
+                  mountOption.linearOpacity || 1
+                )
+              );
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd;
+          }
+
+          context.moveTo(startX, zeroPoints);
+          context.quadraticCurveTo(item.x - 0, zeroPoints - height / 4, item.x, item.y);
+          context.quadraticCurveTo(item.x + 0, zeroPoints - height / 4, startX + item.width || 0, zeroPoints);
+          context.setStrokeStyle(strokeColor);
+          context.setFillStyle(fillColor);
+
+          if ((mountOption.borderWidth || 0) > 0) {
+            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
+            context.stroke();
+          }
+          context.fill();
+        }
+      }
+      break;
+  }
+
+  if (opts.dataLabel !== false && process === 1) {
+    const ranges = opts.chartData?.yAxisData?.ranges ? [].concat(opts.chartData.yAxisData.ranges[0]) : [];
+    const minRange = ranges.pop();
+    const maxRange = ranges.shift();
+    const points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints);
+    drawMountPointText(points, series, config, context, opts, zeroPoints);
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: points,
+    eachSpacing: eachSpacing,
+  };
+}
+
+/**
+ * 绘制条形图数据点
+ * @param series 系列数据
+ * @param opts 图表配置
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @returns 计算结果
+ */
+export function drawBarDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): ColumnResult {
+  const yAxisPoints: number[] = [];
+  const eachSpacing = (opts.height - opts.area![0] - opts.area![2]) / (opts.categories?.length || 1);
+
+  for (let i = 0; i < (opts.categories?.length || 0); i++) {
+    yAxisPoints.push(opts.area![0] + eachSpacing / 2 + eachSpacing * i);
+  }
+
+  const columnOption = assign(
+    {},
+    {
+      type: 'group',
+      width: eachSpacing / 2,
+      meterBorder: 4,
+      meterFillColor: '#FFFFFF',
+      barBorderCircle: false,
+      barBorderRadius: [],
+      seriesGap: 2,
+      linearType: 'none',
+      linearOpacity: 1,
+      customColor: [],
+      colorStop: 0,
+    },
+    opts.extra?.bar
+  );
+
+  const calPoints: any[] = [];
+
+  context.save();
+  let leftNum = -2;
+  let rightNum = yAxisPoints.length + 2;
+
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawBarToolTipSplitArea(opts.tooltip.offset.y, opts, config, context, eachSpacing);
+  }
+
+  columnOption.customColor = fillCustomColor(
+    columnOption.linearType || 'none',
+    columnOption.customColor || [],
+    series,
+    config
+  );
+
+  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+    const ranges = opts.chartData?.xAxisData?.ranges ? [].concat(opts.chartData.xAxisData.ranges) : [];
+    const maxRange = ranges.pop();
+    const minRange = ranges.shift();
+    const data = eachSeries.data;
+
+    switch (columnOption.type) {
+      case 'group':
+        let points = getBarDataPoints(data as number[], minRange, maxRange, yAxisPoints, eachSpacing, opts, config);
+        const tooltipPoints = getBarStackDataPoints(
+          data as number[],
+          minRange,
+          maxRange,
+          yAxisPoints,
+          eachSpacing,
+          opts,
+          config,
+          seriesIndex,
+          series
+        );
+        calPoints.push(tooltipPoints);
+        points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+
+        for (let i = 0; i < points.length; i++) {
+          const item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            const startX = opts.area![3];
+            const startY = item.y - item.width || 0 / 2;
+            const height = item.height;
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color;
+            const strokeColor = item.color || eachSeries.color;
+
+            if (columnOption.linearType !== 'none') {
+              const grd = context.createLinearGradient(startX, item.y, item.x, item.y);
+              // 透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity || 1));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(
+                  0,
+                  hexToRgb((columnOption.customColor || [])[eachSeries.linearIndex || 0], columnOption.linearOpacity || 1)
+                );
+                grd.addColorStop(
+                  columnOption.colorStop || 0,
+                  hexToRgb(
+                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
+                    columnOption.linearOpacity || 1
+                  )
+                );
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd;
+            }
+
+            // 圆角边框
+            if (
+              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
+              columnOption.barBorderCircle === true
+            ) {
+              const left = startX;
+              const width = item.width || 0;
+              const top = item.y - item.width || 0 / 2;
+              const height = item.height;
+
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              const minRadius = Math.min(width / 2, height / 2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+
+              context.arc(left + r3, top + r3, r3, -Math.PI, -Math.PI / 2);
+              context.arc(item.x - r0, top + r0, r0, -Math.PI / 2, 0);
+              context.arc(item.x - r1, top + width - r1, r1, 0, Math.PI / 2);
+              context.arc(left + r2, top + width - r2, r2, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, startY);
+              context.lineTo(item.x, startY);
+              context.lineTo(item.x, startY + item.width || 0);
+              context.lineTo(startX, startY + item.width || 0);
+              context.lineTo(startX, startY);
+            }
+
+            context.setFillStyle(fillColor);
+            context.closePath();
+            context.fill();
+          }
+        }
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+      const ranges = opts.chartData?.xAxisData?.ranges ? [].concat(opts.chartData.xAxisData.ranges) : [];
+      const maxRange = ranges.pop();
+      const minRange = ranges.shift();
+      const data = eachSeries.data;
+
+      const points = getBarDataPoints(data as number[], minRange, maxRange, yAxisPoints, eachSpacing, opts, config);
+      const fixedPoints = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+      drawBarPointText(fixedPoints, eachSeries, config, context, opts);
+    });
+  }
+
+  context.restore();
+
+  return {
+    yAxisPoints: yAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  };
+}
+
+/**
+ * 绘制柱状图数据点文本
+ * @param points 数据点数组
+ * @param series 系列数据
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param opts 图表配置
+ */
+export function drawColumePointText(
+  points: (Point | null)[],
+  series: any,
+  config: UChartsConfig,
+  context: CanvasContext,
+  opts: ChartOptions
+): void {
+  const data = series.data;
+  const textOffset = series.textOffset || 0;
+  const Position = opts.extra?.column?.labelPosition || 'outside';
+
+  points.forEach(function (item: any, index: number) {
+    if (item !== null) {
+      context.beginPath();
+      const fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+
+      let value = data[index];
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (Array.isArray(data[index])) {
+          value = data[index][1];
+        } else {
+          value = data[index].value;
+        }
+      }
+
+      const formatVal = series.formatter ? series.formatter(value, index, series, opts) : value;
+      context.setTextAlign('center');
+      let startY = item.y - 4 * opts.pix + textOffset * opts.pix;
+
+      if (item.y > series.zeroPoints) {
+        startY = item.y + textOffset * opts.pix + fontSize;
+      }
+
+      if (Position == 'insideTop') {
+        startY = item.y + fontSize + textOffset * opts.pix;
+        if (item.y > series.zeroPoints) {
+          startY = item.y - textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+
+      if (Position == 'center') {
+        startY = item.y + textOffset * opts.pix + (opts.height! - opts.area![2] - item.y + fontSize) / 2;
+        if (series.zeroPoints < opts.height! - opts.area![2]) {
+          startY = item.y + textOffset * opts.pix + (series.zeroPoints - item.y + fontSize) / 2;
+        }
+        if (item.y > series.zeroPoints) {
+          startY = item.y - textOffset * opts.pix - (item.y - series.zeroPoints - fontSize) / 2;
+        }
+        if (opts.extra?.column?.type == 'stack') {
+          startY = item.y + textOffset * opts.pix + (item.y0 - item.y + fontSize) / 2;
+        }
+      }
+
+      if (Position == 'bottom') {
+        startY = opts.height! - opts.area![2] + textOffset * opts.pix - 4 * opts.pix;
+        if (series.zeroPoints < opts.height! - opts.area![2]) {
+          startY = series.zeroPoints + textOffset * opts.pix - 4 * opts.pix;
+        }
+        if (item.y > series.zeroPoints) {
+          startY = series.zeroPoints - textOffset * opts.pix + fontSize + 2 * opts.pix;
+        }
+        if (opts.extra?.column?.type == 'stack') {
+          startY = item.y0 + textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+/**
+ * 绘制堆叠柱状图/山峰图数据点文本
+ * @param points 数据点数组
+ * @param series 系列数据
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param opts 图表配置
+ * @param zeroPoints 零点坐标
+ */
+export function drawMountPointText(
+  points: any[],
+  series: SeriesItem[],
+  config: UChartsConfig,
+  context: CanvasContext,
+  opts: ChartOptions,
+  zeroPoints: number
+): void {
+  points.forEach(function (item: any, index: number) {
+    if (item !== null) {
+      context.beginPath();
+      const fontSize = series[index].textSize ? series[index].textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series[index].textColor || opts.fontColor);
+
+      let value = item.value;
+      const formatVal = series[index].formatter
+        ? series[index].formatter(value, index, series, opts)
+        : value;
+
+      context.setTextAlign('center');
+      let startY = item.y - 4 * opts.pix + (series[index].textOffset || 0) * opts.pix;
+
+      if (item.y > zeroPoints) {
+        startY = item.y + (series[index].textOffset || 0) * opts.pix + fontSize;
+      }
+
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+/**
+ * 绘制条形图数据点文本
+ * @param points 数据点数组
+ * @param series 系列数据
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param opts 图表配置
+ */
+export function drawBarPointText(
+  points: (Point | null)[],
+  series: any,
+  config: UChartsConfig,
+  context: CanvasContext,
+  opts: ChartOptions
+): void {
+  const data = series.data;
+  const textOffset = series.textOffset || 0;
+
+  points.forEach(function (item: any, index: number) {
+    if (item !== null) {
+      context.beginPath();
+      const fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+
+      let value = data[index];
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        value = data[index].value;
+      }
+
+      const formatVal = series.formatter ? series.formatter(value, index, series, opts) : value;
+      context.setTextAlign('left');
+      let startX = item.x + 4 * opts.pix + textOffset * opts.pix;
+
+      if (item.x < series.zeroPoints) {
+        startX = item.x - textOffset * opts.pix - fontSize;
+        context.setTextAlign('right');
+      }
+
+      context.fillText(String(formatVal), startX, item.y + fontSize / 2 - 3);
+      context.closePath();
+      context.stroke();
+    }
+  });
+}

+ 957 - 0
mini-ui-packages/mini-charts/src/lib/renderers/common-renderer.ts

@@ -0,0 +1,957 @@
+/**
+ * 通用绘制函数
+ *
+ * 从 u-charts 核心库搬迁的通用绘制相关函数
+ * 用于处理数据点形状、工具提示等通用绘制操作
+ */
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig } from '../data-processing/series-calculator';
+import { measureText } from '../utils/text';
+import { convertCoordinateOrigin } from '../utils/coordinate';
+import { avoidCollision } from '../utils/collision';
+import { hexToRgb } from '../utils/color';
+import { assign } from '../config';
+
+// Canvas 上下文类型(使用 any 以兼容小程序环境)
+export type CanvasContext = any;
+
+/**
+ * 坐标点接口
+ */
+export interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * 工具提示文本项接口
+ */
+export interface ToolTipTextItem {
+  text: string | number;
+  color?: string | null;
+  legendShape?: string;
+}
+
+/**
+ * 工具提示选项接口
+ */
+export interface ToolTipOption {
+  group?: number[];
+  index?: number | number[];
+  offset?: Point;
+  textList?: ToolTipTextItem[];
+  horizentalLine?: boolean;
+  xAxisLabel?: boolean;
+  yAxisLabel?: boolean;
+  gridType?: string;
+  gridColor?: string;
+  dashLength?: number;
+  labelBgColor?: string;
+  labelBgOpacity?: number;
+  labelFontColor?: string;
+  showBox?: boolean;
+  showArrow?: boolean;
+  showCategory?: boolean;
+  bgColor?: string;
+  bgOpacity?: number;
+  borderColor?: string;
+  borderWidth?: number;
+  borderRadius?: number;
+  borderOpacity?: number;
+  boxPadding?: number;
+  fontColor?: string;
+  fontSize?: number;
+  lineHeight?: number;
+  legendShow?: boolean;
+  legendShape?: string;
+  splitLine?: boolean;
+  activeBgColor?: string;
+  activeBgOpacity?: number;
+  activeWidth?: number;
+  [key: string]: any;
+}
+
+/**
+ * 标记线数据项接口
+ */
+export interface MarkLineDataItem {
+  value: number;
+  y: number;
+  labelText?: string;
+  lineColor?: string;
+  showLabel?: boolean;
+  labelFontSize?: number;
+  labelPadding?: number;
+  labelFontColor?: string;
+  labelBgColor?: string;
+  labelBgOpacity?: number;
+  labelAlign?: string;
+  labelOffsetX?: number;
+  labelOffsetY?: number;
+  type?: string;
+  dashLength?: number;
+  data?: MarkLineDataItem[];
+  [key: string]: any;
+}
+
+/**
+ * 激活点选项接口
+ */
+export interface ActivePointOption {
+  activeType?: string;
+  [key: string]: any;
+}
+
+/**
+ * 标题选项接口
+ */
+export interface TitleOption {
+  name?: string;
+  fontSize?: number;
+  color?: string;
+  offsetX?: number;
+  offsetY?: number;
+}
+
+/**
+ * 计算标记线数据
+ * @param data - 标记线数据数组
+ * @param opts - 图表配置选项
+ * @returns 计算后的标记线数据数组
+ */
+function calMarkLineData(data: MarkLineDataItem[], opts: ChartOptions): MarkLineDataItem[] {
+  const points: MarkLineDataItem[] = [];
+  const spacingValid = opts.height! - opts.area![0] - opts.area![2];
+  const minRange = opts.chartData?.yAxisData?.ranges?.[0]?.[0] || 0;
+  const maxRange = opts.chartData?.yAxisData?.ranges?.[0]?.[1] || 0;
+
+  for (let i = 0; i < data.length; i++) {
+    const item = data[i];
+    const y = opts.height! - Math.round(spacingValid * (item.value - minRange) / (maxRange - minRange)) - opts.area![2];
+    points.push({ ...item, y });
+  }
+  return points;
+}
+
+/**
+ * 计算工具提示Y轴数据
+ * @param offsetY - Y轴偏移量
+ * @param series - 系列数据数组
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param eachSpacing - 每个数据点的间距
+ * @returns Y轴标签文本数组
+ */
+function calTooltipYAxisData(
+  offsetY: number,
+  series: any[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  eachSpacing: number
+): string[] {
+  const yAxisData = opts.chartData?.yAxisData;
+  if (!yAxisData) return [];
+
+  const ranges = yAxisData.ranges || [];
+  const result: string[] = [];
+
+  for (let i = 0; i < ranges.length; i++) {
+    const range = ranges[i];
+    const minRange = range[0];
+    const maxRange = range[1];
+    const yAxisItem = opts.yAxis?.data?.[i];
+
+    if (maxRange === undefined || minRange === undefined) continue;
+
+    const spacingValid = opts.height! - opts.area![0] - opts.area![2];
+    const value = maxRange - (opts.height! - opts.area![2] - offsetY) * (maxRange - minRange) / spacingValid;
+
+    let labelText = String(value);
+    if (yAxisItem?.formatter) {
+      labelText = yAxisItem.formatter(value, i, opts);
+    } else if (opts.yAxis?.formatter) {
+      labelText = opts.yAxis.formatter(value, i, opts);
+    }
+
+    result.push(labelText);
+  }
+
+  return result;
+}
+
+/**
+ * 绘制数据点形状
+ * @param points - 数据点坐标数组
+ * @param color - 填充颜色
+ * @param shape - 点形状 ('diamond', 'circle', 'square', 'triangle', 'none')
+ * @param context - Canvas 渲染上下文
+ * @param opts - 图表配置选项
+ */
+export function drawPointShape(
+  points: (Point | null)[],
+  color: string,
+  shape: string,
+  context: CanvasContext,
+  opts: ChartOptions
+): void {
+  context.beginPath();
+  if (opts.dataPointShapeType === 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+  if (shape === 'diamond') {
+    points.forEach(function(item) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item) {
+      if (item !== null) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item) {
+      if (item !== null) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+/**
+ * 绘制激活数据点
+ * @param points - 数据点坐标数组
+ * @param color - 填充颜色
+ * @param shape - 点形状
+ * @param context - Canvas 渲染上下文
+ * @param opts - 图表配置选项
+ * @param option - 激活点选项
+ * @param seriesIndex - 系列索引
+ */
+export function drawActivePoint(
+  points: (Point | null)[],
+  color: string,
+  shape: string,
+  context: CanvasContext,
+  opts: ChartOptions,
+  option: ActivePointOption,
+  seriesIndex: number
+): void {
+  if (!opts.tooltip) {
+    return;
+  }
+  if (opts.tooltip.group && opts.tooltip.group.length > 0 && opts.tooltip.group.includes(seriesIndex) === false) {
+    return;
+  }
+  const pointIndex = typeof opts.tooltip.index === 'number'
+    ? opts.tooltip.index
+    : opts.tooltip.index![opts.tooltip.group.indexOf(seriesIndex)];
+
+  context.beginPath();
+  if (option.activeType === 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex === index) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex === index) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex === index) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex === index) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+/**
+ * 绘制环形图标题
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param center - 中心点坐标
+ */
+export function drawRingTitle(
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  center: Point
+): void {
+  const titlefontSize = opts.title?.fontSize || config.titleFontSize;
+  const subtitlefontSize = opts.subtitle?.fontSize || config.subtitleFontSize;
+  const title = opts.title?.name || '';
+  const subtitle = opts.subtitle?.name || '';
+  const titleFontColor = opts.title?.color || opts.fontColor;
+  const subtitleFontColor = opts.subtitle?.color || opts.fontColor;
+  const titleHeight = title ? titlefontSize : 0;
+  const subtitleHeight = subtitle ? subtitlefontSize : 0;
+  const margin = 5;
+
+  if (subtitle) {
+    const textWidth = measureText(subtitle, subtitlefontSize * opts.pix, context);
+    const startX = center.x - textWidth / 2 + (opts.subtitle?.offsetX || 0) * opts.pix;
+    const startY = center.y + subtitlefontSize * opts.pix / 2 + (opts.subtitle?.offsetY || 0) * opts.pix;
+    const finalStartY = title ? startY + (titleHeight * opts.pix + margin) / 2 : startY;
+
+    context.beginPath();
+    context.setFontSize(subtitlefontSize * opts.pix);
+    context.setFillStyle(subtitleFontColor);
+    context.fillText(subtitle, startX, finalStartY);
+    context.closePath();
+    context.stroke();
+  }
+
+  if (title) {
+    const _textWidth = measureText(title, titlefontSize * opts.pix, context);
+    const _startX = center.x - _textWidth / 2 + (opts.title?.offsetX || 0);
+    let _startY = center.y + titlefontSize * opts.pix / 2 + (opts.title?.offsetY || 0) * opts.pix;
+    if (subtitle) {
+      _startY -= (subtitleHeight * opts.pix + margin) / 2;
+    }
+    context.beginPath();
+    context.setFontSize(titlefontSize * opts.pix);
+    context.setFillStyle(titleFontColor);
+    context.fillText(title, _startX, _startY);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+/**
+ * 绘制数据点文本
+ * @param points - 数据点坐标数组
+ * @param series - 系列数据
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param opts - 图表配置选项
+ */
+export function drawPointText(
+  points: (Point | null)[],
+  series: any,
+  config: UChartsConfig,
+  context: CanvasContext,
+  opts: ChartOptions
+): void {
+  const data = series.data;
+  const textOffset = series.textOffset || 0;
+
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      const fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+
+      let value = data[index];
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (Array.isArray(data[index])) {
+          value = data[index][1];
+        } else {
+          value = data[index].value;
+        }
+      }
+
+      const formatVal = series.formatter ? series.formatter(value, index, series, opts) : value;
+      context.setTextAlign('center');
+      context.fillText(String(formatVal), item.x, item.y - 4 + textOffset * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+/**
+ * 绘制工具提示分割线
+ * @param offsetX - X轴偏移量
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawToolTipSplitLine(
+  offsetX: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  const toolTipOption = opts.extra?.tooltip || {};
+  const gridType = toolTipOption.gridType === undefined ? 'solid' : toolTipOption.gridType;
+  const dashLength = toolTipOption.dashLength === undefined ? 4 : toolTipOption.dashLength;
+
+  const startY = opts.area![0];
+  const endY = opts.height! - opts.area![2];
+
+  if (gridType === 'dash') {
+    context.setLineDash([dashLength, dashLength]);
+  }
+
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(offsetX, startY);
+  context.lineTo(offsetX, endY);
+  context.stroke();
+  context.setLineDash([]);
+
+  if (toolTipOption.xAxisLabel && opts.categories && opts.tooltip?.index !== undefined) {
+    const labelText = String(opts.categories[opts.tooltip.index]);
+    context.setFontSize(config.fontSize);
+    const textWidth = measureText(labelText, config.fontSize, context);
+    const textX = offsetX - 0.5 * textWidth;
+    const textY = endY + 2 * opts.pix;
+
+    context.beginPath();
+    context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+    context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+    context.setLineWidth(1 * opts.pix);
+    context.rect(textX - toolTipOption.boxPadding * opts.pix, textY, textWidth + 2 * toolTipOption.boxPadding * opts.pix, config.fontSize + 2 * toolTipOption.boxPadding * opts.pix);
+    context.closePath();
+    context.stroke();
+    context.fill();
+
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+    context.fillText(labelText, textX, textY + toolTipOption.boxPadding * opts.pix + config.fontSize);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+/**
+ * 绘制标记线
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ */
+export function drawMarkLine(
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  const markLineOption = assign({
+    type: 'solid',
+    dashLength: 4,
+    data: []
+  } as any, opts.extra?.markLine || {});
+
+  const startX = opts.area![3];
+  const endX = opts.width! - opts.area![1];
+  const points = calMarkLineData(markLineOption.data, opts);
+
+  for (let i = 0; i < points.length; i++) {
+    const item = assign({}, {
+      lineColor: '#DE4A42',
+      showLabel: false,
+      labelFontSize: 13,
+      labelPadding: 6,
+      labelFontColor: '#666666',
+      labelBgColor: '#DFE8FF',
+      labelBgOpacity: 0.8,
+      labelAlign: 'left',
+      labelOffsetX: 0,
+      labelOffsetY: 0,
+    }, points[i]);
+
+    if (markLineOption.type === 'dash') {
+      context.setLineDash([markLineOption.dashLength, markLineOption.dashLength]);
+    }
+
+    context.setStrokeStyle(item.lineColor);
+    context.setLineWidth(1 * opts.pix);
+    context.beginPath();
+    context.moveTo(startX, item.y);
+    context.lineTo(endX, item.y);
+    context.stroke();
+    context.setLineDash([]);
+
+    if (item.showLabel) {
+      const fontSize = item.labelFontSize * opts.pix;
+      const labelText = item.labelText ? item.labelText : String(item.value);
+      context.setFontSize(fontSize);
+      const textWidth = measureText(labelText, fontSize, context);
+      const bgWidth = textWidth + item.labelPadding * opts.pix * 2;
+      let bgStartX = item.labelAlign === 'left' ? opts.area![3] - bgWidth : opts.width! - opts.area![1];
+      bgStartX += item.labelOffsetX;
+      let bgStartY = item.y - 0.5 * fontSize - item.labelPadding * opts.pix;
+      bgStartY += item.labelOffsetY;
+      const textX = bgStartX + item.labelPadding * opts.pix;
+      const textY = item.y;
+
+      context.setFillStyle(hexToRgb(item.labelBgColor, item.labelBgOpacity));
+      context.setStrokeStyle(item.labelBgColor);
+      context.setLineWidth(1 * opts.pix);
+      context.beginPath();
+      context.rect(bgStartX, bgStartY, bgWidth, fontSize + 2 * item.labelPadding * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.fill();
+
+      context.setFontSize(fontSize);
+      context.setTextAlign('left');
+      context.setFillStyle(item.labelFontColor);
+      context.fillText(labelText, textX, bgStartY + fontSize + item.labelPadding * opts.pix / 2);
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  }
+}
+
+/**
+ * 绘制工具提示水平线
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param eachSpacing - 每个数据点的间距
+ * @param xAxisPoints - X轴数据点数组
+ */
+export function drawToolTipHorizentalLine(
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  eachSpacing: number,
+  xAxisPoints: number[]
+): void {
+  const toolTipOption = assign({
+    gridType: 'solid',
+    dashLength: 4
+  } as any, opts.extra?.tooltip || {});
+
+  const startX = opts.area![3];
+  const endX = opts.width! - opts.area![1];
+
+  if (toolTipOption.gridType === 'dash') {
+    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);
+  }
+
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(startX, opts.tooltip!.offset.y);
+  context.lineTo(endX, opts.tooltip!.offset.y);
+  context.stroke();
+  context.setLineDash([]);
+
+  if (toolTipOption.yAxisLabel) {
+    const boxPadding = toolTipOption.boxPadding * opts.pix;
+    const labelText = calTooltipYAxisData(opts.tooltip!.offset.y, opts.series || [], opts, config, eachSpacing);
+    const widthArr = opts.chartData?.yAxisData?.yAxisWidth || [];
+    let tStartLeft = opts.area![3];
+    let tStartRight = opts.width! - opts.area![1];
+
+    for (let i = 0; i < labelText.length; i++) {
+      context.setFontSize(toolTipOption.fontSize * opts.pix);
+      const textWidth = measureText(labelText[i], toolTipOption.fontSize * opts.pix, context);
+      let bgStartX: number, bgEndX: number, bgWidth: number;
+
+      if (widthArr[i]?.position === 'left') {
+        bgStartX = tStartLeft - (textWidth + boxPadding * 2) - 2 * opts.pix;
+        bgEndX = Math.max(bgStartX, bgStartX + textWidth + boxPadding * 2);
+      } else {
+        bgStartX = tStartRight + 2 * opts.pix;
+        bgEndX = Math.max(bgStartX + (widthArr[i]?.width || 0), bgStartX + textWidth + boxPadding * 2);
+      }
+      bgWidth = bgEndX - bgStartX;
+      const textX = bgStartX + (bgWidth - textWidth) / 2;
+      const textY = opts.tooltip!.offset.y;
+
+      context.beginPath();
+      context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+      context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+      context.setLineWidth(1 * opts.pix);
+      context.rect(bgStartX, textY - 0.5 * config.fontSize - boxPadding, bgWidth, config.fontSize + 2 * boxPadding);
+      context.closePath();
+      context.stroke();
+      context.fill();
+
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+      context.fillText(labelText[i], textX, textY + 0.5 * config.fontSize);
+      context.closePath();
+      context.stroke();
+
+      if (widthArr[i]?.position === 'left') {
+        tStartLeft -= ((widthArr[i]?.width || 0) + opts.yAxis?.padding * opts.pix || 0);
+      } else {
+        tStartRight += (widthArr[i]?.width || 0) + opts.yAxis?.padding * opts.pix || 0;
+      }
+    }
+  }
+}
+
+/**
+ * 绘制工具提示分割区域
+ * @param offsetX - X轴偏移量
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param eachSpacing - 每个数据点的间距
+ */
+export function drawToolTipSplitArea(
+  offsetX: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  eachSpacing: number
+): void {
+  const toolTipOption = assign({
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08,
+    activeWidth: eachSpacing
+  } as any, opts.extra?.column || {});
+
+  toolTipOption.activeWidth = toolTipOption.activeWidth > eachSpacing ? eachSpacing : toolTipOption.activeWidth;
+  const startY = opts.area![0];
+  const endY = opts.height! - opts.area![2];
+
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect(offsetX - toolTipOption.activeWidth / 2, startY, toolTipOption.activeWidth, endY - startY);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+/**
+ * 绘制条形图工具提示分割区域
+ * @param offsetX - X轴偏移量
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param eachSpacing - 每个数据点的间距
+ */
+export function drawBarToolTipSplitArea(
+  offsetX: number,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  eachSpacing: number
+): void {
+  const toolTipOption = assign({}, {
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08
+  }, opts.extra?.bar);
+
+  const startX = opts.area![3];
+  const endX = opts.width! - opts.area![1];
+
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect(startX, offsetX - eachSpacing / 2, endX - startX, eachSpacing);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+/**
+ * 绘制工具提示
+ * @param textList - 文本列表
+ * @param offset - 偏移量
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param eachSpacing - 每个数据点的间距
+ * @param xAxisPoints - X轴数据点数组
+ */
+export function drawToolTip(
+  textList: ToolTipTextItem[],
+  offset: Point,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  eachSpacing: number,
+  xAxisPoints: number[]
+): void {
+  const toolTipOption = assign({}, {
+    showBox: true,
+    showArrow: true,
+    showCategory: false,
+    bgColor: '#000000',
+    bgOpacity: 0.7,
+    borderColor: '#000000',
+    borderWidth: 0,
+    borderRadius: 0,
+    borderOpacity: 0.7,
+    boxPadding: 3,
+    fontColor: '#FFFFFF',
+    fontSize: 13,
+    lineHeight: 20,
+    legendShow: true,
+    legendShape: 'auto',
+    splitLine: true,
+  }, opts.extra?.tooltip);
+
+  if (toolTipOption.showCategory === true && opts.categories) {
+    textList.unshift({ text: String(opts.categories[opts.tooltip!.index!]), color: null });
+  }
+
+  const fontSize = toolTipOption.fontSize * opts.pix;
+  const lineHeight = toolTipOption.lineHeight * opts.pix;
+  const boxPadding = toolTipOption.boxPadding * opts.pix;
+  let legendWidth = fontSize;
+  let legendMarginRight = 5 * opts.pix;
+
+  if (toolTipOption.legendShow === false) {
+    legendWidth = 0;
+    legendMarginRight = 0;
+  }
+
+  const arrowWidth = toolTipOption.showArrow ? 8 * opts.pix : 0;
+  let isOverRightBorder = false;
+
+  if (opts.type === 'line' || opts.type === 'mount' || opts.type === 'area' || opts.type === 'candle' || opts.type === 'mix') {
+    if (toolTipOption.splitLine === true) {
+      drawToolTipSplitLine(opts.tooltip!.offset.x, opts, config, context);
+    }
+  }
+
+  offset = assign({
+    x: 0,
+    y: 0
+  }, offset);
+  offset.y -= 8 * opts.pix;
+
+  const textWidth = textList.map(function(item) {
+    return measureText(String(item.text), fontSize, context);
+  });
+  const toolTipWidth = legendWidth + legendMarginRight + 4 * boxPadding + Math.max(...textWidth);
+  const toolTipHeight = 2 * boxPadding + textList.length * lineHeight;
+
+  if (toolTipOption.showBox === false) {
+    return;
+  }
+
+  // 检查是否超出右边界
+  if (offset.x - Math.abs(opts._scrollDistance_ || 0) + arrowWidth + toolTipWidth > opts.width!) {
+    isOverRightBorder = true;
+  }
+
+  if (toolTipHeight + offset.y > opts.height!) {
+    offset.y = opts.height! - toolTipHeight;
+  }
+
+  // 绘制背景矩形
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.bgColor, toolTipOption.bgOpacity));
+  context.setLineWidth(toolTipOption.borderWidth * opts.pix);
+  context.setStrokeStyle(hexToRgb(toolTipOption.borderColor, toolTipOption.borderOpacity));
+  const radius = toolTipOption.borderRadius;
+
+  if (isOverRightBorder) {
+    // 增加左侧仍然超出的判断
+    if (toolTipWidth + arrowWidth > opts.width!) {
+      offset.x = opts.width! + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width!);
+    }
+    if (toolTipWidth > offset.x) {
+      offset.x = opts.width! + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width!);
+    }
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+    }
+    context.arc(offset.x - arrowWidth - radius, offset.y + toolTipHeight - radius, radius, 0, Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + toolTipHeight - radius, radius, Math.PI / 2, Math.PI, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - radius, offset.y + radius, radius, -Math.PI / 2, 0, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  } else {
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+    }
+    context.arc(offset.x + arrowWidth + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + radius, radius, -Math.PI / 2, 0, false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + toolTipHeight - radius, radius, 0, Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + radius, offset.y + toolTipHeight - radius, radius, Math.PI / 2, Math.PI, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  }
+  context.closePath();
+  context.fill();
+
+  if (toolTipOption.borderWidth > 0) {
+    context.stroke();
+  }
+
+  // 绘制图例
+  if (toolTipOption.legendShow) {
+    textList.forEach(function(item) {
+      if (item.color !== null) {
+        context.beginPath();
+        context.setFillStyle(item.color);
+        let startX = offset.x + arrowWidth + 2 * boxPadding;
+        const startY = offset.y + (lineHeight - fontSize) / 2 + lineHeight * textList.indexOf(item) + boxPadding + 1;
+
+        if (isOverRightBorder) {
+          startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding;
+        }
+
+        switch (item.legendShape) {
+          case 'line':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 2 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 2 * opts.pix, legendWidth, 4 * opts.pix);
+            break;
+          case 'triangle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'diamond':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'circle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.arc(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth, 5 * opts.pix, 0, 2 * Math.PI);
+            break;
+          case 'rect':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+            break;
+          case 'square':
+            context.moveTo(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+            break;
+          default:
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+        }
+        context.closePath();
+        context.fill();
+      }
+    });
+  }
+
+  // 绘制文本列表
+  textList.forEach(function(item) {
+    let startX = offset.x + arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    if (isOverRightBorder) {
+      startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    }
+    const startY = offset.y + lineHeight * textList.indexOf(item) + (lineHeight - fontSize) / 2 - 1 + boxPadding + fontSize;
+
+    context.beginPath();
+    context.setFontSize(fontSize);
+    context.setTextBaseline('normal');
+    context.setFillStyle(toolTipOption.fontColor);
+    context.fillText(String(item.text), startX, startY);
+    context.closePath();
+    context.stroke();
+  });
+}
+
+/**
+ * 绘制工具提示桥接函数
+ * @param opts - 图表配置选项
+ * @param config - uCharts配置对象
+ * @param context - Canvas 渲染上下文
+ * @param process - 动画进程 (0-1)
+ * @param eachSpacing - 每个数据点的间距
+ * @param xAxisPoints - X轴数据点数组
+ */
+export function drawToolTipBridge(
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  process: number,
+  eachSpacing: number,
+  xAxisPoints: number[]
+): void {
+  const toolTipOption = opts.extra?.tooltip || {};
+  if (toolTipOption.horizentalLine && opts.tooltip && process === 1 && (opts.type === 'line' || opts.type === 'area' || opts.type === 'column' || opts.type === 'mount' || opts.type === 'candle' || opts.type === 'mix')) {
+    drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints);
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTip(opts.tooltip.textList, opts.tooltip.offset, opts, config, context, eachSpacing, xAxisPoints);
+  }
+  context.restore();
+}
+
+/**
+ * 绘制Canvas
+ * @param opts - 图表配置选项
+ * @param context - Canvas 渲染上下文
+ */
+export function drawCanvas(opts: ChartOptions, context: CanvasContext): void {
+  context.save();
+  context.translate(0, 0.5);
+  context.restore();
+  context.draw();
+}

+ 71 - 0
mini-ui-packages/mini-charts/src/lib/renderers/index.ts

@@ -0,0 +1,71 @@
+/**
+ * 绘制模块统一导出
+ *
+ * 从 u-charts 核心库搬迁的绘制相关函数
+ * 用于处理各种图表类型的绘制操作
+ */
+
+// 通用绘制函数
+export type { CanvasContext, Point } from './common-renderer';
+export * from './common-renderer';
+
+// 从 helper-functions 导入 contextRotate
+export { contextRotate } from '../helper-functions/misc-helpers';
+
+// 坐标轴和图例绘制
+export type { GaugeOption, RadarOption } from './axis-renderer';
+export {
+  drawXAxis,
+  drawYAxisGrid,
+  drawYAxis,
+  drawLegend,
+  drawGaugeLabel,
+  drawRadarLabel
+} from './axis-renderer';
+
+// 柱状图和条形图绘制
+export type { ColumnOption, ColumnResult } from './column-renderer';
+export {
+  drawColumnDataPoints,
+  drawBarDataPoints,
+  drawMountDataPoints
+} from './column-renderer';
+
+// 折线图和面积图绘制
+export {
+  drawLineDataPoints,
+  drawAreaDataPoints
+} from './line-renderer';
+
+// K线图绘制
+export type { CandleOption } from './candle-renderer';
+export {
+  drawCandleDataPoints
+} from './candle-renderer';
+
+// 饼图和环形图绘制
+export {
+  drawPieDataPoints,
+  drawRoseDataPoints,
+  drawGaugeDataPoints,
+  drawArcbarDataPoints
+} from './pie-renderer';
+
+// 雷达图绘制
+export {
+  drawRadarDataPoints
+} from './radar-renderer';
+
+// 地图绘制
+export {
+  drawMapDataPoints
+} from './map-renderer';
+
+// 特殊图表绘制
+export {
+  drawFunnelDataPoints,
+  drawWordCloudDataPoints,
+  drawMixDataPoints,
+  drawScatterDataPoints,
+  drawBubbleDataPoints
+} from './special-renderer';

+ 481 - 0
mini-ui-packages/mini-charts/src/lib/renderers/line-renderer.ts

@@ -0,0 +1,481 @@
+/**
+ * 折线图和面积图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的折线图和面积图绘制相关函数
+ */
+
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { getLineDataPoints, getDataPoints } from '../charts-data/basic-charts';
+import { splitPoints, createCurveControlPoints } from '../utils/misc';
+import { drawPointShape, drawActivePoint, drawPointText } from './common-renderer';
+import { assign } from '../config';
+import { hexToRgb } from '../utils/color';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+  r?: number;
+  t?: number;
+}
+
+export interface LineOption {
+  type?: 'straight' | 'curve' | 'step';
+  width?: number;
+  activeType?: string;
+  linearType?: string;
+  onShadow?: boolean;
+  animation?: string;
+}
+
+export interface AreaOption extends LineOption {
+  opacity?: number;
+  addLine?: boolean;
+  gradient?: boolean;
+}
+
+export interface LineResult {
+  xAxisPoints: number[];
+  calPoints: any[][];
+  eachSpacing: number;
+}
+
+/**
+ * 绘制面积图数据点
+ * @param series 系列数据
+ * @param opts 图表配置
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param process 动画进度(默认为1)
+ * @returns 计算结果
+ */
+export function drawAreaDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  process = 1
+): LineResult {
+  const areaOption = assign(
+    {},
+    {
+      type: 'straight',
+      opacity: 0.2,
+      addLine: false,
+      width: 2,
+      gradient: false,
+      activeType: 'none',
+    },
+    opts.extra?.area
+  );
+
+  const xAxisData = opts.chartData?.xAxisData;
+  if (!xAxisData) {
+    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
+  }
+
+  const xAxisPoints = xAxisData.xAxisPoints;
+  const eachSpacing = xAxisData.eachSpacing;
+  const endY = opts.height - opts.area![2];
+  const calPoints: any[][] = [];
+
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area![3];
+    rightSpace = leftSpace + (opts.xAxis?.itemCount || 0 + 4) * eachSpacing;
+  }
+
+  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+    const ranges = opts.chartData?.yAxisData?.ranges
+      ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
+      : [];
+    const minRange = ranges.pop();
+    const maxRange = ranges.shift();
+    const data = eachSeries.data;
+
+    const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
+    calPoints.push(points);
+    const splitPointList = splitPoints(points, eachSeries);
+
+    for (let i = 0; i < splitPointList.length; i++) {
+      const points = splitPointList[i];
+
+      // 绘制区域
+      context.beginPath();
+      context.setStrokeStyle(hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));
+
+      if (areaOption.gradient) {
+        const gradient = context.createLinearGradient(0, opts.area![0], 0, opts.height - opts.area![2]);
+        gradient.addColorStop('0', hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));
+        gradient.addColorStop('1.0', hexToRgb('#FFFFFF', 0.1));
+        context.setFillStyle(gradient);
+      } else {
+        context.setFillStyle(hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));
+      }
+
+      context.setLineWidth((areaOption.width || 2) * opts.pix);
+
+      if (points.length > 1) {
+        const firstPoint = points[0];
+        const lastPoint = points[points.length - 1];
+        context.moveTo(firstPoint.x, firstPoint.y);
+        let startPoint = 0;
+
+        if (areaOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              const ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          }
+        }
+
+        if (areaOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          }
+        }
+
+        if (areaOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          }
+        }
+
+        context.lineTo(lastPoint.x, endY);
+        context.lineTo(firstPoint.x, endY);
+        context.lineTo(firstPoint.x, firstPoint.y);
+      } else {
+        const item = points[0];
+        context.moveTo(item.x - eachSpacing / 2, item.y);
+      }
+
+      context.closePath();
+      context.fill();
+
+      // 画连线
+      if (areaOption.addLine) {
+        if (eachSeries.lineType == 'dash') {
+          const dashLength = (eachSeries as any).dashLength ? (eachSeries as any).dashLength : 8;
+          context.setLineDash([dashLength * opts.pix, dashLength * opts.pix]);
+        }
+
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color || '');
+        context.setLineWidth((areaOption.width || 2) * opts.pix);
+
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+
+          if (areaOption.type === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              const item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                const ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            }
+          }
+
+          if (areaOption.type === 'straight') {
+            for (let j = 0; j < points.length; j++) {
+              const item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+
+          if (areaOption.type === 'step') {
+            for (let j = 0; j < points.length; j++) {
+              const item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, points[j - 1].y);
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+
+          context.moveTo(points[0].x, points[0].y);
+        }
+
+        context.stroke();
+        context.setLineDash([]);
+      }
+    }
+
+    // 画点
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color || '', eachSeries.pointShape, context, opts);
+    }
+
+    drawActivePoint(points, eachSeries.color || '', eachSeries.pointShape, context, opts, areaOption, seriesIndex);
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+      const ranges = opts.chartData?.yAxisData?.ranges
+        ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
+        : [];
+      const minRange = ranges.pop();
+      const maxRange = ranges.shift();
+      const data = eachSeries.data;
+
+      const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  };
+}
+
+/**
+ * 绘制折线图数据点
+ * @param series 系列数据
+ * @param opts 图表配置
+ * @param config UCharts配置
+ * @param context Canvas上下文
+ * @param process 动画进度(默认为1)
+ * @returns 计算结果
+ */
+export function drawLineDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  process = 1
+): LineResult {
+  const lineOption = assign(
+    {},
+    {
+      type: 'straight',
+      width: 2,
+      activeType: 'none',
+      linearType: 'none',
+      onShadow: false,
+      animation: 'vertical',
+    },
+    opts.extra?.line
+  );
+
+  (lineOption as any).width *= opts.pix;
+
+  const xAxisData = opts.chartData?.xAxisData;
+  if (!xAxisData) {
+    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
+  }
+
+  const xAxisPoints = xAxisData.xAxisPoints;
+  const eachSpacing = xAxisData.eachSpacing;
+  const calPoints: any[][] = [];
+
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area![3];
+    rightSpace = leftSpace + (opts.xAxis?.itemCount || 0 + 4) * eachSpacing;
+  }
+
+  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+    // 这段很神奇的代码用于解决ios16的setStrokeStyle失效的bug
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color || '');
+    context.moveTo(-10000, -10000);
+    context.lineTo(-10001, -10001);
+    context.stroke();
+
+    const ranges = opts.chartData?.yAxisData?.ranges
+      ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
+      : [];
+    const minRange = ranges.pop();
+    const maxRange = ranges.shift();
+    const data = eachSeries.data;
+
+    const points = getLineDataPoints(
+      data as number[],
+      minRange,
+      maxRange,
+      xAxisPoints,
+      eachSpacing,
+      opts,
+      config,
+      lineOption,
+      process
+    );
+    calPoints.push(points);
+    const splitPointList = splitPoints(points, eachSeries);
+
+    if (eachSeries.lineType == 'dash') {
+      const dashLength = (eachSeries as any).dashLength ? (eachSeries as any).dashLength : 8;
+      context.setLineDash([dashLength * opts.pix, dashLength * opts.pix]);
+    }
+
+    context.beginPath();
+    let strokeColor = eachSeries.color || '';
+
+    if (
+      lineOption.linearType !== 'none' &&
+      (eachSeries as any).linearColor &&
+      (eachSeries as any).linearColor.length > 0
+    ) {
+      const grd = context.createLinearGradient(
+        xAxisData.startX,
+        opts.height / 2,
+        xAxisData.endX,
+        opts.height / 2
+      );
+      for (let i = 0; i < (eachSeries as any).linearColor.length; i++) {
+        grd.addColorStop((eachSeries as any).linearColor[i][0], hexToRgb((eachSeries as any).linearColor[i][1], 1));
+      }
+      strokeColor = grd;
+    }
+
+    context.setStrokeStyle(strokeColor);
+
+    if (lineOption.onShadow == true && (eachSeries as any).setShadow && (eachSeries as any).setShadow.length > 0) {
+      context.setShadow(
+        (eachSeries as any).setShadow[0],
+        (eachSeries as any).setShadow[1],
+        (eachSeries as any).setShadow[2],
+        (eachSeries as any).setShadow[3]
+      );
+    } else {
+      context.setShadow(0, 0, 0, 'rgba(0,0,0,0)');
+    }
+
+    context.setLineWidth(lineOption.width || 2);
+
+    splitPointList.forEach(function (points: any[], index: number) {
+      if (points.length === 1) {
+        context.moveTo(points[0].x, points[0].y);
+      } else {
+        context.moveTo(points[0].x, points[0].y);
+        let startPoint = 0;
+
+        if (lineOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              const ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          }
+        }
+
+        if (lineOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          }
+        }
+
+        if (lineOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            const item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          }
+        }
+
+        context.moveTo(points[0].x, points[0].y);
+      }
+    });
+
+    context.stroke();
+    context.setLineDash([]);
+
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color || '', eachSeries.pointShape, context, opts);
+    }
+
+    drawActivePoint(points, eachSeries.color || '', eachSeries.pointShape, context, opts, lineOption);
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
+      const ranges = opts.chartData?.yAxisData?.ranges
+        ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
+        : [];
+      const minRange = ranges.pop();
+      const maxRange = ranges.shift();
+      const data = eachSeries.data;
+
+      const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  };
+}

+ 212 - 0
mini-ui-packages/mini-charts/src/lib/renderers/map-renderer.ts

@@ -0,0 +1,212 @@
+/**
+ * 地图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的地图绘制相关函数
+ */
+
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { measureText } from '../utils/text';
+import { hexToRgb } from '../utils/color';
+import { assign } from '../config';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface MapOption {
+  border: boolean;
+  mercator: boolean;
+  borderWidth: number;
+  active: boolean;
+  borderColor: string;
+  fillOpacity: number;
+  activeBorderColor: string;
+  activeFillColor: string;
+  activeFillOpacity: number;
+  activeTextColor?: string;
+}
+
+export interface Bounds {
+  xMin: number;
+  xMax: number;
+  yMin: number;
+  yMax: number;
+}
+
+export interface MapSeriesItem extends SeriesItem {
+  geometry: {
+    coordinates: any[];
+  };
+  properties: {
+    name?: string;
+    centroid?: number[];
+  };
+  color?: string;
+  fillOpacity?: number;
+  textSize?: number;
+  textColor?: string;
+}
+
+// 经纬度转墨卡托
+function lonlat2mercator(longitude: number, latitude: number): number[] {
+  let mercator = [0, 0];
+  let x = longitude * 20037508.34 / 180;
+  let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180);
+  y = y * 20037508.34 / 180;
+  mercator[0] = x;
+  mercator[1] = y;
+  return mercator;
+}
+
+function getBoundingBox(data: MapSeriesItem[]): Bounds {
+  let bounds: Bounds = {
+    xMin: 180,
+    xMax: 0,
+    yMin: 90,
+    yMax: 0
+  };
+  let coords: any[];
+
+  for (let i = 0; i < data.length; i++) {
+    let coorda = data[i].geometry.coordinates;
+    for (let k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0];
+      }
+      for (let j = 0; j < coords.length; j++) {
+        let longitude = coords[j][0];
+        let latitude = coords[j][1];
+        bounds.xMin = bounds.xMin < longitude ? bounds.xMin : longitude;
+        bounds.xMax = bounds.xMax > longitude ? bounds.xMax : longitude;
+        bounds.yMin = bounds.yMin < latitude ? bounds.yMin : latitude;
+        bounds.yMax = bounds.yMax > latitude ? bounds.yMax : latitude;
+      }
+    }
+  }
+  return bounds;
+}
+
+function coordinateToPoint(latitude: number, longitude: number, bounds: Bounds, scale: number, xoffset: number, yoffset: number): Point {
+  return {
+    x: (longitude - bounds.xMin) * scale + xoffset,
+    y: (bounds.yMax - latitude) * scale + yoffset
+  };
+}
+
+export function drawMapDataPoints(
+  series: MapSeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let mapOption = assign({}, {
+    border: true,
+    mercator: false,
+    borderWidth: 1,
+    active: true,
+    borderColor: '#666666',
+    fillOpacity: 0.6,
+    activeBorderColor: '#f04864',
+    activeFillColor: '#facc14',
+    activeFillOpacity: 1
+  }, opts.extra.map) as MapOption;
+
+  let data = series;
+  let bounds = getBoundingBox(data);
+
+  if (mapOption.mercator) {
+    let max = lonlat2mercator(bounds.xMax, bounds.yMax);
+    let min = lonlat2mercator(bounds.xMin, bounds.yMin);
+    bounds.xMax = max[0];
+    bounds.yMax = max[1];
+    bounds.xMin = min[0];
+    bounds.yMin = min[1];
+  }
+
+  let xScale = opts.width / Math.abs(bounds.xMax - bounds.xMin);
+  let yScale = opts.height / Math.abs(bounds.yMax - bounds.yMin);
+  let scale = xScale < yScale ? xScale : yScale;
+  let xoffset = opts.width / 2 - Math.abs(bounds.xMax - bounds.xMin) / 2 * scale;
+  let yoffset = opts.height / 2 - Math.abs(bounds.yMax - bounds.yMin) / 2 * scale;
+
+  for (let i = 0; i < data.length; i++) {
+    context.beginPath();
+    context.setLineWidth(mapOption.borderWidth * opts.pix);
+    context.setStrokeStyle(mapOption.borderColor);
+    context.setFillStyle(hexToRgb(series[i].color!, series[i].fillOpacity || mapOption.fillOpacity));
+
+    if (mapOption.active == true && opts.tooltip) {
+      if (opts.tooltip.index == i) {
+        context.setStrokeStyle(mapOption.activeBorderColor);
+        context.setFillStyle(hexToRgb(mapOption.activeFillColor, mapOption.activeFillOpacity));
+      }
+    }
+
+    let coorda = data[i].geometry.coordinates;
+    for (let k = 0; k < coorda.length; k++) {
+      let coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0];
+      }
+      for (let j = 0; j < coords.length; j++) {
+        let gaosi: number[];
+        if (mapOption.mercator) {
+          gaosi = lonlat2mercator(coords[j][0], coords[j][1]);
+        } else {
+          gaosi = coords[j];
+        }
+        let point = coordinateToPoint(gaosi[1], gaosi[0], bounds, scale, xoffset, yoffset);
+        if (j === 0) {
+          context.beginPath();
+          context.moveTo(point.x, point.y);
+        } else {
+          context.lineTo(point.x, point.y);
+        }
+      }
+      context.fill();
+      if (mapOption.border == true) {
+        context.stroke();
+      }
+    }
+  }
+
+  if (opts.dataLabel == true) {
+    for (let i = 0; i < data.length; i++) {
+      let centerPoint = data[i].properties.centroid;
+      if (centerPoint) {
+        let gaosi: number[];
+        if (mapOption.mercator) {
+          gaosi = lonlat2mercator(data[i].properties.centroid![0], data[i].properties.centroid![1]);
+        } else {
+          gaosi = data[i].properties.centroid!;
+        }
+        let point = coordinateToPoint(gaosi[1], gaosi[0], bounds, scale, xoffset, yoffset);
+        let fontSize = data[i].textSize! * opts.pix || config.fontSize;
+        let fontColor = data[i].textColor || opts.fontColor;
+        if (mapOption.active && mapOption.activeTextColor && opts.tooltip && opts.tooltip.index == i) {
+          fontColor = mapOption.activeTextColor;
+        }
+        let text = data[i].properties.name || '';
+        context.beginPath();
+        context.setFontSize(fontSize);
+        context.setFillStyle(fontColor);
+        context.fillText(text, point.x - measureText(text, fontSize, context) / 2, point.y + fontSize / 2);
+        context.closePath();
+        context.stroke();
+      }
+    }
+  }
+
+  opts.chartData.mapData = {
+    bounds: bounds,
+    scale: scale,
+    xoffset: xoffset,
+    yoffset: yoffset,
+    mercator: mapOption.mercator
+  };
+}

+ 834 - 0
mini-ui-packages/mini-charts/src/lib/renderers/pie-renderer.ts

@@ -0,0 +1,834 @@
+/**
+ * 饼图和环形图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的饼图和环形图绘制相关函数
+ */
+
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { getPieDataPoints } from '../charts-data/pie-charts';
+import { getRoseDataPoints } from '../charts-data/pie-charts';
+import { getArcbarDataPoints } from '../charts-data/gauge-charts';
+import { getGaugeAxisPoints, getGaugeArcbarDataPoints } from '../charts-data/gauge-charts';
+import { measureText } from '../utils/text';
+import { hexToRgb } from '../utils/color';
+import { assign } from '../config';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface PieSeriesItem extends SeriesItem {
+  _start_?: number;
+  _proportion_?: number;
+  _rose_proportion_?: number;
+  _radius_?: number;
+  color?: string;
+  labelText?: string;
+  labelShow?: boolean;
+  textColor?: string;
+  textSize?: number;
+  formatter?: (item: any, index: number, series: any[], opts: any) => string;
+}
+
+export interface TextObject {
+  lineStart: Point;
+  lineEnd: Point;
+  start: Point;
+  width: number;
+  height: number;
+  text: string;
+  color: string;
+  textColor?: string;
+  textSize?: number;
+}
+
+export interface PieOption {
+  activeOpacity: number;
+  activeRadius: number;
+  offsetAngle: number;
+  labelWidth: number;
+  ringWidth: number;
+  customRadius: number;
+  border: boolean;
+  borderWidth: number;
+  borderColor: string;
+  centerColor: string;
+  linearType: string;
+  customColor: string[];
+}
+
+export interface RoseOption {
+  type: string;
+  activeOpacity: number;
+  activeRadius: number;
+  offsetAngle: number;
+  labelWidth: number;
+  border: boolean;
+  borderWidth: number;
+  borderColor: string;
+  linearType: string;
+  customColor: string[];
+  minRadius?: number;
+}
+
+export interface ArcbarOption {
+  startAngle: number;
+  endAngle: number;
+  type: string;
+  direction: string;
+  lineCap: string;
+  width: number;
+  gap: number;
+  linearType: string;
+  customColor: string[];
+  backgroundColor?: string;
+  centerX?: number;
+  centerY?: number;
+  radius?: number;
+}
+
+export interface GaugeOption {
+  type: string;
+  startAngle: number;
+  endAngle: number;
+  width: number;
+  labelOffset: number;
+  oldAngle?: number;
+  oldData?: number;
+  startNumber?: number;
+  endNumber?: number;
+  labelColor?: string;
+  formatter?: (value: number, index: number, opts: any) => string;
+  splitLine: {
+    fixRadius: number;
+    splitNumber: number;
+    width: number;
+    color: string;
+    childNumber: number;
+    childWidth: number;
+  };
+  pointer: {
+    width: number;
+    color: string;
+  };
+}
+
+// 工具函数
+const util = {
+  toFixed: function toFixed(num: number, limit: number): string {
+    limit = limit || 2;
+    if (this.isFloat(num)) {
+      return num.toFixed(limit);
+    }
+    return String(num);
+  },
+  isFloat: function isFloat(num: number): boolean {
+    return num % 1 !== 0;
+  },
+  approximatelyEqual: function approximatelyEqual(num1: number, num2: number): boolean {
+    return Math.abs(num1 - num2) < 1e-10;
+  },
+  isSameSign: function isSameSign(num1: number, num2: number): boolean {
+    return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2;
+  },
+  isSameXCoordinateArea: function isSameXCoordinateArea(p1: Point, p2: Point): boolean {
+    return this.isSameSign(p1.x, p2.x);
+  }
+};
+
+function convertCoordinateOrigin(x: number, y: number, center: Point): Point {
+  return {
+    x: center.x + x,
+    y: center.y - y
+  };
+}
+
+function avoidCollision(obj: TextObject, target: TextObject | null): TextObject {
+  if (target) {
+    while (isCollision(obj, target)) {
+      if (obj.start.x > 0) {
+        obj.start.y--;
+      } else if (obj.start.x < 0) {
+        obj.start.y++;
+      } else {
+        if (obj.start.y > 0) {
+          obj.start.y++;
+        } else {
+          obj.start.y--;
+        }
+      }
+    }
+  }
+  return obj;
+}
+
+function isCollision(obj1: TextObject, obj2: TextObject): boolean {
+  const obj1End = {
+    x: obj1.start.x + obj1.width,
+    y: obj1.start.y - obj1.height
+  };
+  const obj2End = {
+    x: obj2.start.x + obj2.width,
+    y: obj2.start.y - obj2.height
+  };
+  const flag = obj2.start.x > obj1End.x || obj2End.x < obj1.start.x || obj2End.y > obj1.start.y || obj2.start.y < obj1End.y;
+  return !flag;
+}
+
+function fillCustomColor(linearType: string, customColor: string[], series: PieSeriesItem[], config: UChartsConfig): string[] {
+  let newcolor = customColor || [];
+  if (linearType == 'custom' && newcolor.length == 0) {
+    newcolor = config.linearColor;
+  }
+  if (linearType == 'custom' && newcolor.length < series.length) {
+    let chazhi = series.length - newcolor.length;
+    for (let i = 0; i < chazhi; i++) {
+      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);
+    }
+  }
+  return newcolor;
+}
+
+function drawRingTitle(opts: ChartOptions, config: UChartsConfig, context: CanvasContext, center: Point): void {
+  // 简化实现,如果需要可以后续添加
+}
+
+export function drawPieDataPoints(
+  series: PieSeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { center: Point; radius: number; series: PieSeriesItem[] } {
+  let pieOption = assign({}, {
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    ringWidth: 30,
+    customRadius: 0,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    centerColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.type == "pie" ? opts.extra.pie : opts.extra.ring) as PieOption;
+
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = pieOption.activeRadius * opts.pix;
+  }
+
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+
+  if (pieOption.customRadius > 0) {
+    radius = pieOption.customRadius * opts.pix;
+  }
+
+  series = getPieDataPoints(series, radius);
+  let activeRadius = pieOption.activeRadius * opts.pix;
+  pieOption.customColor = fillCustomColor(pieOption.linearType, pieOption.customColor, series, config);
+
+  series = series.map(function(eachSeries) {
+    eachSeries._start_! += (pieOption.offsetAngle) * Math.PI / 180;
+    return eachSeries;
+  });
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color!, pieOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_! + activeRadius, eachSeries._start_, eachSeries._start_! + 2 * eachSeries._proportion_! * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(pieOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(pieOption.borderColor);
+    let fillcolor = eachSeries.color;
+    if (pieOption.linearType == 'custom') {
+      let grd;
+      if (context.createCircularGradient) {
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_!);
+      } else {
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0, centerPosition.x, centerPosition.y, eachSeries._radius_!);
+      }
+      grd.addColorStop(0, hexToRgb(pieOption.customColor[eachSeries.linearIndex!], 1));
+      grd.addColorStop(1, hexToRgb(eachSeries.color!, 1));
+      fillcolor = grd;
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_!, eachSeries._start_, eachSeries._start_! + 2 * eachSeries._proportion_! * Math.PI);
+    context.closePath();
+    context.fill();
+    if (pieOption.border == true) {
+      context.stroke();
+    }
+  });
+
+  if (opts.type === 'ring') {
+    let innerPieWidth = radius * 0.6;
+    if (typeof pieOption.ringWidth === 'number' && pieOption.ringWidth > 0) {
+      innerPieWidth = Math.max(0, radius - pieOption.ringWidth * opts.pix);
+    }
+    context.beginPath();
+    context.setFillStyle(pieOption.centerColor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, innerPieWidth, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+  }
+
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+
+  if (process === 1 && opts.type === 'ring') {
+    drawRingTitle(opts, config, context, centerPosition);
+  }
+
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+export function drawRoseDataPoints(
+  series: PieSeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { center: Point; radius: number; series: PieSeriesItem[] } {
+  let roseOption = assign({}, {
+    type: 'area',
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.rose) as RoseOption;
+
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = roseOption.activeRadius * opts.pix;
+  }
+
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+
+  let minRadius = roseOption.minRadius || radius * 0.5;
+  if (radius < minRadius) {
+    radius = minRadius + 10;
+  }
+
+  series = getRoseDataPoints(series, roseOption.type, minRadius, radius);
+  let activeRadius = roseOption.activeRadius * opts.pix;
+  roseOption.customColor = fillCustomColor(roseOption.linearType, roseOption.customColor, series, config);
+
+  series = series.map(function(eachSeries) {
+    eachSeries._start_! += (roseOption.offsetAngle || 0) * Math.PI / 180;
+    return eachSeries;
+  });
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color!, roseOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, activeRadius + eachSeries._radius_!, eachSeries._start_, eachSeries._start_! + 2 * eachSeries._rose_proportion_! * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(roseOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(roseOption.borderColor);
+    let fillcolor = eachSeries.color;
+    if (roseOption.linearType == 'custom') {
+      let grd;
+      if (context.createCircularGradient) {
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_!);
+      } else {
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0, centerPosition.x, centerPosition.y, eachSeries._radius_!);
+      }
+      grd.addColorStop(0, hexToRgb(roseOption.customColor[eachSeries.linearIndex!], 1));
+      grd.addColorStop(1, hexToRgb(eachSeries.color!, 1));
+      fillcolor = grd;
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_!, eachSeries._start_, eachSeries._start_! + 2 * eachSeries._rose_proportion_! * Math.PI);
+    context.closePath();
+    context.fill();
+    if (roseOption.border == true) {
+      context.stroke();
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+export function drawPieText(
+  series: PieSeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext,
+  radius: number,
+  center: Point
+): void {
+  let lineRadius = config.pieChartLinePadding;
+  let textObjectCollection: TextObject[] = [];
+  let lastTextObject: TextObject | null = null;
+
+  let seriesConvert = series.map(function(item, index) {
+    let text = item.formatter ? item.formatter(item, index, series, opts) : util.toFixed(item._proportion_! * 100, 2) + '%';
+    text = item.labelText ? item.labelText : text;
+    let arc = 2 * Math.PI - (item._start_! + 2 * Math.PI * item._proportion_! / 2);
+    if (item._rose_proportion_) {
+      arc = 2 * Math.PI - (item._start_! + 2 * Math.PI * item._rose_proportion_! / 2);
+    }
+    let color = item.color;
+    let radius = item._radius_;
+    return {
+      arc: arc,
+      text: text,
+      color: color!,
+      radius: radius!,
+      textColor: item.textColor,
+      textSize: item.textSize,
+      labelShow: item.labelShow
+    };
+  });
+
+  for (let i = 0; i < seriesConvert.length; i++) {
+    let item = seriesConvert[i];
+    // line end
+    let orginX1 = Math.cos(item.arc) * (item.radius + lineRadius);
+    let orginY1 = Math.sin(item.arc) * (item.radius + lineRadius);
+    // line start
+    let orginX2 = Math.cos(item.arc) * item.radius;
+    let orginY2 = Math.sin(item.arc) * item.radius;
+    // text start
+    let orginX3 = orginX1 >= 0 ? orginX1 + config.pieChartTextPadding : orginX1 - config.pieChartTextPadding;
+    let orginY3 = orginY1;
+    let textWidth = measureText(item.text, item.textSize! * opts.pix || config.fontSize, context);
+    let startY = orginY3;
+
+    if (lastTextObject && util.isSameXCoordinateArea(lastTextObject.start, {
+        x: orginX3
+      })) {
+      if (orginX3 > 0) {
+        startY = Math.min(orginY3, lastTextObject.start.y);
+      } else if (orginX1 < 0) {
+        startY = Math.max(orginY3, lastTextObject.start.y);
+      } else {
+        if (orginY3 > 0) {
+          startY = Math.max(orginY3, lastTextObject.start.y);
+        } else {
+          startY = Math.min(orginY3, lastTextObject.start.y);
+        }
+      }
+    }
+
+    if (orginX3 < 0) {
+      orginX3 -= textWidth;
+    }
+
+    let textObject: TextObject = {
+      lineStart: {
+        x: orginX2,
+        y: orginY2
+      },
+      lineEnd: {
+        x: orginX1,
+        y: orginY1
+      },
+      start: {
+        x: orginX3,
+        y: startY
+      },
+      width: textWidth,
+      height: config.fontSize,
+      text: item.text,
+      color: item.color,
+      textColor: item.textColor,
+      textSize: item.textSize
+    };
+
+    lastTextObject = avoidCollision(textObject, lastTextObject);
+    textObjectCollection.push(lastTextObject);
+  }
+
+  for (let i = 0; i < textObjectCollection.length; i++) {
+    if (seriesConvert[i].labelShow === false) {
+      continue;
+    }
+    let item = textObjectCollection[i];
+    let lineStartPoistion = convertCoordinateOrigin(item.lineStart.x, item.lineStart.y, center);
+    let lineEndPoistion = convertCoordinateOrigin(item.lineEnd.x, item.lineEnd.y, center);
+    let textPosition = convertCoordinateOrigin(item.start.x, item.start.y, center);
+
+    context.setLineWidth(1 * opts.pix);
+    context.setFontSize(item.textSize! * opts.pix || config.fontSize);
+    context.beginPath();
+    context.setStrokeStyle(item.color);
+    context.setFillStyle(item.color);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+
+    let curveStartX = item.start.x < 0 ? textPosition.x + item.width : textPosition.x;
+    let textStartX = item.start.x < 0 ? textPosition.x - 5 : textPosition.x + 5;
+    context.quadraticCurveTo(lineEndPoistion.x, lineEndPoistion.y, curveStartX, textPosition.y);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+    context.stroke();
+    context.closePath();
+
+    context.beginPath();
+    context.moveTo(textPosition.x + item.width, textPosition.y);
+    context.arc(curveStartX, textPosition.y, 2 * opts.pix, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+
+    context.beginPath();
+    context.setFontSize(item.textSize! * opts.pix || config.fontSize);
+    context.setFillStyle(item.textColor || opts.fontColor);
+    context.fillText(item.text, textStartX, textPosition.y + 3);
+    context.closePath();
+    context.stroke();
+    context.closePath();
+  }
+}
+
+export function drawArcbarDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let arcbarOption = assign({}, {
+    startAngle: 0.75,
+    endAngle: 0.25,
+    type: 'default',
+    direction: 'cw',
+    lineCap: 'round',
+    width: 12,
+    gap: 2,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.arcbar) as ArcbarOption;
+
+  series = getArcbarDataPoints(series as any, arcbarOption) as any;
+
+  let centerPosition: Point;
+  if (arcbarOption.centerX || arcbarOption.centerY) {
+    centerPosition = {
+      x: arcbarOption.centerX ? arcbarOption.centerX : opts.width / 2,
+      y: arcbarOption.centerY ? arcbarOption.centerY : opts.height / 2
+    };
+  } else {
+    centerPosition = {
+      x: opts.width / 2,
+      y: opts.height / 2
+    };
+  }
+
+  let radius: number;
+  if (arcbarOption.radius) {
+    radius = arcbarOption.radius;
+  } else {
+    radius = Math.min(centerPosition.x, centerPosition.y);
+    radius -= 5 * opts.pix;
+    radius -= arcbarOption.width / 2;
+  }
+  radius = radius < 10 ? 10 : radius;
+
+  arcbarOption.customColor = fillCustomColor(arcbarOption.linearType, arcbarOption.customColor, series as any, config);
+
+  for (let i = 0; i < series.length; i++) {
+    let eachSeries = series[i];
+    // 背景颜色
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(arcbarOption.backgroundColor || '#E9E9E9');
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    if (arcbarOption.type == 'default') {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, arcbarOption.endAngle * Math.PI, arcbarOption.direction == 'ccw');
+    } else {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, 0, 2 * Math.PI, arcbarOption.direction == 'ccw');
+    }
+    context.stroke();
+
+    // 进度条
+    let fillColor = eachSeries.color;
+    if (arcbarOption.linearType == 'custom') {
+      let grd = context.createLinearGradient(centerPosition.x - radius, centerPosition.y, centerPosition.x + radius, centerPosition.y);
+      grd.addColorStop(1, hexToRgb(arcbarOption.customColor[eachSeries.linearIndex!], 1));
+      grd.addColorStop(0, hexToRgb(eachSeries.color!, 1));
+      fillColor = grd;
+    }
+
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(fillColor);
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, (eachSeries as any)._proportion_ * Math.PI, arcbarOption.direction == 'ccw');
+    context.stroke();
+  }
+
+  drawRingTitle(opts, config, context, centerPosition);
+}
+
+export function drawGaugeDataPoints(
+  categories: any[],
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { center: Point; radius: number; innerRadius: number } {
+  let gaugeOption = assign({}, {
+    type: 'default',
+    startAngle: 0.75,
+    endAngle: 0.25,
+    width: 15,
+    labelOffset: 13,
+    splitLine: {
+      fixRadius: 0,
+      splitNumber: 10,
+      width: 15,
+      color: '#FFFFFF',
+      childNumber: 5,
+      childWidth: 5
+    },
+    pointer: {
+      width: 15,
+      color: 'auto'
+    }
+  }, opts.extra.gauge) as GaugeOption;
+
+  if (gaugeOption.oldAngle == undefined) {
+    gaugeOption.oldAngle = gaugeOption.startAngle;
+  }
+  if (gaugeOption.oldData == undefined) {
+    gaugeOption.oldData = 0;
+  }
+
+  categories = getGaugeAxisPoints(categories, gaugeOption.startAngle, gaugeOption.endAngle);
+
+  let centerPosition = {
+    x: opts.width / 2,
+    y: opts.height / 2
+  };
+
+  let radius = Math.min(centerPosition.x, centerPosition.y);
+  radius -= 5 * opts.pix;
+  radius -= gaugeOption.width / 2;
+  radius = radius < 10 ? 10 : radius;
+  let innerRadius = radius - gaugeOption.width;
+
+  // 判断仪表盘的样式:default百度样式,progress新样式
+  if (gaugeOption.type == 'progress') {
+    // ## 第一步画中心圆形背景和进度条背景
+    let pieRadius = radius - gaugeOption.width * 3;
+    context.beginPath();
+    let gradient = context.createLinearGradient(centerPosition.x, centerPosition.y - pieRadius, centerPosition.x, centerPosition.y + pieRadius);
+    gradient.addColorStop('0', hexToRgb(series[0].color!, 0.3));
+    gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+    context.setFillStyle(gradient);
+    context.arc(centerPosition.x, centerPosition.y, pieRadius, 0, 2 * Math.PI, false);
+    context.fill();
+
+    // 画进度条背景
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(hexToRgb(series[0].color!, 0.3));
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, gaugeOption.endAngle * Math.PI, false);
+    context.stroke();
+
+    // ## 第二步画刻度线
+    let totalAngle = 0;
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+
+    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;
+    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;
+    let endX = -radius - gaugeOption.width - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;
+
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    let len = gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1;
+    let proc = (series[0].data as number) * process;
+
+    for (let i = 0; i < len; i++) {
+      context.beginPath();
+      // 刻度线随进度变色
+      if (proc > (i / len)) {
+        context.setStrokeStyle(hexToRgb(series[0].color!, 1));
+      } else {
+        context.setStrokeStyle(hexToRgb(series[0].color!, 0.3));
+      }
+      context.setLineWidth(3 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(endX, 0);
+      context.stroke();
+      context.rotate(childAngle * Math.PI);
+    }
+    context.restore();
+
+    // ## 第三步画进度条
+    series = getGaugeArcbarDataPoints(series as any, gaugeOption) as any;
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(series[0].color);
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, (series[0] as any)._proportion_ * Math.PI, false);
+    context.stroke();
+
+    // ## 第四步画指针
+    let pointerRadius = radius - gaugeOption.width * 2.5;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate(((series[0] as any)._proportion_ - 1) * Math.PI);
+    context.beginPath();
+    context.setLineWidth(gaugeOption.width / 3);
+    let gradient3 = context.createLinearGradient(0, -pointerRadius * 0.6, 0, pointerRadius * 0.6);
+    gradient3.addColorStop('0', hexToRgb('#FFFFFF', 0));
+    gradient3.addColorStop('0.5', hexToRgb(series[0].color!, 1));
+    gradient3.addColorStop('1.0', hexToRgb('#FFFFFF', 0));
+    context.setStrokeStyle(gradient3);
+    context.arc(0, 0, pointerRadius, 0.85 * Math.PI, 1.15 * Math.PI, false);
+    context.stroke();
+
+    context.beginPath();
+    context.setLineWidth(1);
+    context.setStrokeStyle(series[0].color);
+    context.setFillStyle(series[0].color);
+    context.moveTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2 - 4, 0);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, 4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.stroke();
+    context.fill();
+    context.restore();
+  } else {
+    // default百度样式
+    context.setLineWidth(gaugeOption.width);
+    context.setLineCap('butt');
+    for (let i = 0; i < categories.length; i++) {
+      let eachCategories = categories[i];
+      context.beginPath();
+      context.setStrokeStyle(eachCategories.color);
+      context.arc(centerPosition.x, centerPosition.y, radius, eachCategories._startAngle_! * Math.PI, eachCategories._endAngle_! * Math.PI, false);
+      context.stroke();
+    }
+
+    series = getGaugeArcbarDataPoints(series as any, gaugeOption) as any;
+    let pointerRadius = radius - gaugeOption.width * 2.5;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate(((series[0] as any)._proportion_ - 1) * Math.PI);
+    context.beginPath();
+    context.setLineWidth(gaugeOption.width / 3);
+    context.setStrokeStyle(series[0].color);
+    context.arc(0, 0, pointerRadius, 0.85 * Math.PI, 1.15 * Math.PI, false);
+    context.stroke();
+    context.restore();
+  }
+
+  // 画标签
+  drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context);
+
+  return {
+    center: centerPosition,
+    radius: radius,
+    innerRadius: innerRadius
+  };
+}
+
+export function drawGaugeLabel(
+  gaugeOption: GaugeOption,
+  radius: number,
+  centerPosition: Point,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  radius -= gaugeOption.width / 2 + gaugeOption.labelOffset * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+
+  let totalAngle: number;
+  if (gaugeOption.endAngle! < gaugeOption.startAngle!) {
+    totalAngle = 2 + gaugeOption.endAngle! - gaugeOption.startAngle!;
+  } else {
+    totalAngle = gaugeOption.startAngle! - gaugeOption.endAngle!;
+  }
+
+  let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+  let totalNumber = (gaugeOption.endNumber || 10) - (gaugeOption.startNumber || 0);
+  let splitNumber = totalNumber / gaugeOption.splitLine.splitNumber;
+  let nowAngle = gaugeOption.startAngle!;
+  let nowNumber = gaugeOption.startNumber || 0;
+
+  for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {
+    let pos = {
+      x: radius * Math.cos(nowAngle * Math.PI),
+      y: radius * Math.sin(nowAngle * Math.PI)
+    };
+    let labelText = gaugeOption.formatter ? gaugeOption.formatter(nowNumber, i, opts) : String(nowNumber);
+    pos.x += centerPosition.x - measureText(labelText, config.fontSize, context) / 2;
+    pos.y += centerPosition.y;
+    let startX = pos.x;
+    let startY = pos.y;
+
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(gaugeOption.labelColor || opts.fontColor);
+    context.fillText(labelText, startX, startY + config.fontSize / 2);
+    context.closePath();
+    context.stroke();
+
+    nowAngle += splitAngle;
+    if (nowAngle >= 2) {
+      nowAngle = nowAngle % 2;
+    }
+    nowNumber += splitNumber;
+  }
+}

+ 358 - 0
mini-ui-packages/mini-charts/src/lib/renderers/radar-renderer.ts

@@ -0,0 +1,358 @@
+/**
+ * 雷达图绘制函数
+ *
+ * 从 u-charts 核心库搬迁的雷达图绘制相关函数
+ */
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { getRadarDataPoints } from '../charts-data/radar-charts';
+import { measureText } from '../utils/text';
+import { hexToRgb } from '../utils/color';
+import { convertCoordinateOrigin } from '../utils/coordinate';
+import { assign } from '../config';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface RadarOption {
+  gridColor: string;
+  gridType: string;
+  gridEval: number;
+  axisLabel: boolean;
+  axisLabelTofix: number;
+  labelShow: boolean;
+  labelColor: string;
+  labelPointShow: boolean;
+  labelPointRadius: number;
+  labelPointColor: string;
+  opacity: number;
+  gridCount: number;
+  border: boolean;
+  borderWidth: number;
+  linearType: string;
+  customColor: string[];
+  max?: number;
+  radius?: number;
+}
+
+function getRadarCoordinateSeries(length: number): number[] {
+  let eachAngle = 2 * Math.PI / length;
+  let CoordinateSeries: number[] = [];
+  for (let i = 0; i < length; i++) {
+    CoordinateSeries.push(eachAngle * i);
+  }
+  return CoordinateSeries.map(function(item) {
+    return -1 * item + Math.PI / 2;
+  });
+}
+
+function getMaxTextListLength(list: string[], fontSize: number, context: CanvasContext): number {
+  let lengthList = list.map(function(item) {
+    return measureText(item, fontSize, context);
+  });
+  return Math.max.apply(null, lengthList);
+}
+
+function fillCustomColor(linearType: string, customColor: string[], series: SeriesItem[], config: UChartsConfig): string[] {
+  let newcolor = customColor || [];
+  if (linearType == 'custom' && newcolor.length == 0) {
+    newcolor = config.linearColor;
+  }
+  if (linearType == 'custom' && newcolor.length < series.length) {
+    let chazhi = series.length - newcolor.length;
+    for (let i = 0; i < chazhi; i++) {
+      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);
+    }
+  }
+  return newcolor;
+}
+
+function dataCombine(series: SeriesItem[]): number[] {
+  return series.reduce(function(a: number[], b) {
+    return (a.data ? a.data : a).concat(b.data as number[]);
+  }, [] as number[]);
+}
+
+function drawPointShape(points: Point[], color: string, shape: string, context: CanvasContext, opts: ChartOptions): void {
+  context.beginPath();
+  context.setStrokeStyle(color);
+  context.setFillStyle(color);
+  context.setLineWidth(1 * opts.pix);
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 3.5);
+        context.lineTo(item.x - 3.5, item.y);
+        context.lineTo(item.x, item.y + 3.5);
+        context.lineTo(item.x + 3.5, item.y);
+        context.lineTo(item.x, item.y - 3.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+export function drawRadarDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { center: Point; radius: number; angleList: number[] } {
+  let radarOption = assign({}, {
+    gridColor: '#cccccc',
+    gridType: 'radar',
+    gridEval: 1,
+    axisLabel: false,
+    axisLabelTofix: 0,
+    labelShow: true,
+    labelColor: '#666666',
+    labelPointShow: false,
+    labelPointRadius: 3,
+    labelPointColor: '#cccccc',
+    opacity: 0.2,
+    gridCount: 3,
+    border: false,
+    borderWidth: 2,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.radar) as RadarOption;
+
+  let coordinateAngle = getRadarCoordinateSeries(opts.categories!.length);
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+
+  let xr = (opts.width - opts.area[1] - opts.area[3]) / 2;
+  let yr = (opts.height - opts.area[0] - opts.area[2]) / 2;
+  let radius = Math.min(xr - (getMaxTextListLength(opts.categories!, config.fontSize, context) + config.radarLabelTextMargin), yr - config.radarLabelTextMargin);
+  radius -= config.radarLabelTextMargin * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  radius = radarOption.radius ? radarOption.radius : radius;
+
+  // 画分割线
+  context.beginPath();
+  context.setLineWidth(1 * opts.pix);
+  context.setStrokeStyle(radarOption.gridColor);
+  coordinateAngle.forEach(function(angle, index) {
+    let pos = convertCoordinateOrigin(radius * Math.cos(angle), radius * Math.sin(angle), centerPosition);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    if (index % radarOption.gridEval == 0) {
+      context.lineTo(pos.x, pos.y);
+    }
+  });
+  context.stroke();
+  context.closePath();
+
+  // 画背景网格
+  for (let i = 1; i <= radarOption.gridCount; i++) {
+    let startPos = {};
+    context.beginPath();
+    context.setLineWidth(1 * opts.pix);
+    context.setStrokeStyle(radarOption.gridColor);
+    if (radarOption.gridType == 'radar') {
+      coordinateAngle.forEach(function(angle, index) {
+        let pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(angle), radius /
+          radarOption.gridCount * i * Math.sin(angle), centerPosition);
+        if (index === 0) {
+          startPos = pos;
+          context.moveTo(pos.x, pos.y);
+        } else {
+          context.lineTo(pos.x, pos.y);
+        }
+      });
+      context.lineTo((startPos as Point).x, (startPos as Point).y);
+    } else {
+      let pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(1.5), radius / radarOption.gridCount * i * Math.sin(1.5), centerPosition);
+      context.arc(centerPosition.x, centerPosition.y, centerPosition.y - pos.y, 0, 2 * Math.PI, false);
+    }
+    context.stroke();
+    context.closePath();
+  }
+
+  radarOption.customColor = fillCustomColor(radarOption.linearType, radarOption.customColor, series, config);
+  let radarDataPoints = getRadarDataPoints(coordinateAngle, centerPosition, radius, series as any, opts);
+
+  radarDataPoints.forEach(function(eachSeries, seriesIndex) {
+    // 绘制区域数据
+    context.beginPath();
+    context.setLineWidth(radarOption.borderWidth * opts.pix);
+    context.setStrokeStyle(eachSeries.color!);
+
+    let fillcolor = hexToRgb(eachSeries.color!, radarOption.opacity);
+    if (radarOption.linearType == 'custom') {
+      let grd;
+      if (context.createCircularGradient) {
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, radius);
+      } else {
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0, centerPosition.x, centerPosition.y, radius);
+      }
+      grd.addColorStop(0, hexToRgb(radarOption.customColor[series[seriesIndex].linearIndex!], radarOption.opacity));
+      grd.addColorStop(1, hexToRgb(eachSeries.color!, radarOption.opacity));
+      fillcolor = grd;
+    }
+
+    context.setFillStyle(fillcolor);
+    eachSeries.data.forEach(function(item: any, index: number) {
+      if (index === 0) {
+        context.moveTo(item.position.x, item.position.y);
+      } else {
+        context.lineTo(item.position.x, item.position.y);
+      }
+    });
+    context.closePath();
+    context.fill();
+    if (radarOption.border === true) {
+      context.stroke();
+    }
+    context.closePath();
+
+    if (opts.dataPointShape !== false) {
+      let points = eachSeries.data.map(function(item: any) {
+        return item.position;
+      });
+      drawPointShape(points, eachSeries.color!, eachSeries.pointShape!, context, opts);
+    }
+  });
+
+  // 画刻度值
+  if (radarOption.axisLabel === true) {
+    const maxData = Math.max(radarOption.max || 0, Math.max.apply(null, dataCombine(series)));
+    const stepLength = radius / radarOption.gridCount;
+    const fontSize = opts.fontSize! * opts.pix;
+    context.setFontSize(fontSize);
+    context.setFillStyle(opts.fontColor!);
+    context.setTextAlign('left');
+    for (let i = 0; i < radarOption.gridCount + 1; i++) {
+      let label = i * maxData / radarOption.gridCount;
+      label = Number(label.toFixed(radarOption.axisLabelTofix));
+      context.fillText(String(label), centerPosition.x + 3 * opts.pix, centerPosition.y - i * stepLength + fontSize / 2);
+    }
+  }
+
+  // draw label text
+  drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context);
+
+  // draw dataLabel
+  if (opts.dataLabel !== false && process === 1) {
+    radarDataPoints.forEach(function(eachSeries: any, seriesIndex: number) {
+      context.beginPath();
+      let fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(eachSeries.textColor || opts.fontColor);
+      eachSeries.data.forEach(function(item: any, index: number) {
+        // 如果是中心点垂直的上下点位
+        if (Math.abs(item.position.x - centerPosition.x) < 2) {
+          // 如果在上面
+          if (item.position.y < centerPosition.y) {
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y - 4);
+          } else {
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y + fontSize + 2);
+          }
+        } else {
+          // 如果在左侧
+          if (item.position.x < centerPosition.x) {
+            context.setTextAlign('right');
+            context.fillText(item.value, item.position.x - 4, item.position.y + fontSize / 2 - 2);
+          } else {
+            context.setTextAlign('left');
+            context.fillText(item.value, item.position.x + 4, item.position.y + fontSize / 2 - 2);
+          }
+        }
+      });
+      context.closePath();
+      context.stroke();
+    });
+    context.setTextAlign('left');
+  }
+
+  return {
+    center: centerPosition,
+    radius: radius,
+    angleList: coordinateAngle
+  };
+}
+
+export function drawRadarLabel(
+  angleList: number[],
+  radius: number,
+  centerPosition: Point,
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let radarOption = opts.extra.radar || {};
+  angleList.forEach(function(angle, index) {
+    if (radarOption.labelPointShow === true && opts.categories![index] !== '') {
+      let posPoint = {
+        x: radius * Math.cos(angle),
+        y: radius * Math.sin(angle)
+      };
+      let posPointAxis = convertCoordinateOrigin(posPoint.x, posPoint.y, centerPosition);
+      context.setFillStyle(radarOption.labelPointColor);
+      context.beginPath();
+      context.arc(posPointAxis.x, posPointAxis.y, radarOption.labelPointRadius * opts.pix, 0, 2 * Math.PI, false);
+      context.closePath();
+      context.fill();
+    }
+    if (radarOption.labelShow === true) {
+      let pos = {
+        x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),
+        y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)
+      };
+      let posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);
+      let startX = posRelativeCanvas.x;
+      let startY = posRelativeCanvas.y;
+      if (Math.abs(pos.x) < 1e-10) {
+        startX -= measureText(opts.categories![index] || '', config.fontSize, context) / 2;
+      } else if (pos.x < 0) {
+        startX -= measureText(opts.categories![index] || '', config.fontSize, context);
+      }
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(radarOption.labelColor || opts.fontColor);
+      context.fillText(opts.categories![index] || '', startX, startY + config.fontSize / 2);
+      context.closePath();
+      context.stroke();
+    }
+  });
+}

+ 688 - 0
mini-ui-packages/mini-charts/src/lib/renderers/special-renderer.ts

@@ -0,0 +1,688 @@
+/**
+ * 特殊图表绘制函数
+ *
+ * 从 u-charts 核心库搬迁的特殊图表绘制相关函数
+ * 包括散点图、气泡图、混合图、词云、漏斗图等
+ */
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
+
+import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
+import { getDataPoints } from '../charts-data/basic-charts';
+import { getFunnelDataPoints } from '../charts-data/funnel-charts';
+import { measureText } from '../utils/text';
+import { hexToRgb } from '../utils/color';
+import { splitPoints, createCurveControlPoints } from '../utils/misc';
+import { assign } from '../config';
+
+export type CanvasContext = any;
+
+export interface Point {
+  x: number;
+  y: number;
+  r?: number;
+  t?: string;
+}
+
+const util = {
+  toFixed: function toFixed(num: number, limit: number): string {
+    limit = limit || 2;
+    if (this.isFloat(num)) {
+      return num.toFixed(limit);
+    }
+    return String(num);
+  },
+  isFloat: function isFloat(num: number): boolean {
+    return num % 1 !== 0;
+  }
+};
+
+function drawPointText(points: Point[], series: SeriesItem, config: UChartsConfig, context: CanvasContext, opts: ChartOptions): void {
+  context.beginPath();
+  context.setFontSize(series.textSize * opts.pix || config.fontSize);
+  context.setFillStyle(series.textColor || opts.fontColor);
+  context.setTextAlign('center');
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.fillText(String(item.y || item.value || ''), item.x, item.y - 4);
+    }
+  });
+  context.closePath();
+  context.stroke();
+}
+
+export function drawScatterDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { xAxisPoints: number[]; calPoints: any[]; eachSpacing: number } {
+  let scatterOption = assign({}, {
+    type: 'circle'
+  }, opts.extra.scatter);
+
+  let xAxisData = opts.chartData.xAxisData;
+  let xAxisPoints = xAxisData.xAxisPoints;
+  let eachSpacing = xAxisData.eachSpacing;
+  let calPoints: any[] = [];
+
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setFillStyle(eachSeries.color);
+    context.setLineWidth(1 * opts.pix);
+    let shape = eachSeries.pointShape;
+
+    if (shape === 'diamond') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y);
+          context.lineTo(item.x, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else if (shape === 'circle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x + 2.5 * opts.pix, item.y);
+          context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+        }
+      });
+    } else if (shape === 'square') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x - 3.5, item.y - 3.5);
+          context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+        }
+      });
+    } else if (shape === 'triangle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y + 4.5);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x + 2.5 * opts.pix, item.y);
+          context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+        }
+      });
+    }
+    context.closePath();
+    context.fill();
+    context.stroke();
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+export function drawBubbleDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { xAxisPoints: number[]; calPoints: any[]; eachSpacing: number } {
+  let bubbleOption = assign({}, {
+    opacity: 1,
+    border: 2
+  }, opts.extra.bubble);
+
+  let xAxisData = opts.chartData.xAxisData;
+  let xAxisPoints = xAxisData.xAxisPoints;
+  let eachSpacing = xAxisData.eachSpacing;
+  let calPoints: any[] = [];
+
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setLineWidth(bubbleOption.border * opts.pix);
+    context.setFillStyle(hexToRgb(eachSeries.color!, bubbleOption.opacity));
+
+    points.forEach(function(item: any, index) {
+      context.moveTo(item.x + item.r, item.y);
+      context.arc(item.x, item.y, item.r * opts.pix, 0, 2 * Math.PI, false);
+    });
+
+    context.closePath();
+    context.fill();
+    context.stroke();
+
+    if (opts.dataLabel !== false && process === 1) {
+      points.forEach(function(item: any, index) {
+        context.beginPath();
+        let fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+        context.setFontSize(fontSize);
+        context.setFillStyle(eachSeries.textColor || "#FFFFFF");
+        context.setTextAlign('center');
+        context.fillText(String(item.t), item.x, item.y + fontSize / 2);
+        context.closePath();
+        context.stroke();
+        context.setTextAlign('left');
+      });
+    }
+  });
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+export function drawMixDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { xAxisPoints: number[]; calPoints: any[]; eachSpacing: number } {
+  let xAxisData = opts.chartData.xAxisData;
+  let xAxisPoints = xAxisData.xAxisPoints;
+  let eachSpacing = xAxisData.eachSpacing;
+
+  let columnOption = assign({}, {
+    width: eachSpacing / 2,
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.mix.column);
+
+  let areaOption = assign({}, {
+    opacity: 0.2,
+    gradient: false
+  }, opts.extra.mix.area);
+
+  let lineOption = assign({}, {
+    width: 2
+  }, opts.extra.mix.line);
+
+  let endY = opts.height - opts.area[2];
+  let calPoints: any[] = [];
+  let columnIndex = 0;
+  let columnLength = 0;
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (eachSeries.type == 'column') {
+      columnLength += 1;
+    }
+  });
+
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+
+  // fillCustomColor implementation would be here
+  columnOption.customColor = columnOption.customColor || [];
+
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    calPoints.push(points);
+
+    // 绘制柱状数据图
+    if (eachSeries.type == 'column') {
+      // Simplified column drawing
+      for (let i = 0; i < points.length; i++) {
+        let item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          let startX = item.x - eachSpacing / columnLength / 2;
+          let itemWidth = eachSpacing / columnLength - columnOption.seriesGap;
+          context.beginPath();
+          context.setFillStyle(eachSeries.color);
+          context.rect(startX, item.y, itemWidth, opts.height - opts.area[2] - item.y);
+          context.closePath();
+          context.fill();
+        }
+      }
+      columnIndex += 1;
+    }
+
+    // 绘制区域图数据
+    if (eachSeries.type == 'area') {
+      let splitPointList = splitPoints(points, eachSeries);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setFillStyle(hexToRgb(eachSeries.color!, areaOption.opacity));
+        context.setLineWidth(2 * opts.pix);
+
+        if (points.length > 1) {
+          let firstPoint = points[0];
+          let lastPoint = points[points.length - 1];
+          context.moveTo(firstPoint.x, firstPoint.y);
+          let startPoint = 0;
+
+          if (eachSeries.style === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            }
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+          context.lineTo(lastPoint.x, endY);
+          context.lineTo(firstPoint.x, endY);
+          context.lineTo(firstPoint.x, firstPoint.y);
+        }
+        context.closePath();
+        context.fill();
+      }
+    }
+
+    // 绘制折线数据图
+    if (eachSeries.type == 'line') {
+      let splitPointList = splitPoints(points, eachSeries);
+      splitPointList.forEach(function(points, index) {
+        if (eachSeries.lineType == 'dash') {
+          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+          dashLength *= opts.pix;
+          context.setLineDash([dashLength, dashLength]);
+        }
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(lineOption.width * opts.pix);
+
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+
+          if (eachSeries.style == 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            }
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+        }
+        context.stroke();
+        context.setLineDash([]);
+      });
+    }
+
+    // 绘制点数据图
+    if (eachSeries.type == 'point') {
+      eachSeries.addPoint = true;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      if (eachSeries.type !== 'column') {
+        drawPointText(points, eachSeries, config, context, opts);
+      }
+    });
+  }
+
+  context.restore();
+
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+export function drawWordCloudDataPoints(
+  series: SeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): void {
+  let wordOption = assign({}, {
+    type: 'normal',
+    autoColors: true
+  }, opts.extra.word);
+
+  if (!opts.chartData.wordCloudData) {
+    opts.chartData.wordCloudData = getWordCloudPoint(opts, wordOption.type, context);
+  }
+
+  context.beginPath();
+  context.setFillStyle(opts.background);
+  context.rect(0, 0, opts.width, opts.height);
+  context.fill();
+
+  context.save();
+  let points = opts.chartData.wordCloudData;
+  context.translate(opts.width / 2, opts.height / 2);
+
+  for (let i = 0; i < points.length; i++) {
+    context.save();
+    if ((points[i] as any).rotate) {
+      context.rotate(90 * Math.PI / 180);
+    }
+    let text = (points[i] as any).name;
+    let tHeight = (points[i] as any).textSize * opts.pix;
+    let tWidth = measureText(text, tHeight, context);
+
+    context.beginPath();
+    context.setStrokeStyle((points[i] as any).color);
+    context.setFillStyle((points[i] as any).color);
+    context.setFontSize(tHeight);
+
+    if ((points[i] as any).rotate) {
+      if ((points[i] as any).areav[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, ((points[i] as any).areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).areav[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, ((points[i] as any).areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).areav[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, ((points[i] as any).areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).areav[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    } else {
+      if ((points[i] as any).area[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, ((points[i] as any).area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).area[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, ((points[i] as any).area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).area[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, ((points[i] as any).area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, ((points[i] as any).area[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    }
+    context.stroke();
+    context.restore();
+  }
+
+  context.restore();
+}
+
+function getWordCloudPoint(opts: ChartOptions, type: string, context: CanvasContext): any[] {
+  let points = opts.series;
+  // Simplified implementation
+  return points.map((p: any) => ({
+    ...p,
+    area: [p.x || 0, p.y || 0, (p.x || 0) + 100, (p.y || 0) + 20],
+    rotate: false
+  }));
+}
+
+export interface FunnelSeriesItem extends SeriesItem {
+  _proportion_?: number;
+  radius?: number;
+  funnelArea?: [number, number, number, number];
+  labelShow?: boolean;
+  labelText?: string;
+  formatter?: (item: any, index: number, series: any[], opts: any) => string;
+  textColor?: string;
+  textSize?: number;
+  centerText?: string;
+  centerTextColor?: string;
+  centerTextSize?: number;
+  linearIndex?: number;
+}
+
+export function drawFunnelDataPoints(
+  series: FunnelSeriesItem[],
+  opts: ChartOptions,
+  config: UChartsConfig,
+  context: CanvasContext
+): { center: any; radius: number; series: FunnelSeriesItem[] } {
+  let funnelOption = assign({}, {
+    type: 'funnel',
+    activeWidth: 10,
+    activeOpacity: 0.3,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    fillOpacity: 1,
+    minSize: 0,
+    labelAlign: 'right',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.funnel);
+
+  let eachSpacing = (opts.height - opts.area[0] - opts.area[2]) / series.length;
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.height - opts.area[2]
+  };
+  let activeWidth = funnelOption.activeWidth * opts.pix;
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - activeWidth, (opts.height - opts.area[0] - opts.area[2]) / 2 - activeWidth);
+
+  let seriesNew = getFunnelDataPoints(series, radius, funnelOption, eachSpacing);
+
+  context.save();
+  context.translate(centerPosition.x, centerPosition.y);
+
+  // fillCustomColor implementation would be here
+  funnelOption.customColor = funnelOption.customColor || [];
+
+  if (funnelOption.type == 'pyramid') {
+    // Pyramid drawing
+    for (let i = 0; i < seriesNew.length; i++) {
+      // Simplified implementation
+      context.beginPath();
+      context.setFillStyle(seriesNew[i].color);
+      context.arc(0, 0, seriesNew[i].radius || 0, 0, 2 * Math.PI);
+      context.fill();
+      context.translate(0, eachSpacing);
+    }
+  } else {
+    // Funnel drawing
+    context.translate(0, -(seriesNew.length - 1) * eachSpacing);
+    for (let i = 0; i < seriesNew.length; i++) {
+      // Simplified implementation
+      context.beginPath();
+      context.setFillStyle(seriesNew[i].color);
+      context.arc(0, 0, seriesNew[i].radius || 0, 0, 2 * Math.PI);
+      context.fill();
+      context.translate(0, eachSpacing);
+    }
+  }
+
+  context.restore();
+
+  if (opts.dataLabel !== false && process === 1) {
+    drawFunnelText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+
+  if (process === 1) {
+    drawFunnelCenterText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: seriesNew
+  };
+}
+
+export function drawFunnelText(
+  series: FunnelSeriesItem[],
+  opts: ChartOptions,
+  context: CanvasContext,
+  eachSpacing: number,
+  labelAlign: string,
+  activeWidth: number,
+  centerPosition: any
+): void {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    if (item.labelShow === false) {
+      continue;
+    }
+    let startX, endX, startY, fontSize;
+    let text = item.formatter ? item.formatter(item, i, series, opts) : util.toFixed((item._proportion_ || 0) * 100, 2) + '%';
+    text = item.labelText ? item.labelText : text;
+
+    if (labelAlign == 'right') {
+      if (i == series.length - 1) {
+        startX = (item.funnelArea![2] + centerPosition.x) / 2;
+      } else {
+        startX = (item.funnelArea![2] + series[i + 1].funnelArea![2]) / 2;
+      }
+      endX = startX + activeWidth * 2;
+      startY = item.funnelArea![1] + eachSpacing / 2;
+      fontSize = item.textSize! * opts.pix || opts.fontSize! * opts.pix;
+
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.color);
+      context.setFillStyle(item.color);
+      context.beginPath();
+      context.moveTo(startX, startY);
+      context.lineTo(endX, startY);
+      context.stroke();
+      context.closePath();
+
+      context.beginPath();
+      context.moveTo(endX, startY);
+      context.arc(endX, startY, 2 * opts.pix, 0, 2 * Math.PI);
+      context.closePath();
+      context.fill();
+
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.textColor || opts.fontColor);
+      context.fillText(text, endX + 5, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+    }
+  }
+}
+
+export function drawFunnelCenterText(
+  series: FunnelSeriesItem[],
+  opts: ChartOptions,
+  context: CanvasContext,
+  eachSpacing: number,
+  labelAlign: string,
+  activeWidth: number,
+  centerPosition: any
+): void {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    let startY, fontSize;
+    if (item.centerText) {
+      startY = item.funnelArea![1] + eachSpacing / 2;
+      fontSize = (item.centerTextSize || opts.fontSize) * opts.pix;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.centerTextColor || "#FFFFFF");
+      context.fillText(item.centerText, centerPosition.x - measureText(item.centerText, fontSize, context) / 2, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+    }
+  }
+}

+ 7706 - 0
mini-ui-packages/mini-charts/src/lib/u-charts-original.js

@@ -0,0 +1,7706 @@
+/*
+ * uCharts (R)
+ * 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360/快手)、Vue、Taro等支持canvas的框架平台
+ * Copyright (C) 2018-2022 QIUN (R) 秋云 https://www.ucharts.cn All rights reserved.
+ * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+ * 复制使用请保留本段注释,感谢支持开源!
+ * 
+ * uCharts (R) 官方网站
+ * https://www.uCharts.cn
+ * 
+ * 开源地址:
+ * https://gitee.com/uCharts/uCharts
+ * 
+ * uni-app插件市场地址:
+ * http://ext.dcloud.net.cn/plugin?id=271
+ * 
+ */
+
+'use strict';
+
+var config = {
+  version: 'v2.5.0-20230101',
+  yAxisWidth: 15,
+  xAxisHeight: 22,
+  padding: [10, 10, 10, 10],
+  rotate: false,
+  fontSize: 13,
+  fontColor: '#666666',
+  dataPointShape: ['circle', 'circle', 'circle', 'circle'],
+  color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],
+  linearColor: ['#0EE2F8', '#2BDCA8', '#FA7D8D', '#EB88E2', '#2AE3A0', '#0EE2F8', '#EB88E2', '#6773E3', '#F78A85'],
+  pieChartLinePadding: 15,
+  pieChartTextPadding: 5,
+  titleFontSize: 20,
+  subtitleFontSize: 15,
+  radarLabelTextMargin: 13,
+};
+
+var assign = function(target, ...varArgs) {
+  if (target == null) {
+    throw new TypeError('[uCharts] Cannot convert undefined or null to object');
+  }
+  if (!varArgs || varArgs.length <= 0) {
+    return target;
+  }
+  // 深度合并对象
+  function deepAssign(obj1, obj2) {
+    for (let key in obj2) {
+      obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" ?
+        deepAssign(obj1[key], obj2[key]) : obj1[key] = obj2[key];
+    }
+    return obj1;
+  }
+  varArgs.forEach(val => {
+    target = deepAssign(target, val);
+  });
+  return target;
+};
+
+var util = {
+  toFixed: function toFixed(num, limit) {
+    limit = limit || 2;
+    if (this.isFloat(num)) {
+      num = num.toFixed(limit);
+    }
+    return num;
+  },
+  isFloat: function isFloat(num) {
+    return num % 1 !== 0;
+  },
+  approximatelyEqual: function approximatelyEqual(num1, num2) {
+    return Math.abs(num1 - num2) < 1e-10;
+  },
+  isSameSign: function isSameSign(num1, num2) {
+    return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2;
+  },
+  isSameXCoordinateArea: function isSameXCoordinateArea(p1, p2) {
+    return this.isSameSign(p1.x, p2.x);
+  },
+  isCollision: function isCollision(obj1, obj2) {
+    obj1.end = {};
+    obj1.end.x = obj1.start.x + obj1.width;
+    obj1.end.y = obj1.start.y - obj1.height;
+    obj2.end = {};
+    obj2.end.x = obj2.start.x + obj2.width;
+    obj2.end.y = obj2.start.y - obj2.height;
+    var flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y;
+    return !flag;
+  }
+};
+
+//兼容H5点击事件
+function getH5Offset(e) {
+  e.mp = {
+    changedTouches: []
+  };
+  e.mp.changedTouches.push({
+    x: e.offsetX,
+    y: e.offsetY
+  });
+  return e;
+}
+
+// hex 转 rgba
+function hexToRgb(hexValue, opc) {
+  var rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+  var hex = hexValue.replace(rgx, function(m, r, g, b) {
+    return r + r + g + g + b + b;
+  });
+  var rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+  var r = parseInt(rgb[1], 16);
+  var g = parseInt(rgb[2], 16);
+  var b = parseInt(rgb[3], 16);
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + opc + ')';
+}
+
+function findRange(num, type, limit) {
+  if (isNaN(num)) {
+    throw new Error('[uCharts] series数据需为Number格式');
+  }
+  limit = limit || 10;
+  type = type ? type : 'upper';
+  var multiple = 1;
+  while (limit < 1) {
+    limit *= 10;
+    multiple *= 10;
+  }
+  if (type === 'upper') {
+    num = Math.ceil(num * multiple);
+  } else {
+    num = Math.floor(num * multiple);
+  }
+  while (num % limit !== 0) {
+    if (type === 'upper') {
+      if (num == num + 1) { //修复数据值过大num++无效的bug by 向日葵 @xrk_jy
+        break;
+      }
+      num++;
+    } else {
+      num--;
+    }
+  }
+  return num / multiple;
+}
+
+function calCandleMA(dayArr, nameArr, colorArr, kdata) {
+  let seriesTemp = [];
+  for (let k = 0; k < dayArr.length; k++) {
+    let seriesItem = {
+      data: [],
+      name: nameArr[k],
+      color: colorArr[k]
+    };
+    for (let i = 0, len = kdata.length; i < len; i++) {
+      if (i < dayArr[k]) {
+        seriesItem.data.push(null);
+        continue;
+      }
+      let sum = 0;
+      for (let j = 0; j < dayArr[k]; j++) {
+        sum += kdata[i - j][1];
+      }
+      seriesItem.data.push(+(sum / dayArr[k]).toFixed(3));
+    }
+    seriesTemp.push(seriesItem);
+  }
+  return seriesTemp;
+}
+
+function calValidDistance(self, distance, chartData, config, opts) {
+  var dataChartAreaWidth = opts.width - opts.area[1] - opts.area[3];
+  var dataChartWidth = chartData.eachSpacing * (opts.chartData.xAxisData.xAxisPoints.length - 1);
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    dataChartWidth += (opts.extra.mount.widthRatio - 1)*chartData.eachSpacing;
+  }
+  var validDistance = distance;
+  if (distance >= 0) {
+    validDistance = 0;
+    self.uevent.trigger('scrollLeft');
+    self.scrollOption.position = 'left'
+    opts.xAxis.scrollPosition = 'left';
+  } else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) {
+    validDistance = dataChartAreaWidth - dataChartWidth;
+    self.uevent.trigger('scrollRight');
+    self.scrollOption.position = 'right'
+    opts.xAxis.scrollPosition = 'right';
+  } else {
+    self.scrollOption.position = distance
+    opts.xAxis.scrollPosition = distance;
+  }
+  return validDistance;
+}
+
+function isInAngleRange(angle, startAngle, endAngle) {
+  function adjust(angle) {
+    while (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+    while (angle > 2 * Math.PI) {
+      angle -= 2 * Math.PI;
+    }
+    return angle;
+  }
+  angle = adjust(angle);
+  startAngle = adjust(startAngle);
+  endAngle = adjust(endAngle);
+  if (startAngle > endAngle) {
+    endAngle += 2 * Math.PI;
+    if (angle < startAngle) {
+      angle += 2 * Math.PI;
+    }
+  }
+  return angle >= startAngle && angle <= endAngle;
+}
+
+function createCurveControlPoints(points, i) {
+  function isNotMiddlePoint(points, i) {
+    if (points[i - 1] && points[i + 1]) {
+      return points[i].y >= Math.max(points[i - 1].y, points[i + 1].y) || points[i].y <= Math.min(points[i - 1].y,
+        points[i + 1].y);
+    } else {
+      return false;
+    }
+  }
+  function isNotMiddlePointX(points, i) {
+    if (points[i - 1] && points[i + 1]) {
+      return points[i].x >= Math.max(points[i - 1].x, points[i + 1].x) || points[i].x <= Math.min(points[i - 1].x,
+        points[i + 1].x);
+    } else {
+      return false;
+    }
+  }
+  var a = 0.2;
+  var b = 0.2;
+  var pAx = null;
+  var pAy = null;
+  var pBx = null;
+  var pBy = null;
+  if (i < 1) {
+    pAx = points[0].x + (points[1].x - points[0].x) * a;
+    pAy = points[0].y + (points[1].y - points[0].y) * a;
+  } else {
+    pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a;
+    pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a;
+  }
+
+  if (i > points.length - 3) {
+    var last = points.length - 1;
+    pBx = points[last].x - (points[last].x - points[last - 1].x) * b;
+    pBy = points[last].y - (points[last].y - points[last - 1].y) * b;
+  } else {
+    pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b;
+    pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b;
+  }
+  if (isNotMiddlePoint(points, i + 1)) {
+    pBy = points[i + 1].y;
+  }
+  if (isNotMiddlePoint(points, i)) {
+    pAy = points[i].y;
+  }
+  if (isNotMiddlePointX(points, i + 1)) {
+    pBx = points[i + 1].x;
+  }
+  if (isNotMiddlePointX(points, i)) {
+    pAx = points[i].x;
+  }
+  if (pAy >= Math.max(points[i].y, points[i + 1].y) || pAy <= Math.min(points[i].y, points[i + 1].y)) {
+    pAy = points[i].y;
+  }
+  if (pBy >= Math.max(points[i].y, points[i + 1].y) || pBy <= Math.min(points[i].y, points[i + 1].y)) {
+    pBy = points[i + 1].y;
+  }
+  if (pAx >= Math.max(points[i].x, points[i + 1].x) || pAx <= Math.min(points[i].x, points[i + 1].x)) {
+    pAx = points[i].x;
+  }
+  if (pBx >= Math.max(points[i].x, points[i + 1].x) || pBx <= Math.min(points[i].x, points[i + 1].x)) {
+    pBx = points[i + 1].x;
+  }
+  return {
+    ctrA: {
+      x: pAx,
+      y: pAy
+    },
+    ctrB: {
+      x: pBx,
+      y: pBy
+    }
+  };
+}
+
+
+function convertCoordinateOrigin(x, y, center) {
+  return {
+    x: center.x + x,
+    y: center.y - y
+  };
+}
+
+function avoidCollision(obj, target) {
+  if (target) {
+    // is collision test
+    while (util.isCollision(obj, target)) {
+      if (obj.start.x > 0) {
+        obj.start.y--;
+      } else if (obj.start.x < 0) {
+        obj.start.y++;
+      } else {
+        if (obj.start.y > 0) {
+          obj.start.y++;
+        } else {
+          obj.start.y--;
+        }
+      }
+    }
+  }
+  return obj;
+}
+
+function fixPieSeries(series, opts, config){
+  let pieSeriesArr = [];
+  if(series.length>0 && series[0].data.constructor.toString().indexOf('Array') > -1){
+    opts._pieSeries_ = series;
+    let oldseries = series[0].data;
+    for (var i = 0; i < oldseries.length; i++) {
+      oldseries[i].formatter = series[0].formatter;
+      oldseries[i].data = oldseries[i].value;
+      pieSeriesArr.push(oldseries[i]);
+    }
+    opts.series = pieSeriesArr;
+  }else{
+    pieSeriesArr = series;
+  }
+  return pieSeriesArr;
+}
+
+function fillSeries(series, opts, config) {
+  var index = 0;
+  for (var i = 0; i < series.length; i++) {
+    let item = series[i];
+    if (!item.color) {
+      item.color = config.color[index];
+      index = (index + 1) % config.color.length;
+    }
+    if (!item.linearIndex) {
+      item.linearIndex = i;
+    }
+    if (!item.index) {
+      item.index = 0;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (typeof item.show == "undefined") {
+      item.show = true;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (!item.pointShape) {
+      item.pointShape = "circle";
+    }
+    if (!item.legendShape) {
+      switch (item.type) {
+        case 'line':
+          item.legendShape = "line";
+          break;
+        case 'column':
+        case 'bar':
+          item.legendShape = "rect";
+          break;
+        case 'area':
+        case 'mount':
+          item.legendShape = "triangle";
+          break;
+        default:
+          item.legendShape = "circle";
+      }
+    }
+  }
+  return series;
+}
+
+function fillCustomColor(linearType, customColor, series, config) {
+  var newcolor = customColor || [];
+  if (linearType == 'custom' && newcolor.length == 0 ) {
+    newcolor = config.linearColor;
+  }
+  if (linearType == 'custom' && newcolor.length < series.length) {
+    let chazhi = series.length - newcolor.length;
+    for (var i = 0; i < chazhi; i++) {
+      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);
+    }
+  }
+  return newcolor;
+}
+
+function getDataRange(minData, maxData) {
+  var limit = 0;
+  var range = maxData - minData;
+  if (range >= 10000) {
+    limit = 1000;
+  } else if (range >= 1000) {
+    limit = 100;
+  } else if (range >= 100) {
+    limit = 10;
+  } else if (range >= 10) {
+    limit = 5;
+  } else if (range >= 1) {
+    limit = 1;
+  } else if (range >= 0.1) {
+    limit = 0.1;
+  } else if (range >= 0.01) {
+    limit = 0.01;
+  } else if (range >= 0.001) {
+    limit = 0.001;
+  } else if (range >= 0.0001) {
+    limit = 0.0001;
+  } else if (range >= 0.00001) {
+    limit = 0.00001;
+  } else {
+    limit = 0.000001;
+  }
+  return {
+    minRange: findRange(minData, 'lower', limit),
+    maxRange: findRange(maxData, 'upper', limit)
+  };
+}
+
+function measureText(text, fontSize, context) {
+  var width = 0;
+  text = String(text);
+  // #ifdef MP-ALIPAY || MP-BAIDU || APP-NVUE
+  context = false;
+  // #endif
+  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {
+    context.setFontSize(fontSize);
+    return context.measureText(text).width;
+  } else {
+    var text = text.split('');
+    for (let i = 0; i < text.length; i++) {
+      let item = text[i];
+      if (/[a-zA-Z]/.test(item)) {
+        width += 7;
+      } else if (/[0-9]/.test(item)) {
+        width += 5.5;
+      } else if (/\./.test(item)) {
+        width += 2.7;
+      } else if (/-/.test(item)) {
+        width += 3.25;
+      } else if (/:/.test(item)) {
+        width += 2.5;
+      } else if (/[\u4e00-\u9fa5]/.test(item)) {
+        width += 10;
+      } else if (/\(|\)/.test(item)) {
+        width += 3.73;
+      } else if (/\s/.test(item)) {
+        width += 2.5;
+      } else if (/%/.test(item)) {
+        width += 8;
+      } else {
+        width += 10;
+      }
+    }
+    return width * fontSize / 10;
+  }
+}
+
+function dataCombine(series) {
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data);
+  }, []);
+}
+
+function dataCombineStack(series, len) {
+  var sum = new Array(len);
+  for (var j = 0; j < sum.length; j++) {
+    sum[j] = 0;
+  }
+  for (var i = 0; i < series.length; i++) {
+    for (var j = 0; j < sum.length; j++) {
+      sum[j] += series[i].data[j];
+    }
+  }
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data).concat(sum);
+  }, []);
+}
+
+function getTouches(touches, opts, e) {
+  let x, y;
+  if (touches.clientX) {
+    if (opts.rotate) {
+      y = opts.height - touches.clientX * opts.pix;
+      x = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    } else {
+      x = touches.clientX * opts.pix;
+      y = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    }
+  } else {
+    if (opts.rotate) {
+      y = opts.height - touches.x * opts.pix;
+      x = touches.y * opts.pix;
+    } else {
+      x = touches.x * opts.pix;
+      y = touches.y * opts.pix;
+    }
+  }
+  return {
+    x: x,
+    y: y
+  }
+}
+
+function getSeriesDataItem(series, index, group) {
+  var data = [];
+  var newSeries = [];
+  var indexIsArr = index.constructor.toString().indexOf('Array') > -1;
+  if(indexIsArr){
+    let tempSeries = filterSeries(series);
+    for (var i = 0; i < group.length; i++) {
+      newSeries.push(tempSeries[group[i]]);
+    }
+  }else{
+    newSeries = series;
+  };
+  for (let i = 0; i < newSeries.length; i++) {
+    let item = newSeries[i];
+    let tmpindex = -1;
+    if(indexIsArr){
+      tmpindex = index[i];
+    }else{
+      tmpindex = index;
+    }
+    if (item.data[tmpindex] !== null && typeof item.data[tmpindex] !== 'undefined' && item.show) {
+      let seriesItem = {};
+      seriesItem.color = item.color;
+      seriesItem.type = item.type;
+      seriesItem.style = item.style;
+      seriesItem.pointShape = item.pointShape;
+      seriesItem.disableLegend = item.disableLegend;
+      seriesItem.legendShape = item.legendShape;
+      seriesItem.name = item.name;
+      seriesItem.show = item.show;
+      seriesItem.data = item.formatter ? item.formatter(item.data[tmpindex]) : item.data[tmpindex];
+      data.push(seriesItem);
+    }
+  }
+  return data;
+}
+
+function getMaxTextListLength(list, fontSize, context) {
+  var lengthList = list.map(function(item) {
+    return measureText(item, fontSize, context);
+  });
+  return Math.max.apply(null, lengthList);
+}
+
+function getRadarCoordinateSeries(length) {
+  var eachAngle = 2 * Math.PI / length;
+  var CoordinateSeries = [];
+  for (var i = 0; i < length; i++) {
+    CoordinateSeries.push(eachAngle * i);
+  }
+  return CoordinateSeries.map(function(item) {
+    return -1 * item + Math.PI / 2;
+  });
+}
+
+function getToolTipData(seriesData, opts, index, group, categories) {
+  var option = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
+  var calPoints = opts.chartData.calPoints?opts.chartData.calPoints:[];
+  let points = {};
+  if(group.length > 0){
+    let filterPoints = [];
+    for (let i = 0; i < group.length; i++) {
+      filterPoints.push(calPoints[group[i]])
+    }
+    points = filterPoints[0][index[0]];
+  }else{
+    for (let i = 0; i < calPoints.length; i++) {
+      if(calPoints[i][index]){
+        points = calPoints[i][index];
+        break;
+      }
+    }
+  };
+  var textList = seriesData.map(function(item) {
+    let titleText = null;
+    if (opts.categories && opts.categories.length>0) {
+      titleText = categories[index];
+    };
+    return {
+      text: option.formatter ? option.formatter(item, titleText, index, opts) : item.name + ': ' + item.data,
+      color: item.color,
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+  });
+  var offset = {
+    x: Math.round(points.x),
+    y: Math.round(points.y)
+  };
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function getMixToolTipData(seriesData, opts, index, categories) {
+  var option = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
+  var points = opts.chartData.xAxisPoints[index] + opts.chartData.eachSpacing / 2;
+  var textList = seriesData.map(function(item) {
+    return {
+      text: option.formatter ? option.formatter(item, categories[index], index, opts) : item.name + ': ' + item.data,
+      color: item.color,
+      disableLegend: item.disableLegend ? true : false,
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+  });
+  textList = textList.filter(function(item) {
+    if (item.disableLegend !== true) {
+      return item;
+    }
+  });
+  var offset = {
+    x: Math.round(points),
+    y: 0
+  };
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function getCandleToolTipData(series, seriesData, opts, index, categories, extra) {
+  var option = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};
+  var calPoints = opts.chartData.calPoints;
+  let upColor = extra.color.upFill;
+  let downColor = extra.color.downFill;
+  //颜色顺序为开盘,收盘,最低,最高
+  let color = [upColor, upColor, downColor, upColor];
+  var textList = [];
+  seriesData.map(function(item) {
+    if (index == 0) {
+      if (item.data[1] - item.data[0] < 0) {
+        color[1] = downColor;
+      } else {
+        color[1] = upColor;
+      }
+    } else {
+      if (item.data[0] < series[index - 1][1]) {
+        color[0] = downColor;
+      }
+      if (item.data[1] < item.data[0]) {
+        color[1] = downColor;
+      }
+      if (item.data[2] > series[index - 1][1]) {
+        color[2] = upColor;
+      }
+      if (item.data[3] < series[index - 1][1]) {
+        color[3] = downColor;
+      }
+    }
+    let text1 = {
+      text: '开盘:' + item.data[0],
+      color: color[0],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text2 = {
+      text: '收盘:' + item.data[1],
+      color: color[1],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text3 = {
+      text: '最低:' + item.data[2],
+      color: color[2],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text4 = {
+      text: '最高:' + item.data[3],
+      color: color[3],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    textList.push(text1, text2, text3, text4);
+  });
+  var validCalPoints = [];
+  var offset = {
+    x: 0,
+    y: 0
+  };
+  for (let i = 0; i < calPoints.length; i++) {
+    let points = calPoints[i];
+    if (typeof points[index] !== 'undefined' && points[index] !== null) {
+      validCalPoints.push(points[index]);
+    }
+  }
+  offset.x = Math.round(validCalPoints[0][0].x);
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function filterSeries(series) {
+  let tempSeries = [];
+  for (let i = 0; i < series.length; i++) {
+    if (series[i].show == true) {
+      tempSeries.push(series[i])
+    }
+  }
+  return tempSeries;
+}
+
+function findCurrentIndex(currentPoints, calPoints, opts, config) {
+  var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
+  var current={ index:-1, group:[] };
+  var spacing = opts.chartData.eachSpacing / 2;
+  let xAxisPoints = [];
+  if (calPoints && calPoints.length > 0) {
+    if (!opts.categories) {
+      spacing = 0;
+    }else{
+      for (let i = 1; i < opts.chartData.xAxisPoints.length; i++) {
+        xAxisPoints.push(opts.chartData.xAxisPoints[i] - spacing);
+      }
+      if ((opts.type == 'line' || opts.type == 'area') && opts.xAxis.boundaryGap == 'justify') {
+        xAxisPoints = opts.chartData.xAxisPoints;
+      }
+    }
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      if (!opts.categories) {
+        let timePoints = Array(calPoints.length);
+        for (let i = 0; i < calPoints.length; i++) {
+          timePoints[i] = Array(calPoints[i].length)
+          for (let j = 0; j < calPoints[i].length; j++) {
+            timePoints[i][j] = (Math.abs(calPoints[i][j].x - currentPoints.x));
+          }
+        };
+        let pointValue =  Array(timePoints.length);
+        let pointIndex =  Array(timePoints.length);
+        for (let i = 0; i < timePoints.length; i++) {
+          pointValue[i] = Math.min.apply(null, timePoints[i]);
+          pointIndex[i] = timePoints[i].indexOf(pointValue[i]);
+        }
+        let minValue = Math.min.apply(null, pointValue);
+        current.index = [];
+        for (let i = 0; i < pointValue.length; i++) {
+          if(pointValue[i] == minValue){
+            current.group.push(i);
+            current.index.push(pointIndex[i]);
+          }
+        };
+      }else{
+        xAxisPoints.forEach(function(item, index) {
+          if (currentPoints.x + offset + spacing > item) {
+            current.index = index;
+          }
+        });
+      }
+    }
+  }
+  return current;
+}
+
+function findBarChartCurrentIndex(currentPoints, calPoints, opts, config) {
+  var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
+  var current={ index:-1, group:[] };
+  var spacing = opts.chartData.eachSpacing / 2;
+  let yAxisPoints = opts.chartData.yAxisPoints;
+  if (calPoints && calPoints.length > 0) {
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      yAxisPoints.forEach(function(item, index) {
+        if (currentPoints.y + offset + spacing > item) {
+          current.index = index;
+        }
+      });
+    }
+  }
+  return current;
+}
+
+function findLegendIndex(currentPoints, legendData, opts) {
+  let currentIndex = -1;
+  let gap = 0;
+  if (isInExactLegendArea(currentPoints, legendData.area)) {
+    let points = legendData.points;
+    let index = -1;
+    for (let i = 0, len = points.length; i < len; i++) {
+      let item = points[i];
+      for (let j = 0; j < item.length; j++) {
+        index += 1;
+        let area = item[j]['area'];
+        if (area && currentPoints.x > area[0] - gap && currentPoints.x < area[2] + gap && currentPoints.y > area[1] - gap && currentPoints.y < area[3] + gap) {
+          currentIndex = index;
+          break;
+        }
+      }
+    }
+    return currentIndex;
+  }
+  return currentIndex;
+}
+
+function isInExactLegendArea(currentPoints, area) {
+  return currentPoints.x > area.start.x && currentPoints.x < area.end.x && currentPoints.y > area.start.y && currentPoints.y < area.end.y;
+}
+
+function isInExactChartArea(currentPoints, opts, config) {
+  return currentPoints.x <= opts.width - opts.area[1] + 10 && currentPoints.x >= opts.area[3] - 10 && currentPoints.y >= opts.area[0] && currentPoints.y <= opts.height - opts.area[2];
+}
+
+function findRadarChartCurrentIndex(currentPoints, radarData, count) {
+  var eachAngleArea = 2 * Math.PI / count;
+  var currentIndex = -1;
+  if (isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)) {
+    var fixAngle = function fixAngle(angle) {
+      if (angle < 0) {
+        angle += 2 * Math.PI;
+      }
+      if (angle > 2 * Math.PI) {
+        angle -= 2 * Math.PI;
+      }
+      return angle;
+    };
+    var angle = Math.atan2(radarData.center.y - currentPoints.y, currentPoints.x - radarData.center.x);
+    angle = -1 * angle;
+    if (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+    var angleList = radarData.angleList.map(function(item) {
+      item = fixAngle(-1 * item);
+      return item;
+    });
+    angleList.forEach(function(item, index) {
+      var rangeStart = fixAngle(item - eachAngleArea / 2);
+      var rangeEnd = fixAngle(item + eachAngleArea / 2);
+      if (rangeEnd < rangeStart) {
+        rangeEnd += 2 * Math.PI;
+      }
+      if (angle >= rangeStart && angle <= rangeEnd || angle + 2 * Math.PI >= rangeStart && angle + 2 * Math.PI <= rangeEnd) {
+        currentIndex = index;
+      }
+    });
+  }
+  return currentIndex;
+}
+
+function findFunnelChartCurrentIndex(currentPoints, funnelData) {
+  var currentIndex = -1;
+  for (var i = 0, len = funnelData.series.length; i < len; i++) {
+    var item = funnelData.series[i];
+    if (currentPoints.x > item.funnelArea[0] && currentPoints.x < item.funnelArea[2] && currentPoints.y > item.funnelArea[1] && currentPoints.y < item.funnelArea[3]) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findWordChartCurrentIndex(currentPoints, wordData) {
+  var currentIndex = -1;
+  for (var i = 0, len = wordData.length; i < len; i++) {
+    var item = wordData[i];
+    if (currentPoints.x > item.area[0] && currentPoints.x < item.area[2] && currentPoints.y > item.area[1] && currentPoints.y < item.area[3]) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findMapChartCurrentIndex(currentPoints, opts) {
+  var currentIndex = -1;
+  var cData = opts.chartData.mapData;
+  var data = opts.series;
+  var tmp = pointToCoordinate(currentPoints.y, currentPoints.x, cData.bounds, cData.scale, cData.xoffset, cData.yoffset);
+  var poi = [tmp.x, tmp.y];
+  for (var i = 0, len = data.length; i < len; i++) {
+    var item = data[i].geometry.coordinates;
+    if (isPoiWithinPoly(poi, item, opts.chartData.mapData.mercator)) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findRoseChartCurrentIndex(currentPoints, pieData, opts) {
+  var currentIndex = -1;
+  var series = getRoseDataPoints(opts._series_, opts.extra.rose.type, pieData.radius, pieData.radius);
+  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {
+    var angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);
+    angle = -angle;
+    if(opts.extra.rose && opts.extra.rose.offsetAngle){
+      angle = angle - opts.extra.rose.offsetAngle * Math.PI / 180;
+    }
+    for (var i = 0, len = series.length; i < len; i++) {
+      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._rose_proportion_ * 2 * Math.PI)) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}
+
+function findPieChartCurrentIndex(currentPoints, pieData, opts) {
+  var currentIndex = -1;
+  var series = getPieDataPoints(pieData.series);
+  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {
+    var angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);
+    angle = -angle;
+    if(opts.extra.pie && opts.extra.pie.offsetAngle){
+      angle = angle - opts.extra.pie.offsetAngle * Math.PI / 180;
+    }
+    if(opts.extra.ring && opts.extra.ring.offsetAngle){
+      angle = angle - opts.extra.ring.offsetAngle * Math.PI / 180;
+    }
+    for (var i = 0, len = series.length; i < len; i++) {
+      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._proportion_ * 2 * Math.PI)) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}
+
+function isInExactPieChartArea(currentPoints, center, radius) {
+  return Math.pow(currentPoints.x - center.x, 2) + Math.pow(currentPoints.y - center.y, 2) <= Math.pow(radius, 2);
+}
+
+
+function splitPoints(points,eachSeries) {
+  var newPoints = [];
+  var items = [];
+  points.forEach(function(item, index) {
+    if(eachSeries.connectNulls){
+      if (item !== null) {
+        items.push(item);
+      }
+    }else{
+      if (item !== null) {
+        items.push(item);
+      } else {
+        if (items.length) {
+          newPoints.push(items);
+        }
+        items = [];
+      }
+    }
+    
+  });
+  if (items.length) {
+    newPoints.push(items);
+  }
+  return newPoints;
+}
+
+
+function calLegendData(series, opts, config, chartData, context) {
+  let legendData = {
+    area: {
+      start: {
+        x: 0,
+        y: 0
+      },
+      end: {
+        x: 0,
+        y: 0
+      },
+      width: 0,
+      height: 0,
+      wholeWidth: 0,
+      wholeHeight: 0
+    },
+    points: [],
+    widthArr: [],
+    heightArr: []
+  };
+  if (opts.legend.show === false) {
+    chartData.legendData = legendData;
+    return legendData;
+  }
+  let padding = opts.legend.padding * opts.pix;
+  let margin = opts.legend.margin * opts.pix;
+  let fontSize = opts.legend.fontSize ? opts.legend.fontSize * opts.pix : config.fontSize;
+  let shapeWidth = 15 * opts.pix;
+  let shapeRight = 5 * opts.pix;
+  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);
+  if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+    let legendList = [];
+    let widthCount = 0;
+    let widthCountArr = [];
+    let currentRow = [];
+    for (let i = 0; i < series.length; i++) {
+      let item = series[i];
+      const legendText = item.legendText ? item.legendText : item.name;
+      let itemWidth = shapeWidth + shapeRight + measureText(legendText || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;
+      if (widthCount + itemWidth > opts.width - opts.area[1] - opts.area[3]) {
+        legendList.push(currentRow);
+        widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+        widthCount = itemWidth;
+        currentRow = [item];
+      } else {
+        widthCount += itemWidth;
+        currentRow.push(item);
+      }
+    }
+    if (currentRow.length) {
+      legendList.push(currentRow);
+      widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+      legendData.widthArr = widthCountArr;
+      let legendWidth = Math.max.apply(null, widthCountArr);
+      switch (opts.legend.float) {
+        case 'left':
+          legendData.area.start.x = opts.area[3];
+          legendData.area.end.x = opts.area[3] + legendWidth + 2 * padding;
+          break;
+        case 'right':
+          legendData.area.start.x = opts.width - opts.area[1] - legendWidth - 2 * padding;
+          legendData.area.end.x = opts.width - opts.area[1];
+          break;
+        default:
+          legendData.area.start.x = (opts.width - legendWidth) / 2 - padding;
+          legendData.area.end.x = (opts.width + legendWidth) / 2 + padding;
+      }
+      legendData.area.width = legendWidth + 2 * padding;
+      legendData.area.wholeWidth = legendWidth + 2 * padding;
+      legendData.area.height = legendList.length * lineHeight + 2 * padding;
+      legendData.area.wholeHeight = legendList.length * lineHeight + 2 * padding + 2 * margin;
+      legendData.points = legendList;
+    }
+  } else {
+    let len = series.length;
+    let maxHeight = opts.height - opts.area[0] - opts.area[2] - 2 * margin - 2 * padding;
+    let maxLength = Math.min(Math.floor(maxHeight / lineHeight), len);
+    legendData.area.height = maxLength * lineHeight + padding * 2;
+    legendData.area.wholeHeight = maxLength * lineHeight + padding * 2;
+    switch (opts.legend.float) {
+      case 'top':
+        legendData.area.start.y = opts.area[0] + margin;
+        legendData.area.end.y = opts.area[0] + margin + legendData.area.height;
+        break;
+      case 'bottom':
+        legendData.area.start.y = opts.height - opts.area[2] - margin - legendData.area.height;
+        legendData.area.end.y = opts.height - opts.area[2] - margin;
+        break;
+      default:
+        legendData.area.start.y = (opts.height - legendData.area.height) / 2;
+        legendData.area.end.y = (opts.height + legendData.area.height) / 2;
+    }
+    let lineNum = len % maxLength === 0 ? len / maxLength : Math.floor((len / maxLength) + 1);
+    let currentRow = [];
+    for (let i = 0; i < lineNum; i++) {
+      let temp = series.slice(i * maxLength, i * maxLength + maxLength);
+      currentRow.push(temp);
+    }
+    legendData.points = currentRow;
+    if (currentRow.length) {
+      for (let i = 0; i < currentRow.length; i++) {
+        let item = currentRow[i];
+        let maxWidth = 0;
+        for (let j = 0; j < item.length; j++) {
+          let itemWidth = shapeWidth + shapeRight + measureText(item[j].name || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;
+          if (itemWidth > maxWidth) {
+            maxWidth = itemWidth;
+          }
+        }
+        legendData.widthArr.push(maxWidth);
+        legendData.heightArr.push(item.length * lineHeight + padding * 2);
+      }
+      let legendWidth = 0
+      for (let i = 0; i < legendData.widthArr.length; i++) {
+        legendWidth += legendData.widthArr[i];
+      }
+      legendData.area.width = legendWidth - opts.legend.itemGap * opts.pix + 2 * padding;
+      legendData.area.wholeWidth = legendData.area.width + padding;
+    }
+  }
+  switch (opts.legend.position) {
+    case 'top':
+      legendData.area.start.y = opts.area[0] + margin;
+      legendData.area.end.y = opts.area[0] + margin + legendData.area.height;
+      break;
+    case 'bottom':
+      legendData.area.start.y = opts.height - opts.area[2] - legendData.area.height - margin;
+      legendData.area.end.y = opts.height - opts.area[2] - margin;
+      break;
+    case 'left':
+      legendData.area.start.x = opts.area[3];
+      legendData.area.end.x = opts.area[3] + legendData.area.width;
+      break;
+    case 'right':
+      legendData.area.start.x = opts.width - opts.area[1] - legendData.area.width;
+      legendData.area.end.x = opts.width - opts.area[1];
+      break;
+  }
+  chartData.legendData = legendData;
+  return legendData;
+}
+
+function calCategoriesData(categories, opts, config, eachSpacing, context) {
+  var result = {
+    angle: 0,
+    xAxisHeight: opts.xAxis.lineHeight * opts.pix + opts.xAxis.marginTop * opts.pix
+  };
+  var fontSize = opts.xAxis.fontSize * opts.pix;
+  var categoriesTextLenth = categories.map(function(item,index) {
+    var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;
+    return measureText(String(xitem), fontSize, context);
+  });
+  var maxTextLength = Math.max.apply(this, categoriesTextLenth);
+  if (opts.xAxis.rotateLabel == true) {
+    result.angle = opts.xAxis.rotateAngle * Math.PI / 180;
+    let tempHeight = opts.xAxis.marginTop * opts.pix * 2 +  Math.abs(maxTextLength * Math.sin(result.angle))
+    tempHeight = tempHeight < fontSize + opts.xAxis.marginTop * opts.pix * 2 ? tempHeight + opts.xAxis.marginTop * opts.pix * 2 : tempHeight;
+    result.xAxisHeight = tempHeight;
+  }
+  if (opts.enableScroll && opts.xAxis.scrollShow) {
+    result.xAxisHeight += 6 * opts.pix;
+  }
+  if (opts.xAxis.disabled){
+    result.xAxisHeight = 0;
+  }
+  return result;
+}
+
+function getXAxisTextList(series, opts, config, stack) {
+  var index = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1;
+  var data;
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+  var sorted = [];
+  // remove null from data
+  data = data.filter(function(item) {
+    //return item !== null;
+    if (typeof item === 'object' && item !== null) {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.map(function(item) {
+    if (typeof item === 'object') {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        if (opts.type == 'candle') {
+          item.map(function(subitem) {
+            sorted.push(subitem);
+          })
+        } else {
+          sorted.push(item[0]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  })
+
+  var minData = 0;
+  var maxData = 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(this, sorted);
+    maxData = Math.max.apply(this, sorted);
+  }
+  //为了兼容v1.9.0之前的项目
+  if (index > -1) {
+    if (typeof opts.xAxis.data[index].min === 'number') {
+      minData = Math.min(opts.xAxis.data[index].min, minData);
+    }
+    if (typeof opts.xAxis.data[index].max === 'number') {
+      maxData = Math.max(opts.xAxis.data[index].max, maxData);
+    }
+  } else {
+    if (typeof opts.xAxis.min === 'number') {
+      minData = Math.min(opts.xAxis.min, minData);
+    }
+    if (typeof opts.xAxis.max === 'number') {
+      maxData = Math.max(opts.xAxis.max, maxData);
+    }
+  }
+  if (minData === maxData) {
+    var rangeSpan = maxData || 10;
+    maxData += rangeSpan;
+  }
+  //var dataRange = getDataRange(minData, maxData);
+  var minRange = minData;
+  var maxRange = maxData;
+  var range = [];
+  var eachRange = (maxRange - minRange) / opts.xAxis.splitNumber;
+  for (var i = 0; i <= opts.xAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range;
+}
+
+function calXAxisData(series, opts, config, context) {
+  //堆叠图重算Y轴
+  var columnstyle = assign({}, {
+    type: ""
+  }, opts.extra.bar);
+  var result = {
+    angle: 0,
+    xAxisHeight: opts.xAxis.lineHeight * opts.pix + opts.xAxis.marginTop * opts.pix
+  };
+  result.ranges = getXAxisTextList(series, opts, config, columnstyle.type);
+  result.rangesFormat = result.ranges.map(function(item) {
+    //item = opts.xAxis.formatter ? opts.xAxis.formatter(item) : util.toFixed(item, 2);
+    item = util.toFixed(item, 2);
+    return item;
+  });
+  var xAxisScaleValues = result.ranges.map(function(item) {
+    // 如果刻度值是浮点数,则保留两位小数
+    item = util.toFixed(item, 2);
+    // 若有自定义格式则调用自定义的格式化函数
+    //item = opts.xAxis.formatter ? opts.xAxis.formatter(Number(item)) : item;
+    return item;
+  });
+  result = Object.assign(result, getXAxisPoints(xAxisScaleValues, opts, config));
+  // 计算X轴刻度的属性譬如每个刻度的间隔,刻度的起始点\结束点以及总长
+  var eachSpacing = result.eachSpacing;
+  var textLength = xAxisScaleValues.map(function(item) {
+    return measureText(item, opts.xAxis.fontSize * opts.pix, context);
+  });
+  if (opts.xAxis.disabled === true) {
+    result.xAxisHeight = 0;
+  }
+  return result;
+}
+
+function getRadarDataPoints(angleList, center, radius, series, opts) {
+  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
+  var radarOption = opts.extra.radar || {};
+  radarOption.max = radarOption.max || 0;
+  var maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));
+  var data = [];
+  for (let i = 0; i < series.length; i++) {
+    let each = series[i];
+    let listItem = {};
+    listItem.color = each.color;
+    listItem.legendShape = each.legendShape;
+    listItem.pointShape = each.pointShape;
+    listItem.data = [];
+    each.data.forEach(function(item, index) {
+      let tmp = {};
+      tmp.angle = angleList[index];
+      tmp.proportion = item / maxData;
+      tmp.value = item;
+      tmp.position = convertCoordinateOrigin(radius * tmp.proportion * process * Math.cos(tmp.angle), radius * tmp.proportion * process * Math.sin(tmp.angle), center);
+      listItem.data.push(tmp);
+    });
+    data.push(listItem);
+  }
+  return data;
+}
+
+function getPieDataPoints(series, radius) {
+  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
+  var count = 0;
+  var _start_ = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+    }
+    item._radius_ = radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._proportion_ * Math.PI;
+  }
+  return series;
+}
+
+function getFunnelDataPoints(series, radius, option, eachSpacing) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  for (let i = 0; i < series.length; i++) {
+    if(option.type == 'funnel'){
+      series[i].radius = series[i].data / series[0].data * radius * process;
+    }else{
+      series[i].radius =  (eachSpacing * (series.length - i)) / (eachSpacing * series.length) * radius * process;
+    }
+    series[i]._proportion_ = series[i].data / series[0].data;
+  }
+  // if(option.type !== 'pyramid'){
+  //   series.reverse();
+  // }
+  return series;
+}
+
+function getRoseDataPoints(series, type, minRadius, radius) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var count = 0;
+  var _start_ = 0;
+  var dataArr = [];
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+    dataArr.push(item.data);
+  }
+  var minData = Math.min.apply(null, dataArr);
+  var maxData = Math.max.apply(null, dataArr);
+  var radiusLength = radius - minRadius;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+      item._rose_proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+      if(type == 'area'){
+        item._rose_proportion_ = 1 / series.length * process;
+      }else{
+        item._rose_proportion_ = item.data / count * process;
+      }
+    }
+    item._radius_ = minRadius + radiusLength * ((item.data - minData) / (maxData - minData)) || radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._rose_proportion_ * Math.PI;
+  }
+  return series;
+}
+
+function getArcbarDataPoints(series, arcbarOption) {
+  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
+  if (process == 1) {
+    process = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if(arcbarOption.direction == 'ccw'){
+        if (arcbarOption.startAngle < arcbarOption.endAngle) {
+          totalAngle = 2 + arcbarOption.startAngle - arcbarOption.endAngle;
+        } else {
+          totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+        }
+      }else{
+        if (arcbarOption.endAngle < arcbarOption.startAngle) {
+          totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;
+        } else {
+          totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+        }
+      }
+    }
+    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;
+    if(arcbarOption.direction == 'ccw'){
+      item._proportion_ = arcbarOption.startAngle - totalAngle * item.data * process ;
+    }
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getGaugeArcbarDataPoints(series, arcbarOption) {
+  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
+  if (process == 1) {
+    process = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if (arcbarOption.endAngle < arcbarOption.startAngle) {
+        totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;
+      } else {
+        totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+      }
+    }
+    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getGaugeAxisPoints(categories, startAngle, endAngle) {
+  let totalAngle;
+  if (endAngle < startAngle) {
+    totalAngle = 2 + endAngle - startAngle;
+  } else {
+    totalAngle = startAngle - endAngle;
+  }
+  let tempStartAngle = startAngle;
+  for (let i = 0; i < categories.length; i++) {
+    categories[i].value = categories[i].value === null ? 0 : categories[i].value;
+    categories[i]._startAngle_ = tempStartAngle;
+    categories[i]._endAngle_ = totalAngle * categories[i].value + startAngle;
+    if (categories[i]._endAngle_ >= 2) {
+      categories[i]._endAngle_ = categories[i]._endAngle_ % 2;
+    }
+    tempStartAngle = categories[i]._endAngle_;
+  }
+  return categories;
+}
+
+function getGaugeDataPoints(series, categories, gaugeOption) {
+  let process = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (gaugeOption.pointer.color == 'auto') {
+      for (let i = 0; i < categories.length; i++) {
+        if (item.data <= categories[i].value) {
+          item.color = categories[i].color;
+          break;
+        }
+      }
+    } else {
+      item.color = gaugeOption.pointer.color;
+    }
+    let totalAngle;
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    item._endAngle_ = totalAngle * item.data + gaugeOption.startAngle;
+    item._oldAngle_ = gaugeOption.oldAngle;
+    if (gaugeOption.oldAngle < gaugeOption.endAngle) {
+      item._oldAngle_ += 2;
+    }
+    if (item.data >= gaugeOption.oldData) {
+      item._proportion_ = (item._endAngle_ - item._oldAngle_) * process + gaugeOption.oldAngle;
+    } else {
+      item._proportion_ = item._oldAngle_ - (item._oldAngle_ - item._endAngle_) * process;
+    }
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getPieTextMaxLength(series, config, context, opts) {
+  series = getPieDataPoints(series);
+  let maxLength = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    let text = item.formatter ? item.formatter(+item._proportion_.toFixed(2)) : util.toFixed(item._proportion_ * 100) + '%';
+    maxLength = Math.max(maxLength, measureText(text, item.textSize * opts.pix || config.fontSize, context));
+  }
+  return maxLength;
+}
+
+function fixColumeData(points, eachSpacing, columnLen, index, config, opts) {
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    var seriesGap = 0;
+    var categoryGap = 0;
+    if (opts.type == 'mix') {
+      seriesGap = opts.extra.mix.column.seriesGap * opts.pix || 0;
+      categoryGap = opts.extra.mix.column.categoryGap * opts.pix || 0;
+    } else {
+      seriesGap = opts.extra.column.seriesGap * opts.pix || 0;
+      categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+    }
+    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)
+    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)
+    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);
+    if (opts.extra.mix && opts.extra.mix.column.width && +opts.extra.mix.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.mix.column.width * opts.pix);
+    }
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    item.x += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);
+    return item;
+  });
+}
+
+function fixBarData(points, eachSpacing, columnLen, index, config, opts) {
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    var seriesGap = 0;
+    var categoryGap = 0;
+    seriesGap = opts.extra.bar.seriesGap * opts.pix || 0;
+    categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;
+    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)
+    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)
+    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);
+    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    item.y += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);
+    return item;
+  });
+}
+
+function fixColumeMeterData(points, eachSpacing, columnLen, index, config, opts, border) {
+  var categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    item.width = eachSpacing - 2 * categoryGap;
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (index > 0) {
+      item.width -= border;
+    }
+    return item;
+  });
+}
+
+function fixColumeStackData(points, eachSpacing, columnLen, index, config, opts, series) {
+  var categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+  return points.map(function(item, indexn) {
+    if (item === null) {
+      return null;
+    }
+    item.width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    return item;
+  });
+}
+
+function fixBarStackData(points, eachSpacing, columnLen, index, config, opts, series) {
+  var categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;
+  return points.map(function(item, indexn) {
+    if (item === null) {
+      return null;
+    }
+    item.width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    return item;
+  });
+}
+
+function getXAxisPoints(categories, opts, config) {
+  var spacingValid = opts.width - opts.area[1] - opts.area[3];
+  var dataCount = opts.enableScroll ? Math.min(opts.xAxis.itemCount, categories.length) : categories.length;
+  if ((opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' || opts.type == 'bar') && dataCount > 1 && opts.xAxis.boundaryGap == 'justify') {
+    dataCount -= 1;
+  }
+  var widthRatio = 0;
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    widthRatio = opts.extra.mount.widthRatio - 1;
+    dataCount += widthRatio;
+  }
+  var eachSpacing = spacingValid / dataCount;
+  var xAxisPoints = [];
+  var startX = opts.area[3];
+  var endX = opts.width - opts.area[1];
+  categories.forEach(function(item, index) {
+    xAxisPoints.push(startX + widthRatio / 2 * eachSpacing + index * eachSpacing);
+  });
+  if (opts.xAxis.boundaryGap !== 'justify') {
+    if (opts.enableScroll === true) {
+      xAxisPoints.push(startX + widthRatio * eachSpacing + categories.length * eachSpacing);
+    } else {
+      xAxisPoints.push(endX);
+    }
+  }
+  return {
+    xAxisPoints: xAxisPoints,
+    startX: startX,
+    endX: endX,
+    eachSpacing: eachSpacing
+  };
+}
+
+function getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {
+  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var cPoints = [];
+      item.forEach(function(items, indexs) {
+        var point = {};
+        point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+        var value = items.value || items;
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height - Math.round(height) - opts.area[2];
+        cPoints.push(point);
+      });
+      points.push(cPoints);
+    }
+  });
+  return points;
+}
+
+function getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {
+  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;
+  var boundaryGap = 'center';
+  if (opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' ) {
+    boundaryGap = opts.xAxis.boundaryGap;
+  }
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  var validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      var value = item;
+      if (typeof item === 'object' && item !== null) {
+        if (item.constructor.toString().indexOf('Array') > -1) {
+          let xranges, xminRange, xmaxRange;
+          xranges = [].concat(opts.chartData.xAxisData.ranges);
+          xminRange = xranges.shift();
+          xmaxRange = xranges.pop();
+          value = item[1];
+          point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          if(opts.type == 'bubble'){
+            point.r = item[2];
+            point.t = item[3];
+          }
+        } else {
+          value = item.value;
+        }
+      }
+      if (boundaryGap == 'center') {
+        point.x += eachSpacing / 2;
+      }
+      var height = validHeight * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getLineDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, lineOption, process){
+  var process = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 1;
+  var boundaryGap = opts.xAxis.boundaryGap;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  var validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      if(lineOption.animation == 'vertical'){
+        point.x = xAxisPoints[index];
+        var value = item;
+        if (typeof item === 'object' && item !== null) {
+          if (item.constructor.toString().indexOf('Array') > -1) {
+            let xranges, xminRange, xmaxRange;
+            xranges = [].concat(opts.chartData.xAxisData.ranges);
+            xminRange = xranges.shift();
+            xmaxRange = xranges.pop();
+            value = item[1];
+            point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          } else {
+            value = item.value;
+          }
+        }
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }else{
+        point.x = xAxisPoints[0] + eachSpacing * index * process;
+        var value = item;
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }
+    }
+  });
+  return points;
+}
+
+function getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, zeroPoints, process){
+  var process = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 1;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  var validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      var value = item;
+      if (typeof item === 'object' && item !== null) {
+        if (item.constructor.toString().indexOf('Array') > -1) {
+          let xranges, xminRange, xmaxRange;
+          xranges = [].concat(opts.chartData.xAxisData.ranges);
+          xminRange = xranges.shift();
+          xmaxRange = xranges.pop();
+          value = item[1];
+          point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+        } else {
+          value = item.value;
+        }
+      }
+      point.x += eachSpacing / 2;
+      var height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints) {
+  var process = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 1;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  var validWidth = opts.width - opts.area[1] - opts.area[3];
+  var mountWidth = eachSpacing * mountOption.widthRatio;
+  series.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      point.x += eachSpacing / 2;
+      var value = item.data;
+      var height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      point.value = value;
+      point.width = mountWidth;
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config) {
+  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  var validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.y = yAxisPoints[index];
+      var value = item;
+      if (typeof item === 'object' && item !== null) {
+        value = item.value;
+      }
+      var height = validWidth * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      point.height = height;
+      point.value = value;
+      point.x = height + opts.area[3];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {
+  var process = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : 1;
+  var points = [];
+  var validHeight = opts.height - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+
+      if (seriesIndex > 0) {
+        var value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index];
+        }
+        var value0 = value - item;
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        var height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        var value = item;
+        if (typeof item === 'object' && item !== null) {
+          value = item.value;
+        }
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        var height0 = 0;
+      }
+      var heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.y = opts.height - Math.round(height) - opts.area[2];
+      point.y0 = opts.height - Math.round(heightc) - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {
+  var process = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : 1;
+  var points = [];
+  var validHeight = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      var point = {};
+      point.color = item.color;
+      point.y = yAxisPoints[index];
+      if (seriesIndex > 0) {
+        var value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index];
+        }
+        var value0 = value - item;
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        var height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        var value = item;
+        if (typeof item === 'object' && item !== null) {
+          value = item.value;
+        }
+        var height = validHeight * (value - minRange) / (maxRange - minRange);
+        var height0 = 0;
+      }
+      var heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.height = height - heightc;
+      point.x = opts.area[3] + height;
+      point.x0 = opts.area[3] + heightc;
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getYAxisTextList(series, opts, config, stack, yData) {
+  var index = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : -1;
+  var data;
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+  var sorted = [];
+  // remove null from data
+  data = data.filter(function(item) {
+    //return item !== null;
+    if (typeof item === 'object' && item !== null) {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.map(function(item) {
+    if (typeof item === 'object') {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        if (opts.type == 'candle') {
+          item.map(function(subitem) {
+            sorted.push(subitem);
+          })
+        } else {
+          sorted.push(item[1]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  })
+  var minData = yData.min || 0;
+  var maxData = yData.max || 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(this, sorted);
+    maxData = Math.max.apply(this, sorted);
+  }
+  if (minData === maxData) {
+    if(maxData == 0){
+      maxData = 10;
+    }else{
+      minData = 0;
+    }
+  }
+  var dataRange = getDataRange(minData, maxData);
+  var minRange = (yData.min === undefined || yData.min === null) ? dataRange.minRange : yData.min;
+  var maxRange = (yData.max === undefined || yData.max === null) ? dataRange.maxRange : yData.max;
+  var eachRange = (maxRange - minRange) / opts.yAxis.splitNumber;
+  var range = [];
+  for (var i = 0; i <= opts.yAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range.reverse();
+}
+
+function calYAxisData(series, opts, config, context) {
+  //堆叠图重算Y轴
+  var columnstyle = assign({}, {
+    type: ""
+  }, opts.extra.column);
+  //如果是多Y轴,重新计算
+  var YLength = opts.yAxis.data.length;
+  var newSeries = new Array(YLength);
+  if (YLength > 0) {
+    for (let i = 0; i < YLength; i++) {
+      newSeries[i] = [];
+      for (let j = 0; j < series.length; j++) {
+        if (series[j].index == i) {
+          newSeries[i].push(series[j]);
+        }
+      }
+    }
+    var rangesArr = new Array(YLength);
+    var rangesFormatArr = new Array(YLength);
+    var yAxisWidthArr = new Array(YLength);
+
+    for (let i = 0; i < YLength; i++) {
+      let yData = opts.yAxis.data[i];
+      //如果总开关不显示,强制每个Y轴为不显示
+      if (opts.yAxis.disabled == true) {
+        yData.disabled = true;
+      }
+      if(yData.type === 'categories'){
+        if(!yData.formatter){
+          yData.formatter = (val,index,opts) => {return val + (yData.unit || '')};
+        }
+        yData.categories = yData.categories || opts.categories;
+        rangesArr[i] = yData.categories;
+      }else{
+        if(!yData.formatter){
+          yData.formatter = (val,index,opts) => {return util.toFixed(val, yData.tofix || 0) + (yData.unit || '')};
+        }
+        rangesArr[i] = getYAxisTextList(newSeries[i], opts, config, columnstyle.type, yData, i);
+      }
+      let yAxisFontSizes = yData.fontSize * opts.pix || config.fontSize;
+      yAxisWidthArr[i] = {
+        position: yData.position ? yData.position : 'left',
+        width: 0
+      };
+      rangesFormatArr[i] = rangesArr[i].map(function(items,index) {
+        items = yData.formatter(items,index,opts);
+        yAxisWidthArr[i].width = Math.max(yAxisWidthArr[i].width, measureText(items, yAxisFontSizes, context) + 5);
+        return items;
+      });
+      let calibration = yData.calibration ? 4 * opts.pix : 0;
+      yAxisWidthArr[i].width += calibration + 3 * opts.pix;
+      if (yData.disabled === true) {
+        yAxisWidthArr[i].width = 0;
+      }
+    }
+  } else {
+    var rangesArr = new Array(1);
+    var rangesFormatArr = new Array(1);
+    var yAxisWidthArr = new Array(1);
+    if(opts.type === 'bar'){
+      rangesArr[0] = opts.categories;
+      if(!opts.yAxis.formatter){
+        opts.yAxis.formatter = (val,index,opts) => {return val + (opts.yAxis.unit || '')}
+      }
+    }else{
+      if(!opts.yAxis.formatter){
+        opts.yAxis.formatter = (val,index,opts) => {return val.toFixed(opts.yAxis.tofix ) + (opts.yAxis.unit || '')}
+      }
+      rangesArr[0] = getYAxisTextList(series, opts, config, columnstyle.type, {});
+    }
+    yAxisWidthArr[0] = {
+      position: 'left',
+      width: 0
+    };
+    var yAxisFontSize = opts.yAxis.fontSize * opts.pix || config.fontSize;
+    rangesFormatArr[0] = rangesArr[0].map(function(item,index) {
+      item = opts.yAxis.formatter(item,index,opts);
+      yAxisWidthArr[0].width = Math.max(yAxisWidthArr[0].width, measureText(item, yAxisFontSize, context) + 5);
+      return item;
+    });
+    yAxisWidthArr[0].width += 3 * opts.pix;
+    if (opts.yAxis.disabled === true) {
+      yAxisWidthArr[0] = {
+        position: 'left',
+        width: 0
+      };
+      opts.yAxis.data[0] = {
+        disabled: true
+      };
+    } else {
+      opts.yAxis.data[0] = {
+        disabled: false,
+        position: 'left',
+        max: opts.yAxis.max,
+        min: opts.yAxis.min,
+        formatter: opts.yAxis.formatter
+      };
+      if(opts.type === 'bar'){
+        opts.yAxis.data[0].categories = opts.categories;
+        opts.yAxis.data[0].type = 'categories';
+      }
+    }
+  }
+  return {
+    rangesFormat: rangesFormatArr,
+    ranges: rangesArr,
+    yAxisWidth: yAxisWidthArr
+  };
+}
+
+function calTooltipYAxisData(point, series, opts, config, eachSpacing) {
+  let ranges = [].concat(opts.chartData.yAxisData.ranges);
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  let minAxis = opts.area[0];
+  let items = [];
+  for (let i = 0; i < ranges.length; i++) {
+    let maxVal = Math.max.apply(this, ranges[i]);
+    let minVal = Math.min.apply(this, ranges[i]);
+    let item = maxVal - (maxVal - minVal) * (point - minAxis) / spacingValid;
+    item = opts.yAxis.data && opts.yAxis.data[i].formatter ? opts.yAxis.data[i].formatter(item, i, opts) : item.toFixed(0);
+    items.push(String(item))
+  }
+  return items;
+}
+
+function calMarkLineData(points, opts) {
+  let minRange, maxRange;
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  for (let i = 0; i < points.length; i++) {
+    points[i].yAxisIndex = points[i].yAxisIndex ? points[i].yAxisIndex : 0;
+    let range = [].concat(opts.chartData.yAxisData.ranges[points[i].yAxisIndex]);
+    minRange = range.pop();
+    maxRange = range.shift();
+    let height = spacingValid * (points[i].value - minRange) / (maxRange - minRange);
+    points[i].y = opts.height - Math.round(height) - opts.area[2];
+  }
+  return points;
+}
+
+function contextRotate(context, opts) {
+  if (opts.rotateLock !== true) {
+    context.translate(opts.height, 0);
+    context.rotate(90 * Math.PI / 180);
+  } else if (opts._rotate_ !== true) {
+    context.translate(opts.height, 0);
+    context.rotate(90 * Math.PI / 180);
+    opts._rotate_ = true;
+  }
+}
+
+function drawPointShape(points, color, shape, context, opts) {
+  context.beginPath();
+  if (opts.dataPointShapeType == 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+function drawActivePoint(points, color, shape, context, opts, option, seriesIndex) {
+  if(!opts.tooltip){
+    return
+  }
+  if(opts.tooltip.group.length>0 && opts.tooltip.group.includes(seriesIndex) == false){
+    return
+  }
+  var pointIndex = typeof opts.tooltip.index === 'number' ? opts.tooltip.index : opts.tooltip.index[opts.tooltip.group.indexOf(seriesIndex)];
+  context.beginPath();
+  if (option.activeType == 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index ) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+function drawRingTitle(opts, config, context, center) {
+  var titlefontSize = opts.title.fontSize || config.titleFontSize;
+  var subtitlefontSize = opts.subtitle.fontSize || config.subtitleFontSize;
+  var title = opts.title.name || '';
+  var subtitle = opts.subtitle.name || '';
+  var titleFontColor = opts.title.color || opts.fontColor;
+  var subtitleFontColor = opts.subtitle.color || opts.fontColor;
+  var titleHeight = title ? titlefontSize : 0;
+  var subtitleHeight = subtitle ? subtitlefontSize : 0;
+  var margin = 5;
+  if (subtitle) {
+    var textWidth = measureText(subtitle, subtitlefontSize * opts.pix, context);
+    var startX = center.x - textWidth / 2 + (opts.subtitle.offsetX|| 0) * opts.pix ;
+    var startY = center.y + subtitlefontSize * opts.pix / 2 + (opts.subtitle.offsetY || 0) * opts.pix;
+    if (title) {
+      startY += (titleHeight * opts.pix + margin) / 2;
+    }
+    context.beginPath();
+    context.setFontSize(subtitlefontSize * opts.pix);
+    context.setFillStyle(subtitleFontColor);
+    context.fillText(subtitle, startX, startY);
+    context.closePath();
+    context.stroke();
+  }
+  if (title) {
+    var _textWidth = measureText(title, titlefontSize * opts.pix, context);
+    var _startX = center.x - _textWidth / 2 + (opts.title.offsetX || 0);
+    var _startY = center.y + titlefontSize * opts.pix / 2 + (opts.title.offsetY || 0) * opts.pix;
+    if (subtitle) {
+      _startY -= (subtitleHeight * opts.pix + margin) / 2;
+    }
+    context.beginPath();
+    context.setFontSize(titlefontSize * opts.pix);
+    context.setFillStyle(titleFontColor);
+    context.fillText(title, _startX, _startY);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+function drawPointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  var data = series.data;
+  var textOffset = series.textOffset ? series.textOffset : 0;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      var fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      var value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (data[index].constructor.toString().indexOf('Array')>-1) {
+          value = data[index][1];
+        } else {
+          value = data[index].value
+        }
+      }
+      var formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      context.fillText(String(formatVal), item.x, item.y - 4 + textOffset * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawColumePointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  var data = series.data;
+  var textOffset = series.textOffset ? series.textOffset : 0;
+  var Position = opts.extra.column.labelPosition;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      var fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      var value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (data[index].constructor.toString().indexOf('Array')>-1) {
+          value = data[index][1];
+        } else {
+          value = data[index].value
+        }
+      }
+      var formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      var startY = item.y - 4 * opts.pix + textOffset * opts.pix;
+      if(item.y > series.zeroPoints){
+        startY = item.y + textOffset * opts.pix + fontSize;
+      }
+      if(Position == 'insideTop'){
+        startY = item.y + fontSize + textOffset * opts.pix;
+        if(item.y > series.zeroPoints){
+          startY = item.y - textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+      if(Position == 'center'){
+        startY = item.y + textOffset * opts.pix + (opts.height - opts.area[2] - item.y + fontSize)/2;
+        if(series.zeroPoints < opts.height - opts.area[2]){
+          startY = item.y + textOffset * opts.pix + (series.zeroPoints - item.y + fontSize)/2;
+        }
+        if(item.y > series.zeroPoints){
+          startY = item.y - textOffset * opts.pix - (item.y - series.zeroPoints - fontSize)/2;
+        }
+        if(opts.extra.column.type == 'stack'){
+          startY = item.y + textOffset * opts.pix + (item.y0 - item.y + fontSize)/2;
+        }
+      }
+      if(Position == 'bottom'){
+        startY = opts.height - opts.area[2] + textOffset * opts.pix - 4 * opts.pix;
+        if(series.zeroPoints < opts.height - opts.area[2]){
+          startY = series.zeroPoints + textOffset * opts.pix - 4 * opts.pix;
+        }
+        if(item.y > series.zeroPoints){
+          startY = series.zeroPoints - textOffset * opts.pix + fontSize + 2 * opts.pix;
+        }
+        if(opts.extra.column.type == 'stack'){
+          startY = item.y0 + textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawMountPointText(points, series, config, context, opts, zeroPoints) {
+  // 绘制数据文案
+  var data = series.data;
+  var textOffset = series.textOffset ? series.textOffset : 0;
+  var Position = opts.extra.mount.labelPosition;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      var fontSize = series[index].textSize ? series[index].textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series[index].textColor || opts.fontColor);
+      var value = item.value
+      var formatVal = series[index].formatter ? series[index].formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      var startY = item.y - 4 * opts.pix + textOffset * opts.pix;
+      if(item.y > zeroPoints){
+        startY = item.y + textOffset * opts.pix + fontSize;
+      }
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawBarPointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  var data = series.data;
+  var textOffset = series.textOffset ? series.textOffset : 0;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      var fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      var value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        value = data[index].value ;
+      }
+      var formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('left');
+      context.fillText(String(formatVal), item.x + 4 * opts.pix , item.y + fontSize / 2 - 3 );
+      context.closePath();
+      context.stroke();
+    }
+  });
+}
+
+function drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context) {
+  radius -= gaugeOption.width / 2 + gaugeOption.labelOffset * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  let totalAngle;
+  if (gaugeOption.endAngle < gaugeOption.startAngle) {
+    totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+  } else {
+    totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+  }
+  let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+  let totalNumber = gaugeOption.endNumber - gaugeOption.startNumber;
+  let splitNumber = totalNumber / gaugeOption.splitLine.splitNumber;
+  let nowAngle = gaugeOption.startAngle;
+  let nowNumber = gaugeOption.startNumber;
+  for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {
+    var pos = {
+      x: radius * Math.cos(nowAngle * Math.PI),
+      y: radius * Math.sin(nowAngle * Math.PI)
+    };
+    var labelText = gaugeOption.formatter ? gaugeOption.formatter(nowNumber,i,opts) : nowNumber;
+    pos.x += centerPosition.x - measureText(labelText, config.fontSize, context) / 2;
+    pos.y += centerPosition.y;
+    var startX = pos.x;
+    var startY = pos.y;
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(gaugeOption.labelColor || opts.fontColor);
+    context.fillText(labelText, startX, startY + config.fontSize / 2);
+    context.closePath();
+    context.stroke();
+    nowAngle += splitAngle;
+    if (nowAngle >= 2) {
+      nowAngle = nowAngle % 2;
+    }
+    nowNumber += splitNumber;
+  }
+}
+
+function drawRadarLabel(angleList, radius, centerPosition, opts, config, context) {
+  var radarOption = opts.extra.radar || {};
+  angleList.forEach(function(angle, index) {
+    if(radarOption.labelPointShow === true && opts.categories[index] !== ''){
+      var posPoint = {
+        x: radius * Math.cos(angle),
+        y: radius * Math.sin(angle)
+      };
+      var posPointAxis = convertCoordinateOrigin(posPoint.x, posPoint.y, centerPosition);
+      context.setFillStyle(radarOption.labelPointColor);
+      context.beginPath();
+      context.arc(posPointAxis.x, posPointAxis.y, radarOption.labelPointRadius * opts.pix, 0, 2 * Math.PI, false);
+      context.closePath();
+      context.fill();
+    }
+    if(radarOption.labelShow === true){
+      var pos = {
+        x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),
+        y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)
+      };
+      var posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);
+      var startX = posRelativeCanvas.x;
+      var startY = posRelativeCanvas.y;
+      if (util.approximatelyEqual(pos.x, 0)) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context) / 2;
+      } else if (pos.x < 0) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context);
+      }
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(radarOption.labelColor || opts.fontColor);
+      context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2);
+      context.closePath();
+      context.stroke();
+    }
+  });
+
+}
+
+function drawPieText(series, opts, config, context, radius, center) {
+  var lineRadius = config.pieChartLinePadding;
+  var textObjectCollection = [];
+  var lastTextObject = null;
+  var seriesConvert = series.map(function(item,index) {
+    var text = item.formatter ? item.formatter(item,index,series,opts) : util.toFixed(item._proportion_.toFixed(4) * 100) + '%';
+    text = item.labelText ? item.labelText : text;
+    var arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._proportion_ / 2);
+    if (item._rose_proportion_) {
+      arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._rose_proportion_ / 2);
+    }
+    var color = item.color;
+    var radius = item._radius_;
+    return {
+      arc: arc,
+      text: text,
+      color: color,
+      radius: radius,
+      textColor: item.textColor,
+      textSize: item.textSize,
+      labelShow: item.labelShow
+    };
+  });
+  for (let i = 0; i < seriesConvert.length; i++) {
+    let item = seriesConvert[i];
+    // line end
+    let orginX1 = Math.cos(item.arc) * (item.radius + lineRadius);
+    let orginY1 = Math.sin(item.arc) * (item.radius + lineRadius);
+    // line start
+    let orginX2 = Math.cos(item.arc) * item.radius;
+    let orginY2 = Math.sin(item.arc) * item.radius;
+    // text start
+    let orginX3 = orginX1 >= 0 ? orginX1 + config.pieChartTextPadding : orginX1 - config.pieChartTextPadding;
+    let orginY3 = orginY1;
+    let textWidth = measureText(item.text, item.textSize * opts.pix || config.fontSize, context);
+    let startY = orginY3;
+    if (lastTextObject && util.isSameXCoordinateArea(lastTextObject.start, {
+        x: orginX3
+      })) {
+      if (orginX3 > 0) {
+        startY = Math.min(orginY3, lastTextObject.start.y);
+      } else if (orginX1 < 0) {
+        startY = Math.max(orginY3, lastTextObject.start.y);
+      } else {
+        if (orginY3 > 0) {
+          startY = Math.max(orginY3, lastTextObject.start.y);
+        } else {
+          startY = Math.min(orginY3, lastTextObject.start.y);
+        }
+      }
+    }
+    if (orginX3 < 0) {
+      orginX3 -= textWidth;
+    }
+    let textObject = {
+      lineStart: {
+        x: orginX2,
+        y: orginY2
+      },
+      lineEnd: {
+        x: orginX1,
+        y: orginY1
+      },
+      start: {
+        x: orginX3,
+        y: startY
+      },
+      width: textWidth,
+      height: config.fontSize,
+      text: item.text,
+      color: item.color,
+      textColor: item.textColor,
+      textSize: item.textSize
+    };
+    lastTextObject = avoidCollision(textObject, lastTextObject);
+    textObjectCollection.push(lastTextObject);
+  }
+  for (let i = 0; i < textObjectCollection.length; i++) {
+    if(seriesConvert[i].labelShow === false){
+      continue;
+    }
+    let item = textObjectCollection[i];
+    let lineStartPoistion = convertCoordinateOrigin(item.lineStart.x, item.lineStart.y, center);
+    let lineEndPoistion = convertCoordinateOrigin(item.lineEnd.x, item.lineEnd.y, center);
+    let textPosition = convertCoordinateOrigin(item.start.x, item.start.y, center);
+    context.setLineWidth(1 * opts.pix);
+    context.setFontSize(item.textSize * opts.pix || config.fontSize);
+    context.beginPath();
+    context.setStrokeStyle(item.color);
+    context.setFillStyle(item.color);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+    let curveStartX = item.start.x < 0 ? textPosition.x + item.width : textPosition.x;
+    let textStartX = item.start.x < 0 ? textPosition.x - 5 : textPosition.x + 5;
+    context.quadraticCurveTo(lineEndPoistion.x, lineEndPoistion.y, curveStartX, textPosition.y);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+    context.stroke();
+    context.closePath();
+    context.beginPath();
+    context.moveTo(textPosition.x + item.width, textPosition.y);
+    context.arc(curveStartX, textPosition.y, 2 * opts.pix, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+    context.beginPath();
+    context.setFontSize(item.textSize * opts.pix || config.fontSize);
+    context.setFillStyle(item.textColor || opts.fontColor);
+    context.fillText(item.text, textStartX, textPosition.y + 3);
+    context.closePath();
+    context.stroke();
+    context.closePath();
+  }
+}
+
+function drawToolTipSplitLine(offsetX, opts, config, context) {
+  var toolTipOption = opts.extra.tooltip || {};
+  toolTipOption.gridType = toolTipOption.gridType == undefined ? 'solid' : toolTipOption.gridType;
+  toolTipOption.dashLength = toolTipOption.dashLength == undefined ? 4 : toolTipOption.dashLength;
+  var startY = opts.area[0];
+  var endY = opts.height - opts.area[2];
+  if (toolTipOption.gridType == 'dash') {
+    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);
+  }
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(offsetX, startY);
+  context.lineTo(offsetX, endY);
+  context.stroke();
+  context.setLineDash([]);
+  if (toolTipOption.xAxisLabel) {
+    let labelText = opts.categories[opts.tooltip.index];
+    context.setFontSize(config.fontSize);
+    let textWidth = measureText(labelText, config.fontSize, context);
+    let textX = offsetX - 0.5 * textWidth;
+    let textY = endY + 2 * opts.pix;
+    context.beginPath();
+    context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+    context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+    context.setLineWidth(1 * opts.pix);
+    context.rect(textX - toolTipOption.boxPadding * opts.pix, textY, textWidth + 2 * toolTipOption.boxPadding * opts.pix, config.fontSize + 2 * toolTipOption.boxPadding * opts.pix);
+    context.closePath();
+    context.stroke();
+    context.fill();
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+    context.fillText(String(labelText), textX, textY + toolTipOption.boxPadding * opts.pix + config.fontSize);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+function drawMarkLine(opts, config, context) {
+  let markLineOption = assign({}, {
+    type: 'solid',
+    dashLength: 4,
+    data: []
+  }, opts.extra.markLine);
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  let points = calMarkLineData(markLineOption.data, opts);
+  for (let i = 0; i < points.length; i++) {
+    let item = assign({}, {
+      lineColor: '#DE4A42',
+      showLabel: false,
+      labelFontSize: 13,
+      labelPadding: 6,
+      labelFontColor: '#666666',
+      labelBgColor: '#DFE8FF',
+      labelBgOpacity: 0.8,
+      labelAlign: 'left',
+      labelOffsetX: 0,
+      labelOffsetY: 0,
+    }, points[i]);
+    if (markLineOption.type == 'dash') {
+      context.setLineDash([markLineOption.dashLength, markLineOption.dashLength]);
+    }
+    context.setStrokeStyle(item.lineColor);
+    context.setLineWidth(1 * opts.pix);
+    context.beginPath();
+    context.moveTo(startX, item.y);
+    context.lineTo(endX, item.y);
+    context.stroke();
+    context.setLineDash([]);
+    if (item.showLabel) {
+      let fontSize = item.labelFontSize * opts.pix;
+      let labelText = item.labelText ? item.labelText : item.value;
+      context.setFontSize(fontSize);
+      let textWidth = measureText(labelText, fontSize, context);
+      let bgWidth = textWidth + item.labelPadding * opts.pix * 2;
+      let bgStartX = item.labelAlign == 'left' ? opts.area[3] - bgWidth : opts.width - opts.area[1];
+      bgStartX += item.labelOffsetX;
+      let bgStartY = item.y - 0.5 * fontSize - item.labelPadding * opts.pix;
+      bgStartY += item.labelOffsetY;
+      let textX = bgStartX + item.labelPadding * opts.pix;
+      let textY = item.y;
+      context.setFillStyle(hexToRgb(item.labelBgColor, item.labelBgOpacity));
+      context.setStrokeStyle(item.labelBgColor);
+      context.setLineWidth(1 * opts.pix);
+      context.beginPath();
+      context.rect(bgStartX, bgStartY, bgWidth, fontSize + 2 * item.labelPadding * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.fill();
+      context.setFontSize(fontSize);
+      context.setTextAlign('left');
+      context.setFillStyle(item.labelFontColor);
+      context.fillText(String(labelText), textX, bgStartY + fontSize + item.labelPadding * opts.pix/2);
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  }
+}
+
+function drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints) {
+  var toolTipOption = assign({}, {
+    gridType: 'solid',
+    dashLength: 4
+  }, opts.extra.tooltip);
+  var startX = opts.area[3];
+  var endX = opts.width - opts.area[1];
+  if (toolTipOption.gridType == 'dash') {
+    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);
+  }
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(startX, opts.tooltip.offset.y);
+  context.lineTo(endX, opts.tooltip.offset.y);
+  context.stroke();
+  context.setLineDash([]);
+  if (toolTipOption.yAxisLabel) {
+    let boxPadding = toolTipOption.boxPadding * opts.pix;
+    let labelText = calTooltipYAxisData(opts.tooltip.offset.y, opts.series, opts, config, eachSpacing);
+    let widthArr = opts.chartData.yAxisData.yAxisWidth;
+    let tStartLeft = opts.area[3];
+    let tStartRight = opts.width - opts.area[1];
+    for (let i = 0; i < labelText.length; i++) {
+      context.setFontSize(toolTipOption.fontSize * opts.pix);
+      let textWidth = measureText(labelText[i], toolTipOption.fontSize * opts.pix, context);
+      let bgStartX, bgEndX, bgWidth;
+      if (widthArr[i].position == 'left') {
+        bgStartX = tStartLeft - (textWidth + boxPadding * 2) - 2 * opts.pix;
+        bgEndX = Math.max(bgStartX, bgStartX + textWidth + boxPadding * 2);
+      } else {
+        bgStartX = tStartRight + 2 * opts.pix;
+        bgEndX = Math.max(bgStartX + widthArr[i].width, bgStartX + textWidth + boxPadding * 2);
+      }
+      bgWidth = bgEndX - bgStartX;
+      let textX = bgStartX + (bgWidth - textWidth) / 2;
+      let textY = opts.tooltip.offset.y;
+      context.beginPath();
+      context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+      context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+      context.setLineWidth(1 * opts.pix);
+      context.rect(bgStartX, textY - 0.5 * config.fontSize - boxPadding, bgWidth, config.fontSize + 2 * boxPadding);
+      context.closePath();
+      context.stroke();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+      context.fillText(labelText[i], textX, textY + 0.5 * config.fontSize);
+      context.closePath();
+      context.stroke();
+      if (widthArr[i].position == 'left') {
+        tStartLeft -= (widthArr[i].width + opts.yAxis.padding * opts.pix);
+      } else {
+        tStartRight += widthArr[i].width + opts.yAxis.padding * opts.pix;
+      }
+    }
+  }
+}
+
+function drawToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {
+  var toolTipOption = assign({}, {
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08,
+    activeWidth: eachSpacing
+  }, opts.extra.column);
+  toolTipOption.activeWidth = toolTipOption.activeWidth > eachSpacing ? eachSpacing : toolTipOption.activeWidth;
+  var startY = opts.area[0];
+  var endY = opts.height - opts.area[2];
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect(offsetX - toolTipOption.activeWidth / 2, startY, toolTipOption.activeWidth, endY - startY);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+function drawBarToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {
+  var toolTipOption = assign({}, {
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08
+  }, opts.extra.bar);
+  var startX = opts.area[3];
+  var endX = opts.width - opts.area[1];
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect( startX ,offsetX - eachSpacing / 2 ,  endX - startX,eachSpacing);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+
+function drawToolTip(textList, offset, opts, config, context, eachSpacing, xAxisPoints) {
+  var toolTipOption = assign({}, {
+    showBox: true,
+    showArrow: true,
+    showCategory: false,
+    bgColor: '#000000',
+    bgOpacity: 0.7,
+    borderColor: '#000000',
+    borderWidth: 0,
+    borderRadius: 0,
+    borderOpacity: 0.7,
+    boxPadding: 3,
+    fontColor: '#FFFFFF',
+    fontSize: 13,
+    lineHeight: 20,
+    legendShow: true,
+    legendShape: 'auto',
+    splitLine: true,
+  }, opts.extra.tooltip);
+  if(toolTipOption.showCategory==true && opts.categories){
+    textList.unshift({text:opts.categories[opts.tooltip.index],color:null})
+  }
+  var fontSize = toolTipOption.fontSize * opts.pix;
+  var lineHeight = toolTipOption.lineHeight * opts.pix;
+  var boxPadding = toolTipOption.boxPadding * opts.pix;
+  var legendWidth = fontSize;
+  var legendMarginRight = 5 * opts.pix;
+  if(toolTipOption.legendShow == false){
+    legendWidth = 0;
+    legendMarginRight = 0;
+  }
+  var arrowWidth = toolTipOption.showArrow ? 8 * opts.pix : 0;
+  var isOverRightBorder = false;
+  if (opts.type == 'line' || opts.type == 'mount' || opts.type == 'area' || opts.type == 'candle' || opts.type == 'mix') {
+    if (toolTipOption.splitLine == true) {
+      drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context);
+    }
+  }
+  offset = assign({
+    x: 0,
+    y: 0
+  }, offset);
+  offset.y -= 8 * opts.pix;
+  var textWidth = textList.map(function(item) {
+    return measureText(item.text, fontSize, context);
+  });
+  var toolTipWidth = legendWidth + legendMarginRight + 4 * boxPadding + Math.max.apply(null, textWidth);
+  var toolTipHeight = 2 * boxPadding + textList.length * lineHeight;
+  if (toolTipOption.showBox == false) {
+    return
+  }
+  // if beyond the right border
+  if (offset.x - Math.abs(opts._scrollDistance_ || 0) + arrowWidth + toolTipWidth > opts.width) {
+    isOverRightBorder = true;
+  }
+  if (toolTipHeight + offset.y > opts.height) {
+    offset.y = opts.height - toolTipHeight;
+  }
+  // draw background rect
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.bgColor, toolTipOption.bgOpacity));
+  context.setLineWidth(toolTipOption.borderWidth * opts.pix);
+  context.setStrokeStyle(hexToRgb(toolTipOption.borderColor, toolTipOption.borderOpacity));
+  var radius = toolTipOption.borderRadius;
+  if (isOverRightBorder) {
+    // 增加左侧仍然超出的判断
+    if(toolTipWidth + arrowWidth > opts.width){
+      offset.x = opts.width + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width)
+    }
+    if(toolTipWidth > offset.x){
+      offset.x = opts.width + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width)
+    }
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+    }
+    context.arc(offset.x - arrowWidth - radius, offset.y + toolTipHeight - radius, radius, 0, Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + toolTipHeight - radius, radius,
+      Math.PI / 2, Math.PI, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - radius, offset.y + radius, radius, -Math.PI / 2, 0, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  } else {
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+    }
+    context.arc(offset.x + arrowWidth + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + radius, radius, -Math.PI / 2, 0,
+      false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + toolTipHeight - radius, radius, 0,
+      Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + radius, offset.y + toolTipHeight - radius, radius, Math.PI / 2, Math.PI, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  }
+  context.closePath();
+  context.fill();
+  if (toolTipOption.borderWidth > 0) {
+    context.stroke();
+  }
+  // draw legend
+  if(toolTipOption.legendShow){
+    textList.forEach(function(item, index) {
+      if (item.color !== null) {
+        context.beginPath();
+        context.setFillStyle(item.color);
+        var startX = offset.x + arrowWidth + 2 * boxPadding;
+        var startY = offset.y + (lineHeight - fontSize) / 2 + lineHeight * index + boxPadding + 1;
+        if (isOverRightBorder) {
+          startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding;
+        }
+        switch (item.legendShape) {
+          case 'line':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 2 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 2 * opts.pix, legendWidth, 4 * opts.pix);
+            break;
+          case 'triangle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'diamond':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'circle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.arc(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth, 5 * opts.pix, 0, 2 * Math.PI);
+            break;
+          case 'rect':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+            break;
+          case 'square':
+            context.moveTo(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+            break;
+          default:
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+        }
+        context.closePath();
+        context.fill();
+      }
+    });
+  }
+  
+  // draw text list
+  textList.forEach(function(item, index) {
+    var startX = offset.x + arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    if (isOverRightBorder) {
+      startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    }
+    var startY = offset.y + lineHeight * index + (lineHeight - fontSize)/2 - 1 + boxPadding + fontSize;
+    context.beginPath();
+    context.setFontSize(fontSize);
+    context.setTextBaseline('normal');
+    context.setFillStyle(toolTipOption.fontColor);
+    context.fillText(item.text, startX, startY);
+    context.closePath();
+    context.stroke();
+  });
+}
+
+function drawColumnDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let columnOption = assign({}, {
+    type: 'group',
+    width: eachSpacing / 2,
+    meterBorder: 4,
+    meterFillColor: '#FFFFFF',
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+    labelPosition: 'outside'
+  }, opts.extra.column);
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+  }
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTipSplitArea(opts.tooltip.offset.x, opts, config, context, eachSpacing);
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    
+    // 计算0轴坐标
+    let spacingValid = opts.height - opts.area[0] - opts.area[2];
+    let zeroHeight = spacingValid * (0 - minRange) / (maxRange - minRange);
+    let zeroPoints = opts.height - Math.round(zeroHeight) - opts.area[2];
+    eachSeries.zeroPoints = zeroPoints;
+    var data = eachSeries.data;
+    switch (columnOption.type) {
+      case 'group':
+        var points = getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, zeroPoints, process);
+        var tooltipPoints = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+        calPoints.push(tooltipPoints);
+        points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          //fix issues/I27B1N yyoinge & Joeshu
+          if (item !== null && i > leftNum && i < rightNum) {
+            var startX = item.x - item.width / 2;
+            var height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            var fillColor = item.color || eachSeries.color
+            var strokeColor = item.color || eachSeries.color
+            if (columnOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+              const left = startX;
+              const top = item.y > zeroPoints ? zeroPoints : item.y;
+              const width = item.width;
+              const height = Math.abs(zeroPoints - item.y);
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              if(item.y > zeroPoints){
+                columnOption.barBorderRadius = [0, 0,width / 2, width / 2];
+              }
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width, item.y);
+              context.lineTo(startX + item.width, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+              context.setLineWidth(1)
+              context.setStrokeStyle(strokeColor);
+            }
+            context.setFillStyle(fillColor);
+            context.closePath();
+            //context.stroke();
+            context.fill();
+          }
+        };
+        break;
+      case 'stack':
+        // 绘制堆叠数据图
+        var points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+        calPoints.push(points);
+        points = fixColumeStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            context.beginPath();
+            var fillColor = item.color || eachSeries.color;
+            var startX = item.x - item.width / 2 + 1;
+            var height = opts.height - item.y - opts.area[2];
+            var height0 = opts.height - item.y0 - opts.area[2];
+            if (seriesIndex > 0) {
+              height -= height0;
+            }
+            context.setFillStyle(fillColor);
+            context.moveTo(startX, item.y);
+            context.fillRect(startX, item.y, item.width, height);
+            context.closePath();
+            context.fill();
+          }
+        };
+        break;
+      case 'meter':
+        // 绘制温度计数据图
+        var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+        calPoints.push(points);
+        points = fixColumeMeterData(points, eachSpacing, series.length, seriesIndex, config, opts, columnOption.meterBorder);
+          for (let i = 0; i < points.length; i++) {
+            let item = points[i];
+            if (item !== null && i > leftNum && i < rightNum) {
+              //画背景颜色
+              context.beginPath();
+              if (seriesIndex == 0 && columnOption.meterBorder > 0) {
+                context.setStrokeStyle(eachSeries.color);
+                context.setLineWidth(columnOption.meterBorder * opts.pix);
+              }
+              if(seriesIndex == 0){
+                context.setFillStyle(columnOption.meterFillColor);
+              }else{
+                context.setFillStyle(item.color || eachSeries.color);
+              }
+              var startX = item.x - item.width / 2;
+              var height = opts.height - item.y - opts.area[2];
+              if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+                const left = startX;
+                const top = item.y;
+                const width = item.width;
+                const height = zeroPoints - item.y;
+                if (columnOption.barBorderCircle) {
+                  columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+                }
+                let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+                let minRadius = Math.min(width/2,height/2);
+                r0 = r0 > minRadius ? minRadius : r0;
+                r1 = r1 > minRadius ? minRadius : r1;
+                r2 = r2 > minRadius ? minRadius : r2;
+                r3 = r3 > minRadius ? minRadius : r3;
+                r0 = r0 < 0 ? 0 : r0;
+                r1 = r1 < 0 ? 0 : r1;
+                r2 = r2 < 0 ? 0 : r2;
+                r3 = r3 < 0 ? 0 : r3;
+                context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+                context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+                context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+                context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+                context.fill();
+              }else{
+                context.moveTo(startX, item.y);
+                context.lineTo(startX + item.width, item.y);
+                context.lineTo(startX + item.width, zeroPoints);
+                context.lineTo(startX, zeroPoints);
+                context.lineTo(startX, item.y);
+                context.fill();
+              }
+              if (seriesIndex == 0 && columnOption.meterBorder > 0) {
+                context.closePath();
+                context.stroke();
+              }
+            }
+          }
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      switch (columnOption.type) {
+        case 'group':
+          var points = getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+          points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        case 'stack':
+          var points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        case 'meter':
+          var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+      }
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawMountDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let mountOption = assign({}, {
+    type: 'mount',
+    widthRatio: 1,
+    borderWidth: 1,
+    barBorderCircle: false,
+    barBorderRadius: [],
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.mount);
+  mountOption.widthRatio = mountOption.widthRatio <= 0 ? 0 : mountOption.widthRatio;
+  mountOption.widthRatio = mountOption.widthRatio >= 2 ? 2 : mountOption.widthRatio;
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+  }
+  mountOption.customColor = fillCustomColor(mountOption.linearType, mountOption.customColor, series, config);
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    
+    // 计算0轴坐标
+    let spacingValid = opts.height - opts.area[0] - opts.area[2];
+    let zeroHeight = spacingValid * (0 - minRange) / (maxRange - minRange);
+    let zeroPoints = opts.height - Math.round(zeroHeight) - opts.area[2];
+    
+    var points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints, process);
+    switch (mountOption.type) {
+      case 'bar':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            var startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            var height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            var fillColor = item.color || series[i].color
+            var strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((mountOption.barBorderRadius && mountOption.barBorderRadius.length === 4) || mountOption.barBorderCircle === true) {
+              const left = startX;
+              const top = item.y > zeroPoints ? zeroPoints : item.y;
+              const width = item.width;
+              const height = Math.abs(zeroPoints - item.y);
+              if (mountOption.barBorderCircle) {
+                mountOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              if(item.y > zeroPoints){
+                mountOption.barBorderRadius = [0, 0,width / 2, width / 2];
+              }
+              let [r0, r1, r2, r3] = mountOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width, item.y);
+              context.lineTo(startX + item.width, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+            }
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.closePath();
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'triangle':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            var startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            var height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            var fillColor = item.color || series[i].color
+            var strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.lineTo(item.x, item.y);
+            context.lineTo(startX + item.width, zeroPoints);
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'mount':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            var startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            var height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            var fillColor = item.color || series[i].color
+            var strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.bezierCurveTo(item.x - item.width/4, zeroPoints, item.x - item.width/4, item.y, item.x, item.y);
+            context.bezierCurveTo(item.x + item.width/4, item.y, item.x + item.width/4, zeroPoints, startX + item.width, zeroPoints);
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'sharp':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            var startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            var height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            var fillColor = item.color || series[i].color
+            var strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.quadraticCurveTo(item.x - 0, zeroPoints - height/4, item.x, item.y);
+            context.quadraticCurveTo(item.x + 0, zeroPoints - height/4, startX + item.width, zeroPoints)
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+    }
+
+  if (opts.dataLabel !== false && process === 1) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints, process);
+    drawMountPointText(points, series, config, context, opts, zeroPoints);
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: points,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawBarDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let yAxisPoints = [];
+  let eachSpacing = (opts.height - opts.area[0] - opts.area[2])/opts.categories.length;
+  for (let i = 0; i < opts.categories.length; i++) {
+    yAxisPoints.push(opts.area[0] + eachSpacing / 2 + eachSpacing * i);
+  }
+  let columnOption = assign({}, {
+    type: 'group',
+    width: eachSpacing / 2,
+    meterBorder: 4,
+    meterFillColor: '#FFFFFF',
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.bar);
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = yAxisPoints.length + 2;
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawBarToolTipSplitArea(opts.tooltip.offset.y, opts, config, context, eachSpacing);
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.xAxisData.ranges);
+    maxRange = ranges.pop();
+    minRange = ranges.shift();
+    var data = eachSeries.data;
+    switch (columnOption.type) {
+      case 'group':
+        var points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, process);
+        var tooltipPoints = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+        calPoints.push(tooltipPoints);
+        points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          //fix issues/I27B1N yyoinge & Joeshu
+          if (item !== null && i > leftNum && i < rightNum) {
+            //var startX = item.x - item.width / 2;
+            var startX = opts.area[3];
+            var startY = item.y - item.width / 2;
+            var height = item.height;
+            context.beginPath();
+            var fillColor = item.color || eachSeries.color
+            var strokeColor = item.color || eachSeries.color
+            if (columnOption.linearType !== 'none') {
+              var grd = context.createLinearGradient(startX, item.y, item.x, item.y);
+              //透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+              const left = startX;
+              const width = item.width;
+              const top = item.y - item.width / 2;
+              const height = item.height;
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              
+              context.arc(left + r3, top + r3, r3, -Math.PI, -Math.PI / 2);
+              context.arc(item.x - r0, top + r0, r0, -Math.PI / 2, 0);
+              context.arc(item.x - r1, top + width - r1, r1, 0, Math.PI / 2);
+              context.arc(left + r2, top + width - r2, r2, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, startY);
+              context.lineTo(item.x, startY);
+              context.lineTo(item.x, startY + item.width);
+              context.lineTo(startX, startY + item.width);
+              context.lineTo(startX, startY);
+              context.setLineWidth(1)
+              context.setStrokeStyle(strokeColor);
+            }
+            context.setFillStyle(fillColor);
+            context.closePath();
+            //context.stroke();
+            context.fill();
+          }
+        };
+        break;
+      case 'stack':
+        // 绘制堆叠数据图
+        var points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+        calPoints.push(points);
+        points = fixBarStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            context.beginPath();
+            var fillColor = item.color || eachSeries.color;
+            var startX = item.x0;
+            context.setFillStyle(fillColor);
+            context.moveTo(startX, item.y - item.width/2);
+            context.fillRect(startX, item.y - item.width/2, item.height , item.width);
+            context.closePath();
+            context.fill();
+          }
+        };
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.xAxisData.ranges);
+      maxRange = ranges.pop();
+      minRange = ranges.shift();
+      var data = eachSeries.data;
+      switch (columnOption.type) {
+        case 'group':
+          var points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, process);
+          points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+          drawBarPointText(points, eachSeries, config, context, opts);
+          break;
+        case 'stack':
+          var points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);
+          drawBarPointText(points, eachSeries, config, context, opts);
+          break;
+      }
+    });
+  }
+  return {
+    yAxisPoints: yAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawCandleDataPoints(series, seriesMA, opts, config, context) {
+  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
+  var candleOption = assign({}, {
+    color: {},
+    average: {}
+  }, opts.extra.candle);
+  candleOption.color = assign({}, {
+    upLine: '#f04864',
+    upFill: '#f04864',
+    downLine: '#2fc25b',
+    downFill: '#2fc25b'
+  }, candleOption.color);
+  candleOption.average = assign({}, {
+    show: false,
+    name: [],
+    day: [],
+    color: config.color
+  }, candleOption.average);
+  opts.extra.candle = candleOption;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  //画均线
+  if (candleOption.average.show || seriesMA) { //Merge pull request !12 from 邱贵翔
+    seriesMA.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      var splitPointList = splitPoints(points,eachSeries);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(1);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              var ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x,
+                item.y);
+            }
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+  //画K线
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var data = eachSeries.data;
+    var points = getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    calPoints.push(points);
+    var splitPointList = splitPoints(points,eachSeries);
+    for (let i = 0; i < splitPointList[0].length; i++) {
+      if (i > leftNum && i < rightNum) {
+        let item = splitPointList[0][i];
+        context.beginPath();
+        //如果上涨
+        if (data[i][1] - data[i][0] > 0) {
+          context.setStrokeStyle(candleOption.color.upLine);
+          context.setFillStyle(candleOption.color.upFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); //顶点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[2].x, item[2].y); //底点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.moveTo(item[3].x, item[3].y); //顶点
+        } else {
+          context.setStrokeStyle(candleOption.color.downLine);
+          context.setFillStyle(candleOption.color.downFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); //顶点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[2].x, item[2].y); //底点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.moveTo(item[3].x, item[3].y); //顶点
+        }
+        context.closePath();
+        context.fill();
+        context.stroke();
+      }
+    }
+  });
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawAreaDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var areaOption = assign({}, {
+    type: 'straight',
+    opacity: 0.2,
+    addLine: false,
+    width: 2,
+    gradient: false,
+    activeType: 'none'
+  }, opts.extra.area);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let endY = opts.height - opts.area[2];
+  let calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    calPoints.push(points);
+    let splitPointList = splitPoints(points,eachSeries);
+    for (let i = 0; i < splitPointList.length; i++) {
+      let points = splitPointList[i];
+      // 绘制区域数
+      context.beginPath();
+      context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+      if (areaOption.gradient) {
+        let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
+        gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
+        gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+        context.setFillStyle(gradient);
+      } else {
+        context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+      }
+      context.setLineWidth(areaOption.width * opts.pix);
+      if (points.length > 1) {
+        let firstPoint = points[0];
+        let lastPoint = points[points.length - 1];
+        context.moveTo(firstPoint.x, firstPoint.y);
+        let startPoint = 0;
+        if (areaOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              let ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          };
+        } 
+        if (areaOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        if (areaOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        context.lineTo(lastPoint.x, endY);
+        context.lineTo(firstPoint.x, endY);
+        context.lineTo(firstPoint.x, firstPoint.y);
+      } else {
+        let item = points[0];
+        context.moveTo(item.x - eachSpacing / 2, item.y);
+        // context.lineTo(item.x + eachSpacing / 2, item.y);
+        // context.lineTo(item.x + eachSpacing / 2, endY);
+        // context.lineTo(item.x - eachSpacing / 2, endY);
+        // context.moveTo(item.x - eachSpacing / 2, item.y);
+      }
+      context.closePath();
+      context.fill();
+      //画连线
+      if (areaOption.addLine) {
+        if (eachSeries.lineType == 'dash') {
+          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+          dashLength *= opts.pix;
+          context.setLineDash([dashLength, dashLength]);
+        }
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(areaOption.width * opts.pix);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          if (areaOption.type === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            };
+          }
+          if (areaOption.type === 'straight') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          if (areaOption.type === 'step') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, points[j - 1].y);
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.stroke();
+        context.setLineDash([]);
+      }
+    }
+    //画点
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+    drawActivePoint(points, eachSeries.color, eachSeries.pointShape, context, opts, areaOption,seriesIndex);
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawScatterDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var scatterOption = assign({}, {
+    type: 'circle'
+  }, opts.extra.scatter);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  var calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var data = eachSeries.data;
+    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setFillStyle(eachSeries.color);
+    context.setLineWidth(1 * opts.pix);
+    var shape = eachSeries.pointShape;
+    if (shape === 'diamond') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y);
+          context.lineTo(item.x, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else if (shape === 'circle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x + 2.5 * opts.pix, item.y);
+          context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+        }
+      });
+    } else if (shape === 'square') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x - 3.5, item.y - 3.5);
+          context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+        }
+      });
+    } else if (shape === 'triangle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y + 4.5);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else if (shape === 'triangle') {
+      return;
+    }
+    context.closePath();
+    context.fill();
+    context.stroke();
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawBubbleDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var bubbleOption = assign({}, {
+    opacity: 1,
+    border:2
+  }, opts.extra.bubble);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  var calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var data = eachSeries.data;
+    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setLineWidth(bubbleOption.border * opts.pix);
+    context.setFillStyle(hexToRgb(eachSeries.color, bubbleOption.opacity));
+    points.forEach(function(item, index) {
+      context.moveTo(item.x + item.r, item.y);
+      context.arc(item.x, item.y, item.r * opts.pix, 0, 2 * Math.PI, false);
+    });
+    context.closePath();
+    context.fill();
+    context.stroke();
+    
+    if (opts.dataLabel !== false && process === 1) {
+      points.forEach(function(item, index) {
+        context.beginPath();
+        var fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+        context.setFontSize(fontSize);
+        context.setFillStyle(eachSeries.textColor || "#FFFFFF");
+        context.setTextAlign('center');
+        context.fillText(String(item.t), item.x, item.y + fontSize/2);
+        context.closePath();
+        context.stroke();
+        context.setTextAlign('left');
+      });
+    }
+  });
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawLineDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var lineOption = assign({}, {
+    type: 'straight',
+    width: 2,
+    activeType: 'none',
+    linearType: 'none',
+    onShadow: false,
+    animation: 'vertical',
+  }, opts.extra.line);
+  lineOption.width *= opts.pix;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  var calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    // 这段很神奇的代码用于解决ios16的setStrokeStyle失效的bug
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.moveTo(-10000, -10000);
+    context.lineTo(-10001, -10001);
+    context.stroke();
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var data = eachSeries.data;
+    var points = getLineDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, lineOption, process);
+    calPoints.push(points);
+    var splitPointList = splitPoints(points,eachSeries);
+    if (eachSeries.lineType == 'dash') {
+      let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+      dashLength *= opts.pix;
+      context.setLineDash([dashLength, dashLength]);
+    }
+    context.beginPath();
+    var strokeColor = eachSeries.color;
+    if (lineOption.linearType !== 'none' && eachSeries.linearColor && eachSeries.linearColor.length > 0) {
+      var grd = context.createLinearGradient(opts.chartData.xAxisData.startX, opts.height/2, opts.chartData.xAxisData.endX, opts.height/2);
+      for (var i = 0; i < eachSeries.linearColor.length; i++) {
+        grd.addColorStop(eachSeries.linearColor[i][0], hexToRgb(eachSeries.linearColor[i][1], 1));
+      }
+      strokeColor = grd
+    }
+    context.setStrokeStyle(strokeColor);
+    if (lineOption.onShadow == true && eachSeries.setShadow && eachSeries.setShadow.length > 0) {
+      context.setShadow(eachSeries.setShadow[0], eachSeries.setShadow[1], eachSeries.setShadow[2], eachSeries.setShadow[3]);
+    }else{
+      context.setShadow(0, 0, 0, 'rgba(0,0,0,0)');
+    }
+    context.setLineWidth(lineOption.width);
+    splitPointList.forEach(function(points, index) {
+      if (points.length === 1) {
+        context.moveTo(points[0].x, points[0].y);
+        // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+      } else {
+        context.moveTo(points[0].x, points[0].y);
+        let startPoint = 0;
+        if (lineOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              var ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          };
+        }
+        if (lineOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        if (lineOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        context.moveTo(points[0].x, points[0].y);
+      }
+    });
+    context.stroke();
+    context.setLineDash([]);
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+    drawActivePoint(points, eachSeries.color, eachSeries.pointShape, context, opts, lineOption);
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawMixDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let columnOption = assign({}, {
+    width: eachSpacing / 2,
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.mix.column);
+  let areaOption = assign({}, {
+    opacity: 0.2,
+    gradient: false
+  }, opts.extra.mix.area);
+  let lineOption = assign({}, {
+    width: 2
+  }, opts.extra.mix.line);
+  let endY = opts.height - opts.area[2];
+  let calPoints = [];
+  var columnIndex = 0;
+  var columnLength = 0;
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (eachSeries.type == 'column') {
+      columnLength += 1;
+    }
+  });
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    var data = eachSeries.data;
+    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+    calPoints.push(points);
+    // 绘制柱状数据图
+    if (eachSeries.type == 'column') {
+      points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);
+      for (let i = 0; i < points.length; i++) {
+        let item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          var startX = item.x - item.width / 2;
+          var height = opts.height - item.y - opts.area[2];
+          context.beginPath();
+          var fillColor = item.color || eachSeries.color
+          var strokeColor = item.color || eachSeries.color
+          if (columnOption.linearType !== 'none') {
+            var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);
+            //透明渐变
+            if (columnOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+              grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd
+          }
+          // 圆角边框
+          if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle) {
+            const left = startX;
+            const top = item.y;
+            const width = item.width;
+            const height = opts.height - opts.area[2] - item.y;
+            if (columnOption.barBorderCircle) {
+              columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+            }
+            let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+            let minRadius = Math.min(width/2,height/2);
+            r0 = r0 > minRadius ? minRadius : r0;
+            r1 = r1 > minRadius ? minRadius : r1;
+            r2 = r2 > minRadius ? minRadius : r2;
+            r3 = r3 > minRadius ? minRadius : r3;
+            r0 = r0 < 0 ? 0 : r0;
+            r1 = r1 < 0 ? 0 : r1;
+            r2 = r2 < 0 ? 0 : r2;
+            r3 = r3 < 0 ? 0 : r3;
+            context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+            context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+            context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+            context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+          } else {
+            context.moveTo(startX, item.y);
+            context.lineTo(startX + item.width, item.y);
+            context.lineTo(startX + item.width, opts.height - opts.area[2]);
+            context.lineTo(startX, opts.height - opts.area[2]);
+            context.lineTo(startX, item.y);
+            context.setLineWidth(1)
+            context.setStrokeStyle(strokeColor);
+          }
+          context.setFillStyle(fillColor);
+          context.closePath();
+          context.fill();
+        }
+      }
+      columnIndex += 1;
+    }
+    //绘制区域图数据
+    if (eachSeries.type == 'area') {
+      let splitPointList = splitPoints(points,eachSeries);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        // 绘制区域数据
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+        if (areaOption.gradient) {
+          let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
+          gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
+          gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+          context.setFillStyle(gradient);
+        } else {
+          context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+        }
+        context.setLineWidth(2 * opts.pix);
+        if (points.length > 1) {
+          var firstPoint = points[0];
+          let lastPoint = points[points.length - 1];
+          context.moveTo(firstPoint.x, firstPoint.y);
+          let startPoint = 0;
+          if (eachSeries.style === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                var ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            };
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          context.lineTo(lastPoint.x, endY);
+          context.lineTo(firstPoint.x, endY);
+          context.lineTo(firstPoint.x, firstPoint.y);
+        } else {
+          let item = points[0];
+          context.moveTo(item.x - eachSpacing / 2, item.y);
+          // context.lineTo(item.x + eachSpacing / 2, item.y);
+          // context.lineTo(item.x + eachSpacing / 2, endY);
+          // context.lineTo(item.x - eachSpacing / 2, endY);
+          // context.moveTo(item.x - eachSpacing / 2, item.y);
+        }
+        context.closePath();
+        context.fill();
+      }
+    }
+    // 绘制折线数据图
+    if (eachSeries.type == 'line') {
+      var splitPointList = splitPoints(points,eachSeries);
+      splitPointList.forEach(function(points, index) {
+        if (eachSeries.lineType == 'dash') {
+          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+          dashLength *= opts.pix;
+          context.setLineDash([dashLength, dashLength]);
+        }
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(lineOption.width * opts.pix);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          if (eachSeries.style == 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                var ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y,
+                  item.x, item.y);
+              }
+            }
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.stroke();
+        context.setLineDash([]);
+      });
+    }
+    // 绘制点数据图
+    if (eachSeries.type == 'point') {
+      eachSeries.addPoint = true;
+    }
+    if (eachSeries.addPoint == true && eachSeries.type !== 'column') {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    var columnIndex = 0;
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      var data = eachSeries.data;
+      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);
+      if (eachSeries.type !== 'column') {
+        drawPointText(points, eachSeries, config, context, opts);
+      } else {
+        points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);
+        drawPointText(points, eachSeries, config, context, opts);
+        columnIndex += 1;
+      }
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  }
+}
+
+
+function drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints) {
+  var toolTipOption = opts.extra.tooltip || {};
+  if (toolTipOption.horizentalLine && opts.tooltip && process === 1 && (opts.type == 'line' || opts.type == 'area' || opts.type == 'column' || opts.type == 'mount' || opts.type == 'candle' || opts.type == 'mix')) {
+    drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints)
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTip(opts.tooltip.textList, opts.tooltip.offset, opts, config, context, eachSpacing, xAxisPoints);
+  }
+  context.restore();
+
+}
+
+function drawXAxis(categories, opts, config, context) {
+
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    startX = xAxisData.startX,
+    endX = xAxisData.endX,
+    eachSpacing = xAxisData.eachSpacing;
+  var boundaryGap = 'center';
+  if (opts.type == 'bar' || opts.type == 'line' || opts.type == 'area'|| opts.type == 'scatter' || opts.type == 'bubble') {
+    boundaryGap = opts.xAxis.boundaryGap;
+  }
+  var startY = opts.height - opts.area[2];
+  var endY = opts.area[0];
+
+  //绘制滚动条
+  if (opts.enableScroll && opts.xAxis.scrollShow) {
+    var scrollY = opts.height - opts.area[2] + config.xAxisHeight;
+    var scrollScreenWidth = endX - startX;
+    var scrollTotalWidth = eachSpacing * (xAxisPoints.length - 1);
+    if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+      if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+      scrollTotalWidth += (opts.extra.mount.widthRatio - 1)*eachSpacing;
+    }
+    var scrollWidth = scrollScreenWidth * scrollScreenWidth / scrollTotalWidth;
+    var scrollLeft = 0;
+    if (opts._scrollDistance_) {
+      scrollLeft = -opts._scrollDistance_ * (scrollScreenWidth) / scrollTotalWidth;
+    }
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollBackgroundColor || "#EFEBEF");
+    context.moveTo(startX, scrollY);
+    context.lineTo(endX, scrollY);
+    context.stroke();
+    context.closePath();
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollColor || "#A6A6A6");
+    context.moveTo(startX + scrollLeft, scrollY);
+    context.lineTo(startX + scrollLeft + scrollWidth, scrollY);
+    context.stroke();
+    context.closePath();
+    context.setLineCap('butt');
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  //绘制X轴刻度线
+  if (opts.xAxis.calibration === true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    xAxisPoints.forEach(function(item, index) {
+      if (index > 0) {
+        context.beginPath();
+        context.moveTo(item - eachSpacing / 2, startY);
+        context.lineTo(item - eachSpacing / 2, startY + 3 * opts.pix);
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+  //绘制X轴网格
+  if (opts.xAxis.disableGrid !== true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    if (opts.xAxis.gridType == 'dash') {
+      context.setLineDash([opts.xAxis.dashLength * opts.pix, opts.xAxis.dashLength * opts.pix]);
+    }
+    opts.xAxis.gridEval = opts.xAxis.gridEval || 1;
+    xAxisPoints.forEach(function(item, index) {
+      if (index % opts.xAxis.gridEval == 0) {
+        context.beginPath();
+        context.moveTo(item, startY);
+        context.lineTo(item, endY);
+        context.stroke();
+      }
+    });
+    context.setLineDash([]);
+  }
+  //绘制X轴文案
+  if (opts.xAxis.disabled !== true) {
+    // 对X轴列表做抽稀处理
+    //默认全部显示X轴标签
+    let maxXAxisListLength = categories.length;
+    //如果设置了X轴单屏数量
+    if (opts.xAxis.labelCount) {
+      //如果设置X轴密度
+      if (opts.xAxis.itemCount) {
+        maxXAxisListLength = Math.ceil(categories.length / opts.xAxis.itemCount * opts.xAxis.labelCount);
+      } else {
+        maxXAxisListLength = opts.xAxis.labelCount;
+      }
+      maxXAxisListLength -= 1;
+    }
+
+    let ratio = Math.ceil(categories.length / maxXAxisListLength);
+
+    let newCategories = [];
+    let cgLength = categories.length;
+    for (let i = 0; i < cgLength; i++) {
+      if (i % ratio !== 0) {
+        newCategories.push("");
+      } else {
+        newCategories.push(categories[i]);
+      }
+    }
+    newCategories[cgLength - 1] = categories[cgLength - 1];
+    var xAxisFontSize = opts.xAxis.fontSize * opts.pix || config.fontSize;
+    if (config._xAxisTextAngle_ === 0) {
+      newCategories.forEach(function(item, index) {
+        var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;
+        var offset = -measureText(String(xitem), xAxisFontSize, context) / 2;
+        if (boundaryGap == 'center') {
+          offset += eachSpacing / 2;
+        }
+        var scrollHeight = 0;
+        if (opts.xAxis.scrollShow) {
+          scrollHeight = 6 * opts.pix;
+        }
+        // 如果在主视图区域内
+        var _scrollDistance_ = opts._scrollDistance_ || 0;
+        var truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if((truePoints - Math.abs(_scrollDistance_)) >= (opts.area[3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width - opts.area[1] + 1)){
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);
+          context.fillText(String(xitem), xAxisPoints[index] + offset, startY + opts.xAxis.marginTop * opts.pix + (opts.xAxis.lineHeight - opts.xAxis.fontSize) * opts.pix / 2 + opts.xAxis.fontSize * opts.pix);
+          context.closePath();
+          context.stroke();
+        }
+      });
+    } else {
+      newCategories.forEach(function(item, index) {
+        var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item) : item;
+        // 如果在主视图区域内
+        var _scrollDistance_ = opts._scrollDistance_ || 0;
+        var truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if((truePoints - Math.abs(_scrollDistance_)) >= (opts.area[3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width - opts.area[1] + 1)){
+          context.save();
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);
+          var textWidth = measureText(String(xitem), xAxisFontSize, context);
+          var offsetX = xAxisPoints[index];
+          if (boundaryGap == 'center') {
+            offsetX = xAxisPoints[index] + eachSpacing / 2;
+          }
+          var scrollHeight = 0;
+          if (opts.xAxis.scrollShow) {
+            scrollHeight = 6 * opts.pix;
+          }
+          var offsetY = startY + opts.xAxis.marginTop * opts.pix + xAxisFontSize - xAxisFontSize * Math.abs(Math.sin(config._xAxisTextAngle_));
+          if(opts.xAxis.rotateAngle < 0){
+            offsetX -= xAxisFontSize / 2;
+            textWidth = 0;
+          }else{
+            offsetX += xAxisFontSize / 2;
+            textWidth = -textWidth;
+          }
+          context.translate(offsetX, offsetY);
+          context.rotate(-1 * config._xAxisTextAngle_);
+          context.fillText(String(xitem), textWidth , 0 );
+          context.closePath();
+          context.stroke();
+          context.restore();
+        }
+      });
+    }
+  }
+  context.restore();
+  
+  //画X轴标题
+  if (opts.xAxis.title) {
+    context.beginPath();
+    context.setFontSize(opts.xAxis.titleFontSize * opts.pix);
+    context.setFillStyle(opts.xAxis.titleFontColor);
+    context.fillText(String(opts.xAxis.title), opts.width - opts.area[1] + opts.xAxis.titleOffsetX * opts.pix,opts.height - opts.area[2] + opts.xAxis.marginTop * opts.pix + (opts.xAxis.lineHeight - opts.xAxis.titleFontSize) * opts.pix / 2 + (opts.xAxis.titleFontSize + opts.xAxis.titleOffsetY) * opts.pix);
+    context.closePath();
+    context.stroke();
+  }
+  
+  //绘制X轴轴线
+  if (opts.xAxis.axisLine) {
+    context.beginPath();
+    context.setStrokeStyle(opts.xAxis.axisLineColor);
+    context.setLineWidth(1 * opts.pix);
+    context.moveTo(startX, opts.height - opts.area[2]);
+    context.lineTo(endX, opts.height - opts.area[2]);
+    context.stroke();
+  }
+}
+
+function drawYAxisGrid(categories, opts, config, context) {
+  if (opts.yAxis.disableGrid === true) {
+    return;
+  }
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  let eachSpacing = spacingValid / opts.yAxis.splitNumber;
+  let startX = opts.area[3];
+  let xAxisPoints = opts.chartData.xAxisData.xAxisPoints,
+    xAxiseachSpacing = opts.chartData.xAxisData.eachSpacing;
+  let TotalWidth = xAxiseachSpacing * (xAxisPoints.length - 1);
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1 ){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    TotalWidth += (opts.extra.mount.widthRatio - 1) * xAxiseachSpacing;
+  }
+  let endX = startX + TotalWidth;
+  let points = [];
+  let startY = 1
+  if (opts.xAxis.axisLine === false) {
+    startY = 0
+  }
+  for (let i = startY; i < opts.yAxis.splitNumber + 1; i++) {
+    points.push(opts.height - opts.area[2] - eachSpacing * i);
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.yAxis.gridType == 'dash') {
+    context.setLineDash([opts.yAxis.dashLength * opts.pix, opts.yAxis.dashLength * opts.pix]);
+  }
+  context.setStrokeStyle(opts.yAxis.gridColor);
+  context.setLineWidth(1 * opts.pix);
+  points.forEach(function(item, index) {
+    context.beginPath();
+    context.moveTo(startX, item);
+    context.lineTo(endX, item);
+    context.stroke();
+  });
+  context.setLineDash([]);
+  context.restore();
+}
+
+function drawYAxis(series, opts, config, context) {
+  if (opts.yAxis.disabled === true) {
+    return;
+  }
+  var spacingValid = opts.height - opts.area[0] - opts.area[2];
+  var eachSpacing = spacingValid / opts.yAxis.splitNumber;
+  var startX = opts.area[3];
+  var endX = opts.width - opts.area[1];
+  var endY = opts.height - opts.area[2];
+  // set YAxis background
+  context.beginPath();
+  context.setFillStyle(opts.background);
+  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'left') {
+    context.fillRect(0, 0, startX, endY + 2 * opts.pix);
+  }
+  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'right') {
+    context.fillRect(endX, 0, opts.width, endY + 2 * opts.pix);
+  }
+  context.closePath();
+  context.stroke();
+  
+  let tStartLeft = opts.area[3];
+  let tStartRight = opts.width - opts.area[1];
+  let tStartCenter = opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2;
+  if (opts.yAxis.data) {
+    for (let i = 0; i < opts.yAxis.data.length; i++) {
+      let yData = opts.yAxis.data[i];
+      var points = [];
+      if(yData.type === 'categories'){
+        for (let i = 0; i <= yData.categories.length; i++) {
+          points.push(opts.area[0] + spacingValid / yData.categories.length / 2 + spacingValid / yData.categories.length * i);
+        }
+      }else{
+        for (let i = 0; i <= opts.yAxis.splitNumber; i++) {
+          points.push(opts.area[0] + eachSpacing * i);
+        }
+      }
+      if (yData.disabled !== true) {
+        let rangesFormat = opts.chartData.yAxisData.rangesFormat[i];
+        let yAxisFontSize = yData.fontSize ? yData.fontSize * opts.pix : config.fontSize;
+        let yAxisWidth = opts.chartData.yAxisData.yAxisWidth[i];
+        let textAlign = yData.textAlign || "right";
+        //画Y轴刻度及文案
+        rangesFormat.forEach(function(item, index) {
+          var pos = points[index];
+          context.beginPath();
+          context.setFontSize(yAxisFontSize);
+          context.setLineWidth(1 * opts.pix);
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setFillStyle(yData.fontColor || opts.fontColor);
+          let tmpstrat = 0;
+          let gapwidth = 4 * opts.pix;
+          if (yAxisWidth.position == 'left') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartLeft, pos);
+              context.lineTo(tStartLeft - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            //画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartLeft - yAxisWidth.width
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartLeft - gapwidth
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartLeft - yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+
+          } else if (yAxisWidth.position == 'right') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartRight, pos);
+              context.lineTo(tStartRight + 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartRight + gapwidth
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartRight + yAxisWidth.width
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartRight + yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartCenter, pos);
+              context.lineTo(tStartCenter - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            //画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartCenter - yAxisWidth.width
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartCenter - gapwidth
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartCenter - yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+          context.setTextAlign('left');
+        });
+        //画Y轴轴线
+        if (yData.axisLine !== false) {
+          context.beginPath();
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setLineWidth(1 * opts.pix);
+          if (yAxisWidth.position == 'left') {
+            context.moveTo(tStartLeft, opts.height - opts.area[2]);
+            context.lineTo(tStartLeft, opts.area[0]);
+          } else if (yAxisWidth.position == 'right') {
+            context.moveTo(tStartRight, opts.height - opts.area[2]);
+            context.lineTo(tStartRight, opts.area[0]);
+          } else if (yAxisWidth.position == 'center') {
+            context.moveTo(tStartCenter, opts.height - opts.area[2]);
+            context.lineTo(tStartCenter, opts.area[0]);
+          }
+          context.stroke();
+        }
+        //画Y轴标题
+        if (opts.yAxis.showTitle) {
+          let titleFontSize = yData.titleFontSize * opts.pix || config.fontSize;
+          let title = yData.title;
+          context.beginPath();
+          context.setFontSize(titleFontSize);
+          context.setFillStyle(yData.titleFontColor || opts.fontColor);
+          if (yAxisWidth.position == 'left') {
+            context.fillText(title, tStartLeft - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'right') {
+            context.fillText(title, tStartRight - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            context.fillText(title, tStartCenter - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+        }
+        if (yAxisWidth.position == 'left') {
+          tStartLeft -= (yAxisWidth.width + opts.yAxis.padding * opts.pix);
+        } else {
+          tStartRight += yAxisWidth.width + opts.yAxis.padding * opts.pix;
+        }
+      }
+    }
+  }
+
+}
+
+function drawLegend(series, opts, config, context, chartData) {
+  if (opts.legend.show === false) {
+    return;
+  }
+  let legendData = chartData.legendData;
+  let legendList = legendData.points;
+  let legendArea = legendData.area;
+  let padding = opts.legend.padding * opts.pix;
+  let fontSize = opts.legend.fontSize * opts.pix;
+  let shapeWidth = 15 * opts.pix;
+  let shapeRight = 5 * opts.pix;
+  let itemGap = opts.legend.itemGap * opts.pix;
+  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);
+  //画背景及边框
+  context.beginPath();
+  context.setLineWidth(opts.legend.borderWidth * opts.pix);
+  context.setStrokeStyle(opts.legend.borderColor);
+  context.setFillStyle(opts.legend.backgroundColor);
+  context.moveTo(legendArea.start.x, legendArea.start.y);
+  context.rect(legendArea.start.x, legendArea.start.y, legendArea.width, legendArea.height);
+  context.closePath();
+  context.fill();
+  context.stroke();
+  legendList.forEach(function(itemList, listIndex) {
+    let width = 0;
+    let height = 0;
+    width = legendData.widthArr[listIndex];
+    height = legendData.heightArr[listIndex];
+    let startX = 0;
+    let startY = 0;
+    if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+      switch (opts.legend.float) {
+        case 'left':
+          startX = legendArea.start.x + padding;
+        break;
+        case 'right':
+          startX = legendArea.start.x + legendArea.width - width;
+        break;
+        default:
+        startX = legendArea.start.x + (legendArea.width - width) / 2;
+      }
+      startY = legendArea.start.y + padding + listIndex * lineHeight;
+    } else {
+      if (listIndex == 0) {
+        width = 0;
+      } else {
+        width = legendData.widthArr[listIndex - 1];
+      }
+      startX = legendArea.start.x + padding + width;
+      startY = legendArea.start.y + padding + (legendArea.height - height) / 2;
+    }
+    context.setFontSize(config.fontSize);
+    for (let i = 0; i < itemList.length; i++) {
+      let item = itemList[i];
+      item.area = [0, 0, 0, 0];
+      item.area[0] = startX;
+      item.area[1] = startY;
+      item.area[3] = startY + lineHeight;
+      context.beginPath();
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.show ? item.color : opts.legend.hiddenColor);
+      context.setFillStyle(item.show ? item.color : opts.legend.hiddenColor);
+      switch (item.legendShape) {
+        case 'line':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 2 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 2 * opts.pix, 15 * opts.pix, 4 * opts.pix);
+          break;
+        case 'triangle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'diamond':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'circle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.arc(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight, 5 * opts.pix, 0, 2 * Math.PI);
+          break;
+        case 'rect':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+          break;
+        case 'square':
+          context.moveTo(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+          break;
+        case 'none':
+          break;
+        default:
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+      }
+      context.closePath();
+      context.fill();
+      context.stroke();
+      startX += shapeWidth + shapeRight;
+      let fontTrans = 0.5 * lineHeight + 0.5 * fontSize - 2;
+      const legendText = item.legendText ? item.legendText : item.name;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.show ? opts.legend.fontColor : opts.legend.hiddenColor);
+      context.fillText(legendText, startX, startY + fontTrans);
+      context.closePath();
+      context.stroke();
+      if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+        startX += measureText(legendText, fontSize, context) + itemGap;
+        item.area[2] = startX;
+      } else {
+        item.area[2] = startX + measureText(legendText, fontSize, context) + itemGap;;
+        startX -= shapeWidth + shapeRight;
+        startY += lineHeight;
+      }
+    }
+  });
+}
+
+function drawPieDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var pieOption = assign({}, {
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    ringWidth: 30,
+    customRadius: 0,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    centerColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.type == "pie" ? opts.extra.pie : opts.extra.ring);
+  var centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = pieOption.activeRadius * opts.pix;
+  }
+
+  var radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+  if (pieOption.customRadius > 0) {
+    radius = pieOption.customRadius * opts.pix;
+  }
+  series = getPieDataPoints(series, radius, process);
+  var activeRadius = pieOption.activeRadius * opts.pix;
+  pieOption.customColor = fillCustomColor(pieOption.linearType, pieOption.customColor, series, config);
+  series = series.map(function(eachSeries) {
+    eachSeries._start_ += (pieOption.offsetAngle) * Math.PI / 180;
+    return eachSeries;
+  });
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color, pieOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_ + activeRadius, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(pieOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(pieOption.borderColor);
+    var fillcolor = eachSeries.color;
+    if (pieOption.linearType == 'custom') {
+      var grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }
+      grd.addColorStop(0, hexToRgb(pieOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))
+      fillcolor = grd
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);
+    context.closePath();
+    context.fill();
+    if (pieOption.border == true) {
+      context.stroke();
+    }
+  });
+  if (opts.type === 'ring') {
+    var innerPieWidth = radius * 0.6;
+    if (typeof pieOption.ringWidth === 'number' && pieOption.ringWidth > 0) {
+      innerPieWidth = Math.max(0, radius - pieOption.ringWidth * opts.pix);
+    }
+    context.beginPath();
+    context.setFillStyle(pieOption.centerColor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, innerPieWidth, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+  }
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+  if (process === 1 && opts.type === 'ring') {
+    drawRingTitle(opts, config, context, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawRoseDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var roseOption = assign({}, {
+    type: 'area',
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.rose);
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = roseOption.activeRadius * opts.pix;
+  }
+  var centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  var radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+  var minRadius = roseOption.minRadius || radius * 0.5;
+  if(radius < minRadius){
+    radius = minRadius + 10;
+  }
+  series = getRoseDataPoints(series, roseOption.type, minRadius, radius, process);
+  var activeRadius = roseOption.activeRadius * opts.pix;
+  roseOption.customColor = fillCustomColor(roseOption.linearType, roseOption.customColor, series, config);
+  series = series.map(function(eachSeries) {
+    eachSeries._start_ += (roseOption.offsetAngle || 0) * Math.PI / 180;
+    return eachSeries;
+  });
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color, roseOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, activeRadius + eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(roseOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(roseOption.borderColor);
+    var fillcolor = eachSeries.color;
+    if (roseOption.linearType == 'custom') {
+      var grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }
+      grd.addColorStop(0, hexToRgb(roseOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))
+      fillcolor = grd
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);
+    context.closePath();
+    context.fill();
+    if (roseOption.border == true) {
+      context.stroke();
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawArcbarDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var arcbarOption = assign({}, {
+    startAngle: 0.75,
+    endAngle: 0.25,
+    type: 'default',
+    direction: 'cw',
+    lineCap: 'round',
+    width: 12 ,
+    gap: 2 ,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.arcbar);
+  series = getArcbarDataPoints(series, arcbarOption, process);
+  var centerPosition;
+  if (arcbarOption.centerX || arcbarOption.centerY) {
+    centerPosition = {
+      x: arcbarOption.centerX ? arcbarOption.centerX : opts.width / 2,
+      y: arcbarOption.centerY ? arcbarOption.centerY : opts.height / 2
+    };
+  } else {
+    centerPosition = {
+      x: opts.width / 2,
+      y: opts.height / 2
+    };
+  }
+  var radius;
+  if (arcbarOption.radius) {
+    radius = arcbarOption.radius;
+  } else {
+    radius = Math.min(centerPosition.x, centerPosition.y);
+    radius -= 5 * opts.pix;
+    radius -= arcbarOption.width / 2;
+  }
+  radius = radius < 10 ? 10 : radius;
+  arcbarOption.customColor = fillCustomColor(arcbarOption.linearType, arcbarOption.customColor, series, config);
+  
+  for (let i = 0; i < series.length; i++) {
+    let eachSeries = series[i];
+    //背景颜色
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(arcbarOption.backgroundColor || '#E9E9E9');
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    if (arcbarOption.type == 'default') {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, arcbarOption.endAngle * Math.PI, arcbarOption.direction == 'ccw');
+    } else {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, 0, 2 * Math.PI, arcbarOption.direction == 'ccw');
+    }
+    context.stroke();
+    //进度条
+    var fillColor = eachSeries.color
+    if(arcbarOption.linearType == 'custom'){
+      var grd = context.createLinearGradient(centerPosition.x - radius, centerPosition.y, centerPosition.x + radius, centerPosition.y);
+      grd.addColorStop(1, hexToRgb(arcbarOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(0, hexToRgb(eachSeries.color, 1))
+      fillColor = grd;
+    }
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(fillColor);
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, eachSeries._proportion_ * Math.PI, arcbarOption.direction == 'ccw');
+    context.stroke();
+  }
+  drawRingTitle(opts, config, context, centerPosition);
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawGaugeDataPoints(categories, series, opts, config, context) {
+  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
+  var gaugeOption = assign({}, {
+    type: 'default',
+    startAngle: 0.75,
+    endAngle: 0.25,
+    width: 15,
+    labelOffset:13,
+    splitLine: {
+      fixRadius: 0,
+      splitNumber: 10,
+      width: 15,
+      color: '#FFFFFF',
+      childNumber: 5,
+      childWidth: 5
+    },
+    pointer: {
+      width: 15,
+      color: 'auto'
+    }
+  }, opts.extra.gauge);
+  if (gaugeOption.oldAngle == undefined) {
+    gaugeOption.oldAngle = gaugeOption.startAngle;
+  }
+  if (gaugeOption.oldData == undefined) {
+    gaugeOption.oldData = 0;
+  }
+  categories = getGaugeAxisPoints(categories, gaugeOption.startAngle, gaugeOption.endAngle);
+  var centerPosition = {
+    x: opts.width / 2,
+    y: opts.height / 2
+  };
+  var radius = Math.min(centerPosition.x, centerPosition.y);
+  radius -= 5 * opts.pix;
+  radius -= gaugeOption.width / 2;
+  radius = radius < 10 ? 10 : radius;
+  var innerRadius = radius - gaugeOption.width;
+  var totalAngle = 0;
+  //判断仪表盘的样式:default百度样式,progress新样式
+  if (gaugeOption.type == 'progress') {
+    //## 第一步画中心圆形背景和进度条背景
+    //中心圆形背景
+    var pieRadius = radius - gaugeOption.width * 3;
+    context.beginPath();
+    let gradient = context.createLinearGradient(centerPosition.x, centerPosition.y - pieRadius, centerPosition.x, centerPosition.y + pieRadius);
+    //配置渐变填充(起点:中心点向上减半径;结束点中心点向下加半径)
+    gradient.addColorStop('0', hexToRgb(series[0].color, 0.3));
+    gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+    context.setFillStyle(gradient);
+    context.arc(centerPosition.x, centerPosition.y, pieRadius, 0, 2 * Math.PI, false);
+    context.fill();
+    //画进度条背景
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(hexToRgb(series[0].color, 0.3));
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, gaugeOption.endAngle * Math.PI, false);
+    context.stroke();
+    //## 第二步画刻度线
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;
+    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;
+    let endX = -radius - gaugeOption.width - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    let len = gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1;
+    let proc = series[0].data * process;
+    for (let i = 0; i < len; i++) {
+      context.beginPath();
+      //刻度线随进度变色
+      if (proc > (i / len)) {
+        context.setStrokeStyle(hexToRgb(series[0].color, 1));
+      } else {
+        context.setStrokeStyle(hexToRgb(series[0].color, 0.3));
+      }
+      context.setLineWidth(3 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(endX, 0);
+      context.stroke();
+      context.rotate(childAngle * Math.PI);
+    }
+    context.restore();
+    //## 第三步画进度条
+    series = getGaugeArcbarDataPoints(series, gaugeOption, process);
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(series[0].color);
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, series[0]._proportion_ * Math.PI, false);
+    context.stroke();
+    //## 第四步画指针
+    let pointerRadius = radius - gaugeOption.width * 2.5;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((series[0]._proportion_ - 1) * Math.PI);
+    context.beginPath();
+    context.setLineWidth(gaugeOption.width / 3);
+    let gradient3 = context.createLinearGradient(0, -pointerRadius * 0.6, 0, pointerRadius * 0.6);
+    gradient3.addColorStop('0', hexToRgb('#FFFFFF', 0));
+    gradient3.addColorStop('0.5', hexToRgb(series[0].color, 1));
+    gradient3.addColorStop('1.0', hexToRgb('#FFFFFF', 0));
+    context.setStrokeStyle(gradient3);
+    context.arc(0, 0, pointerRadius, 0.85 * Math.PI, 1.15 * Math.PI, false);
+    context.stroke();
+    context.beginPath();
+    context.setLineWidth(1);
+    context.setStrokeStyle(series[0].color);
+    context.setFillStyle(series[0].color);
+    context.moveTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2 - 4, 0);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, 4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.stroke();
+    context.fill();
+    context.restore();
+    //default百度样式
+  } else {
+    //画背景
+    context.setLineWidth(gaugeOption.width);
+    context.setLineCap('butt');
+    for (let i = 0; i < categories.length; i++) {
+      let eachCategories = categories[i];
+      context.beginPath();
+      context.setStrokeStyle(eachCategories.color);
+      context.arc(centerPosition.x, centerPosition.y, radius, eachCategories._startAngle_ * Math.PI, eachCategories._endAngle_ * Math.PI, false);
+      context.stroke();
+    }
+    context.save();
+    //画刻度线
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;
+    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;
+    let endX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;
+    let childendX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.childWidth;
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {
+      context.beginPath();
+      context.setStrokeStyle(gaugeOption.splitLine.color);
+      context.setLineWidth(2 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(endX, 0);
+      context.stroke();
+      context.rotate(splitAngle * Math.PI);
+    }
+    context.restore();
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    for (let i = 0; i < gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1; i++) {
+      context.beginPath();
+      context.setStrokeStyle(gaugeOption.splitLine.color);
+      context.setLineWidth(1 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(childendX, 0);
+      context.stroke();
+      context.rotate(childAngle * Math.PI);
+    }
+    context.restore();
+    //画指针
+    series = getGaugeDataPoints(series, categories, gaugeOption, process);
+    for (let i = 0; i < series.length; i++) {
+      let eachSeries = series[i];
+      context.save();
+      context.translate(centerPosition.x, centerPosition.y);
+      context.rotate((eachSeries._proportion_ - 1) * Math.PI);
+      context.beginPath();
+      context.setFillStyle(eachSeries.color);
+      context.moveTo(gaugeOption.pointer.width, 0);
+      context.lineTo(0, -gaugeOption.pointer.width / 2);
+      context.lineTo(-innerRadius, 0);
+      context.lineTo(0, gaugeOption.pointer.width / 2);
+      context.lineTo(gaugeOption.pointer.width, 0);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFillStyle('#FFFFFF');
+      context.arc(0, 0, gaugeOption.pointer.width / 6, 0, 2 * Math.PI, false);
+      context.fill();
+      context.restore();
+    }
+    if (opts.dataLabel !== false) {
+      drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context);
+    }
+  }
+  //画仪表盘标题,副标题
+  drawRingTitle(opts, config, context, centerPosition);
+  if (process === 1 && opts.type === 'gauge') {
+    opts.extra.gauge.oldAngle = series[0]._proportion_;
+    opts.extra.gauge.oldData = series[0].data;
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    innerRadius: innerRadius,
+    categories: categories,
+    totalAngle: totalAngle
+  };
+}
+
+function drawRadarDataPoints(series, opts, config, context) {
+  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  var radarOption = assign({}, {
+    gridColor: '#cccccc',
+    gridType: 'radar',
+    gridEval:1,
+    axisLabel:false,
+    axisLabelTofix:0,
+    labelShow:true,
+    labelColor:'#666666',
+    labelPointShow:false,
+    labelPointRadius:3,
+    labelPointColor:'#cccccc',
+    opacity: 0.2,
+    gridCount: 3,
+    border:false,
+    borderWidth:2,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.radar);
+  var coordinateAngle = getRadarCoordinateSeries(opts.categories.length);
+  var centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  var xr = (opts.width - opts.area[1] - opts.area[3]) / 2
+  var yr = (opts.height - opts.area[0] - opts.area[2]) / 2
+  var radius = Math.min(xr - (getMaxTextListLength(opts.categories, config.fontSize, context) + config.radarLabelTextMargin), yr - config.radarLabelTextMargin);
+  radius -= config.radarLabelTextMargin * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  radius = radarOption.radius ? radarOption.radius : radius;
+  // 画分割线
+  context.beginPath();
+  context.setLineWidth(1 * opts.pix);
+  context.setStrokeStyle(radarOption.gridColor);
+  coordinateAngle.forEach(function(angle,index) {
+    var pos = convertCoordinateOrigin(radius * Math.cos(angle), radius * Math.sin(angle), centerPosition);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    if (index % radarOption.gridEval == 0) {
+      context.lineTo(pos.x, pos.y);
+    }
+  });
+  context.stroke();
+  context.closePath();
+  
+  // 画背景网格
+  var _loop = function _loop(i) {
+    var startPos = {};
+    context.beginPath();
+    context.setLineWidth(1 * opts.pix);
+    context.setStrokeStyle(radarOption.gridColor);
+    if (radarOption.gridType == 'radar') {
+      coordinateAngle.forEach(function(angle, index) {
+        var pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(angle), radius /
+          radarOption.gridCount * i * Math.sin(angle), centerPosition);
+        if (index === 0) {
+          startPos = pos;
+          context.moveTo(pos.x, pos.y);
+        } else {
+          context.lineTo(pos.x, pos.y);
+        }
+      });
+      context.lineTo(startPos.x, startPos.y);
+    } else {
+      var pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(1.5), radius / radarOption.gridCount * i * Math.sin(1.5), centerPosition);
+      context.arc(centerPosition.x, centerPosition.y, centerPosition.y - pos.y, 0, 2 * Math.PI, false);
+    }
+    context.stroke();
+    context.closePath();
+  };
+  for (var i = 1; i <= radarOption.gridCount; i++) {
+    _loop(i);
+  }
+  radarOption.customColor = fillCustomColor(radarOption.linearType, radarOption.customColor, series, config);
+  var radarDataPoints = getRadarDataPoints(coordinateAngle, centerPosition, radius, series, opts, process);
+  radarDataPoints.forEach(function(eachSeries, seriesIndex) {
+    // 绘制区域数据
+    context.beginPath();
+    context.setLineWidth(radarOption.borderWidth * opts.pix);
+    context.setStrokeStyle(eachSeries.color);
+    
+    var fillcolor = hexToRgb(eachSeries.color, radarOption.opacity);
+    if (radarOption.linearType == 'custom') {
+      var grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, radius)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, radius)
+      }
+      grd.addColorStop(0, hexToRgb(radarOption.customColor[series[seriesIndex].linearIndex], radarOption.opacity))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, radarOption.opacity))
+      fillcolor = grd
+    }
+    
+    context.setFillStyle(fillcolor);
+    eachSeries.data.forEach(function(item, index) {
+      if (index === 0) {
+        context.moveTo(item.position.x, item.position.y);
+      } else {
+        context.lineTo(item.position.x, item.position.y);
+      }
+    });
+    context.closePath();
+    context.fill();
+    if(radarOption.border === true){
+      context.stroke();
+    }
+    context.closePath();
+    if (opts.dataPointShape !== false) {
+      var points = eachSeries.data.map(function(item) {
+        return item.position;
+      });
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+  });
+  // 画刻度值
+  if(radarOption.axisLabel === true){
+    const maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));
+    const stepLength = radius / radarOption.gridCount;
+    const fontSize = opts.fontSize * opts.pix;
+    context.setFontSize(fontSize);
+    context.setFillStyle(opts.fontColor);
+    context.setTextAlign('left');
+    for (var i = 0; i < radarOption.gridCount + 1; i++) {
+      let label = i * maxData / radarOption.gridCount;
+      label = label.toFixed(radarOption.axisLabelTofix);
+      context.fillText(String(label), centerPosition.x + 3 * opts.pix, centerPosition.y - i * stepLength + fontSize / 2);
+    }
+  }
+  
+  // draw label text
+  drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context);
+  
+  // draw dataLabel
+  if (opts.dataLabel !== false && process === 1) {
+    radarDataPoints.forEach(function(eachSeries, seriesIndex) {
+      context.beginPath();
+      var fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(eachSeries.textColor || opts.fontColor);
+      eachSeries.data.forEach(function(item, index) {
+        //如果是中心点垂直的上下点位
+        if(Math.abs(item.position.x - centerPosition.x)<2){
+          //如果在上面
+          if(item.position.y < centerPosition.y){
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y - 4);
+          }else{
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y + fontSize + 2);
+          }
+        }else{
+          //如果在左侧
+          if(item.position.x < centerPosition.x){
+            context.setTextAlign('right');
+            context.fillText(item.value, item.position.x - 4, item.position.y + fontSize / 2 - 2);
+          }else{
+            context.setTextAlign('left');
+            context.fillText(item.value, item.position.x + 4, item.position.y + fontSize / 2 - 2);
+          }
+        }
+      });
+      context.closePath();
+      context.stroke();
+    });
+    context.setTextAlign('left');
+  }
+  
+  return {
+    center: centerPosition,
+    radius: radius,
+    angleList: coordinateAngle
+  };
+}
+
+// 经纬度转墨卡托
+function lonlat2mercator(longitude, latitude) {
+  var mercator = Array(2);
+  var x = longitude * 20037508.34 / 180;
+  var y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180);
+  y = y * 20037508.34 / 180;
+  mercator[0] = x;
+  mercator[1] = y;
+  return mercator;
+}
+
+// 墨卡托转经纬度
+function mercator2lonlat(longitude, latitude) {
+  var lonlat = Array(2)
+  var x = longitude / 20037508.34 * 180;
+  var y = latitude / 20037508.34 * 180;
+  y = 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);
+  lonlat[0] = x;
+  lonlat[1] = y;
+  return lonlat;
+}
+
+function getBoundingBox(data) {
+  var bounds = {},coords;
+  bounds.xMin = 180;
+  bounds.xMax = 0;
+  bounds.yMin = 90;
+  bounds.yMax = 0
+  for (var i = 0; i < data.length; i++) {
+    var coorda = data[i].geometry.coordinates
+    for (var k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0]
+      }
+      for (var j = 0; j < coords.length; j++) {
+        var longitude = coords[j][0];
+        var latitude = coords[j][1];
+        var point = {
+          x: longitude,
+          y: latitude
+        }
+        bounds.xMin = bounds.xMin < point.x ? bounds.xMin : point.x;
+        bounds.xMax = bounds.xMax > point.x ? bounds.xMax : point.x;
+        bounds.yMin = bounds.yMin < point.y ? bounds.yMin : point.y;
+        bounds.yMax = bounds.yMax > point.y ? bounds.yMax : point.y;
+      }
+    }
+  }
+  return bounds;
+}
+
+function coordinateToPoint(latitude, longitude, bounds, scale, xoffset, yoffset) {
+  return {
+    x: (longitude - bounds.xMin) * scale + xoffset,
+    y: (bounds.yMax - latitude) * scale + yoffset
+  };
+}
+
+function pointToCoordinate(pointY, pointX, bounds, scale, xoffset, yoffset) {
+  return {
+    x: (pointX - xoffset) / scale + bounds.xMin,
+    y: bounds.yMax - (pointY - yoffset) / scale
+  };
+}
+
+function isRayIntersectsSegment(poi, s_poi, e_poi) {
+  if (s_poi[1] == e_poi[1]) {
+    return false;
+  }
+  if (s_poi[1] > poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[1] < poi[1] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  if (s_poi[1] == poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (e_poi[1] == poi[1] && s_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[0] < poi[0] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  let xseg = e_poi[0] - (e_poi[0] - s_poi[0]) * (e_poi[1] - poi[1]) / (e_poi[1] - s_poi[1]);
+  if (xseg < poi[0]) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function isPoiWithinPoly(poi, poly, mercator) {
+  let sinsc = 0;
+  for (let i = 0; i < poly.length; i++) {
+    let epoly = poly[i][0];
+    if (poly.length == 1) {
+      epoly = poly[i][0]
+    }
+    for (let j = 0; j < epoly.length - 1; j++) {
+      let s_poi = epoly[j];
+      let e_poi = epoly[j + 1];
+      if (mercator) {
+        s_poi = lonlat2mercator(epoly[j][0], epoly[j][1]);
+        e_poi = lonlat2mercator(epoly[j + 1][0], epoly[j + 1][1]);
+      }
+      if (isRayIntersectsSegment(poi, s_poi, e_poi)) {
+        sinsc += 1;
+      }
+    }
+  }
+  if (sinsc % 2 == 1) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function drawMapDataPoints(series, opts, config, context) {
+  var mapOption = assign({}, {
+    border: true,
+    mercator: false,
+    borderWidth: 1,
+    active:true,
+    borderColor: '#666666',
+    fillOpacity: 0.6,
+    activeBorderColor: '#f04864',
+    activeFillColor: '#facc14',
+    activeFillOpacity: 1
+  }, opts.extra.map);
+  var coords, point;
+  var data = series;
+  var bounds = getBoundingBox(data);
+  if (mapOption.mercator) {
+    var max = lonlat2mercator(bounds.xMax, bounds.yMax)
+    var min = lonlat2mercator(bounds.xMin, bounds.yMin)
+    bounds.xMax = max[0]
+    bounds.yMax = max[1]
+    bounds.xMin = min[0]
+    bounds.yMin = min[1]
+  }
+  var xScale = opts.width / Math.abs(bounds.xMax - bounds.xMin);
+  var yScale = opts.height / Math.abs(bounds.yMax - bounds.yMin);
+  var scale = xScale < yScale ? xScale : yScale;
+  var xoffset = opts.width / 2 - Math.abs(bounds.xMax - bounds.xMin) / 2 * scale;
+  var yoffset = opts.height / 2 - Math.abs(bounds.yMax - bounds.yMin) / 2 * scale;
+  for (var i = 0; i < data.length; i++) {
+    context.beginPath();
+    context.setLineWidth(mapOption.borderWidth * opts.pix);
+    context.setStrokeStyle(mapOption.borderColor);
+    context.setFillStyle(hexToRgb(series[i].color, series[i].fillOpacity||mapOption.fillOpacity));
+    if (mapOption.active == true && opts.tooltip) {
+      if (opts.tooltip.index == i) {
+        context.setStrokeStyle(mapOption.activeBorderColor);
+        context.setFillStyle(hexToRgb(mapOption.activeFillColor, mapOption.activeFillOpacity));
+      }
+    }
+    var coorda = data[i].geometry.coordinates
+    for (var k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0]
+      }
+      for (var j = 0; j < coords.length; j++) {
+        var gaosi = Array(2);
+        if (mapOption.mercator) {
+          gaosi = lonlat2mercator(coords[j][0], coords[j][1])
+        } else {
+          gaosi = coords[j]
+        }
+        point = coordinateToPoint(gaosi[1], gaosi[0], bounds, scale, xoffset, yoffset)
+        if (j === 0) {
+          context.beginPath();
+          context.moveTo(point.x, point.y);
+        } else {
+          context.lineTo(point.x, point.y);
+        }
+      }
+      context.fill();
+      if (mapOption.border == true) {
+        context.stroke();
+      }
+    }
+  }
+  if (opts.dataLabel == true) {
+    for (var i = 0; i < data.length; i++) {
+      var centerPoint = data[i].properties.centroid;
+      if (centerPoint) {
+        if (mapOption.mercator) {
+          centerPoint = lonlat2mercator(data[i].properties.centroid[0], data[i].properties.centroid[1])
+        }
+        point = coordinateToPoint(centerPoint[1], centerPoint[0], bounds, scale, xoffset, yoffset);
+        let fontSize = data[i].textSize * opts.pix || config.fontSize;
+        let fontColor = data[i].textColor || opts.fontColor;
+        if(mapOption.active && mapOption.activeTextColor && opts.tooltip && opts.tooltip.index == i){
+          fontColor = mapOption.activeTextColor;
+        }
+        let text = data[i].properties.name;
+        context.beginPath();
+        context.setFontSize(fontSize)
+        context.setFillStyle(fontColor)
+        context.fillText(text, point.x - measureText(text, fontSize, context) / 2, point.y + fontSize / 2);
+        context.closePath();
+        context.stroke();
+      }
+    }
+  }
+  opts.chartData.mapData = {
+    bounds: bounds,
+    scale: scale,
+    xoffset: xoffset,
+    yoffset: yoffset,
+    mercator: mapOption.mercator
+  }
+  drawToolTipBridge(opts, config, context, 1);
+  context.draw();
+}
+
+function normalInt(min, max, iter) {
+  iter = iter == 0 ? 1 : iter;
+  var arr = [];
+  for (var i = 0; i < iter; i++) {
+    arr[i] = Math.random();
+  };
+  return Math.floor(arr.reduce(function(i, j) {
+    return i + j
+  }) / iter * (max - min)) + min;
+};
+
+function collisionNew(area, points, width, height) {
+  var isIn = false;
+  for (let i = 0; i < points.length; i++) {
+    if (points[i].area) {
+      if (area[3] < points[i].area[1] || area[0] > points[i].area[2] || area[1] > points[i].area[3] || area[2] < points[i].area[0]) {
+        if (area[0] < 0 || area[1] < 0 || area[2] > width || area[3] > height) {
+          isIn = true;
+          break;
+        } else {
+          isIn = false;
+        }
+      } else {
+        isIn = true;
+        break;
+      }
+    }
+  }
+  return isIn;
+};
+
+function getWordCloudPoint(opts, type, context) {
+  let points = opts.series;
+  switch (type) {
+    case 'normal':
+      for (let i = 0; i < points.length; i++) {
+        let text = points[i].name;
+        let tHeight = points[i].textSize * opts.pix;
+        let tWidth = measureText(text, tHeight, context);
+        let x, y;
+        let area;
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+          y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+          area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 +
+            opts.height / 2
+          ];
+          let isCollision = collisionNew(area, points, opts.width, opts.height);
+          if (!isCollision) break;
+          if (breaknum == 1000) {
+            area = [-100, -100, -100, -100];
+            break;
+          }
+        };
+        points[i].area = area;
+      }
+      break;
+    case 'vertical':
+      function Spin() {
+        //获取均匀随机值,是否旋转,旋转的概率为(1-0.5)
+        if (Math.random() > 0.7) {
+          return true;
+        } else {
+          return false
+        };
+      };
+      for (let i = 0; i < points.length; i++) {
+        let text = points[i].name;
+        let tHeight = points[i].textSize * opts.pix;
+        let tWidth = measureText(text, tHeight, context);
+        let isSpin = Spin();
+        let x, y, area, areav;
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          let isCollision;
+          if (isSpin) {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [y - 5 - tWidth + opts.width / 2, (-x - 5 + opts.height / 2), y + 5 + opts.width / 2, (-x + tHeight + 5 + opts.height / 2)];
+            areav = [opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) - 5, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) - 5, opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) + tHeight, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) + tWidth + 5];
+            isCollision = collisionNew(areav, points, opts.height, opts.width);
+          } else {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 + opts.height / 2];
+            isCollision = collisionNew(area, points, opts.width, opts.height);
+          }
+          if (!isCollision) break;
+          if (breaknum == 1000) {
+            area = [-1000, -1000, -1000, -1000];
+            break;
+          }
+        };
+        if (isSpin) {
+          points[i].area = areav;
+          points[i].areav = area;
+        } else {
+          points[i].area = area;
+        }
+        points[i].rotate = isSpin;
+      };
+      break;
+  }
+  return points;
+}
+
+function drawWordCloudDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let wordOption = assign({}, {
+    type: 'normal',
+    autoColors: true
+  }, opts.extra.word);
+  if (!opts.chartData.wordCloudData) {
+    opts.chartData.wordCloudData = getWordCloudPoint(opts, wordOption.type, context);
+  }
+  context.beginPath();
+  context.setFillStyle(opts.background);
+  context.rect(0, 0, opts.width, opts.height);
+  context.fill();
+  context.save();
+  let points = opts.chartData.wordCloudData;
+  context.translate(opts.width / 2, opts.height / 2);
+  for (let i = 0; i < points.length; i++) {
+    context.save();
+    if (points[i].rotate) {
+      context.rotate(90 * Math.PI / 180);
+    }
+    let text = points[i].name;
+    let tHeight = points[i].textSize * opts.pix;
+    let tWidth = measureText(text, tHeight, context);
+    context.beginPath();
+    context.setStrokeStyle(points[i].color);
+    context.setFillStyle(points[i].color);
+    context.setFontSize(tHeight);
+    if (points[i].rotate) {
+      if (points[i].areav[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    } else {
+      if (points[i].area[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    }
+    context.stroke();
+    context.restore();
+  }
+  context.restore();
+}
+
+function drawFunnelDataPoints(series, opts, config, context) {
+  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
+  let funnelOption = assign({}, {
+    type:'funnel',
+    activeWidth: 10,
+    activeOpacity: 0.3,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    fillOpacity: 1,
+    minSize: 0,
+    labelAlign: 'right',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.funnel);
+  let eachSpacing = (opts.height - opts.area[0] - opts.area[2]) / series.length;
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.height - opts.area[2]
+  };
+  let activeWidth = funnelOption.activeWidth * opts.pix;
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - activeWidth, (opts.height - opts.area[0] - opts.area[2]) / 2 - activeWidth);
+  let seriesNew = getFunnelDataPoints(series, radius, funnelOption, eachSpacing, process);
+  context.save();
+  context.translate(centerPosition.x, centerPosition.y);
+  funnelOption.customColor = fillCustomColor(funnelOption.linearType, funnelOption.customColor, series, config);
+  if(funnelOption.type == 'pyramid'){
+    for (let i = 0; i < seriesNew.length; i++) {
+      if (i == seriesNew.length -1) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(-activeWidth, -eachSpacing);
+            context.lineTo(-seriesNew[i].radius - activeWidth, 0);
+            context.lineTo(seriesNew[i].radius + activeWidth, 0);
+            context.lineTo(activeWidth, -eachSpacing);
+            context.lineTo(-activeWidth, -eachSpacing);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * i];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        var fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          var grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, -eachSpacing);
+        context.lineTo(-seriesNew[i].radius, 0);
+        context.lineTo(seriesNew[i].radius, 0);
+        context.lineTo(0, -eachSpacing);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      } else {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(0, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, 0);
+            context.lineTo(-seriesNew[i + 1].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i + 1].radius + activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, 0);
+            context.lineTo(0, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * i];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        var fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          var grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-seriesNew[i].radius, 0);
+        context.lineTo(-seriesNew[i + 1].radius, -eachSpacing);
+        context.lineTo(seriesNew[i + 1].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      }
+      context.translate(0, -eachSpacing)
+    }
+  }else{
+    context.translate(0, - (seriesNew.length - 1) * eachSpacing);
+    for (let i = 0; i < seriesNew.length; i++) {
+      if (i == seriesNew.length - 1) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(-activeWidth - funnelOption.minSize/2, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, -eachSpacing);
+            context.lineTo(activeWidth + funnelOption.minSize/2, 0);
+            context.lineTo(-activeWidth - funnelOption.minSize/2, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing, centerPosition.x + seriesNew[i].radius, centerPosition.y ];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        var fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          var grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-funnelOption.minSize/2, 0);
+        context.lineTo(-seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, -eachSpacing);
+        context.lineTo(funnelOption.minSize/2, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      } else {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(0, 0);
+            context.lineTo(-seriesNew[i + 1].radius - activeWidth, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i + 1].radius + activeWidth, 0);
+            context.lineTo(0, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (seriesNew.length - i), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * (seriesNew.length - i - 1)];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        var fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          var grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-seriesNew[i + 1].radius, 0);
+        context.lineTo(-seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i + 1].radius, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      }
+      context.translate(0, eachSpacing)
+    }
+  }
+  
+  context.restore();
+  if (opts.dataLabel !== false && process === 1) {
+    drawFunnelText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+  if (process === 1) {
+    drawFunnelCenterText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: seriesNew
+  };
+}
+
+function drawFunnelText(series, opts, context, eachSpacing, labelAlign, activeWidth, centerPosition) {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    if(item.labelShow === false){
+      continue;
+    }
+    let startX, endX, startY, fontSize;
+    let text = item.formatter ? item.formatter(item,i,series,opts) : util.toFixed(item._proportion_ * 100) + '%';
+    text = item.labelText ? item.labelText : text;
+    if (labelAlign == 'right') {
+      if (i == series.length -1) {
+        startX = (item.funnelArea[2] + centerPosition.x) / 2;
+      } else {
+        startX = (item.funnelArea[2] + series[i + 1].funnelArea[2]) / 2;
+      }
+      endX = startX + activeWidth * 2;
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.color);
+      context.setFillStyle(item.color);
+      context.beginPath();
+      context.moveTo(startX, startY);
+      context.lineTo(endX, startY);
+      context.stroke();
+      context.closePath();
+      context.beginPath();
+      context.moveTo(endX, startY);
+      context.arc(endX, startY, 2 * opts.pix, 0, 2 * Math.PI);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.textColor || opts.fontColor);
+      context.fillText(text, endX + 5, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+    if (labelAlign == 'left') {
+      if (i == series.length -1) {
+        startX = (item.funnelArea[0] + centerPosition.x) / 2;
+      } else {
+        startX = (item.funnelArea[0] + series[i + 1].funnelArea[0]) / 2;
+      }
+      endX = startX - activeWidth * 2;
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.color);
+      context.setFillStyle(item.color);
+      context.beginPath();
+      context.moveTo(startX, startY);
+      context.lineTo(endX, startY);
+      context.stroke();
+      context.closePath();
+      context.beginPath();
+      context.moveTo(endX, startY);
+      context.arc(endX, startY, 2, 0, 2 * Math.PI);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.textColor || opts.fontColor);
+      context.fillText(text, endX - 5 - measureText(text, fontSize, context), startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+  }
+}
+
+function drawFunnelCenterText(series, opts, context, eachSpacing, labelAlign, activeWidth, centerPosition) {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    let startY, fontSize;
+    if (item.centerText) {
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.centerTextSize * opts.pix || opts.fontSize * opts.pix;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.centerTextColor || "#FFFFFF");
+      context.fillText(item.centerText, centerPosition.x - measureText(item.centerText, fontSize, context) / 2, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+  }
+}
+
+
+function drawCanvas(opts, context) {
+  context.save();
+  context.translate(0, 0.5);
+  context.restore();
+  context.draw();
+}
+
+var Timing = {
+  easeIn: function easeIn(pos) {
+    return Math.pow(pos, 3);
+  },
+  easeOut: function easeOut(pos) {
+    return Math.pow(pos - 1, 3) + 1;
+  },
+  easeInOut: function easeInOut(pos) {
+    if ((pos /= 0.5) < 1) {
+      return 0.5 * Math.pow(pos, 3);
+    } else {
+      return 0.5 * (Math.pow(pos - 2, 3) + 2);
+    }
+  },
+  linear: function linear(pos) {
+    return pos;
+  }
+};
+
+function Animation(opts) {
+  this.isStop = false;
+  opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration;
+  opts.timing = opts.timing || 'easeInOut';
+  var delay = 17;
+  function createAnimationFrame() {
+    if (typeof setTimeout !== 'undefined') {
+      return function(step, delay) {
+        setTimeout(function() {
+          var timeStamp = +new Date();
+          step(timeStamp);
+        }, delay);
+      };
+    } else if (typeof requestAnimationFrame !== 'undefined') {
+      return requestAnimationFrame;
+    } else {
+      return function(step) {
+        step(null);
+      };
+    }
+  };
+  var animationFrame = createAnimationFrame();
+  var startTimeStamp = null;
+  var _step = function step(timestamp) {
+    if (timestamp === null || this.isStop === true) {
+      opts.onProcess && opts.onProcess(1);
+      opts.onAnimationFinish && opts.onAnimationFinish();
+      return;
+    }
+    if (startTimeStamp === null) {
+      startTimeStamp = timestamp;
+    }
+    if (timestamp - startTimeStamp < opts.duration) {
+      var process = (timestamp - startTimeStamp) / opts.duration;
+      var timingFunction = Timing[opts.timing];
+      process = timingFunction(process);
+      opts.onProcess && opts.onProcess(process);
+      animationFrame(_step, delay);
+    } else {
+      opts.onProcess && opts.onProcess(1);
+      opts.onAnimationFinish && opts.onAnimationFinish();
+    }
+  };
+  _step = _step.bind(this);
+  animationFrame(_step, delay);
+}
+
+Animation.prototype.stop = function() {
+  this.isStop = true;
+};
+
+function drawCharts(type, opts, config, context) {
+  var _this = this;
+  var series = opts.series;
+  //兼容ECharts饼图类数据格式
+  if (type === 'pie' || type === 'ring' || type === 'mount' || type === 'rose' || type === 'funnel') {
+    series = fixPieSeries(series, opts, config);
+  }
+  var categories = opts.categories;
+  if (type === 'mount') {
+    categories = [];
+    for (let j = 0; j < series.length; j++) {
+      if(series[j].show !== false) categories.push(series[j].name)
+    }
+    opts.categories = categories;
+  }
+  series = fillSeries(series, opts, config);
+  var duration = opts.animation ? opts.duration : 0;
+  _this.animationInstance && _this.animationInstance.stop();
+  var seriesMA = null;
+  if (type == 'candle') {
+    let average = assign({}, opts.extra.candle.average);
+    if (average.show) {
+      seriesMA = calCandleMA(average.day, average.name, average.color, series[0].data);
+      seriesMA = fillSeries(seriesMA, opts, config);
+      opts.seriesMA = seriesMA;
+    } else if (opts.seriesMA) {
+      seriesMA = opts.seriesMA = fillSeries(opts.seriesMA, opts, config);
+    } else {
+      seriesMA = series;
+    }
+  } else {
+    seriesMA = series;
+  }
+  /* 过滤掉show=false的series */
+  opts._series_ = series = filterSeries(series);
+  //重新计算图表区域
+  opts.area = new Array(4);
+  //复位绘图区域
+  for (let j = 0; j < 4; j++) {
+    opts.area[j] = opts.padding[j] * opts.pix;
+  }
+  //通过计算三大区域:图例、X轴、Y轴的大小,确定绘图区域
+  var _calLegendData = calLegendData(seriesMA, opts, config, opts.chartData, context),
+    legendHeight = _calLegendData.area.wholeHeight,
+    legendWidth = _calLegendData.area.wholeWidth;
+
+  switch (opts.legend.position) {
+    case 'top':
+      opts.area[0] += legendHeight;
+      break;
+    case 'bottom':
+      opts.area[2] += legendHeight;
+      break;
+    case 'left':
+      opts.area[3] += legendWidth;
+      break;
+    case 'right':
+      opts.area[1] += legendWidth;
+      break;
+  }
+
+  let _calYAxisData = {},
+    yAxisWidth = 0;
+  if (opts.type === 'line' || opts.type === 'column'|| opts.type === 'mount' || opts.type === 'area' || opts.type === 'mix' || opts.type === 'candle' || opts.type === 'scatter'  || opts.type === 'bubble' || opts.type === 'bar') {
+      _calYAxisData = calYAxisData(series, opts, config, context);
+      yAxisWidth = _calYAxisData.yAxisWidth;
+    //如果显示Y轴标题
+    if (opts.yAxis.showTitle) {
+      let maxTitleHeight = 0;
+      for (let i = 0; i < opts.yAxis.data.length; i++) {
+        maxTitleHeight = Math.max(maxTitleHeight, opts.yAxis.data[i].titleFontSize ? opts.yAxis.data[i].titleFontSize * opts.pix : config.fontSize)
+      }
+      opts.area[0] += maxTitleHeight;
+    }
+    let rightIndex = 0,
+      leftIndex = 0;
+    //计算主绘图区域左右位置
+    for (let i = 0; i < yAxisWidth.length; i++) {
+      if (yAxisWidth[i].position == 'left') {
+        if (leftIndex > 0) {
+          opts.area[3] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[3] += yAxisWidth[i].width;
+        }
+        leftIndex += 1;
+      } else if (yAxisWidth[i].position == 'right') {
+        if (rightIndex > 0) {
+          opts.area[1] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[1] += yAxisWidth[i].width;
+        }
+        rightIndex += 1;
+      }
+    }
+  } else {
+    config.yAxisWidth = yAxisWidth;
+  }
+  opts.chartData.yAxisData = _calYAxisData;
+
+  if (opts.categories && opts.categories.length && opts.type !== 'radar' && opts.type !== 'gauge' && opts.type !== 'bar') {
+    opts.chartData.xAxisData = getXAxisPoints(opts.categories, opts, config);
+    let _calCategoriesData = calCategoriesData(opts.categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),
+      xAxisHeight = _calCategoriesData.xAxisHeight,
+      angle = _calCategoriesData.angle;
+    config.xAxisHeight = xAxisHeight;
+    config._xAxisTextAngle_ = angle;
+    opts.area[2] += xAxisHeight;
+    opts.chartData.categoriesData = _calCategoriesData;
+  } else {
+    if (opts.type === 'line' || opts.type === 'area' || opts.type === 'scatter' || opts.type === 'bubble' || opts.type === 'bar') {
+      opts.chartData.xAxisData = calXAxisData(series, opts, config, context);
+      categories = opts.chartData.xAxisData.rangesFormat;
+      let _calCategoriesData = calCategoriesData(categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),
+        xAxisHeight = _calCategoriesData.xAxisHeight,
+        angle = _calCategoriesData.angle;
+      config.xAxisHeight = xAxisHeight;
+      config._xAxisTextAngle_ = angle;
+      opts.area[2] += xAxisHeight;
+      opts.chartData.categoriesData = _calCategoriesData;
+    } else {
+      opts.chartData.xAxisData = {
+        xAxisPoints: []
+      };
+    }
+  }
+
+  //计算右对齐偏移距离
+  if (opts.enableScroll && opts.xAxis.scrollAlign == 'right' && opts._scrollDistance_ === undefined) {
+    let offsetLeft = 0,
+      xAxisPoints = opts.chartData.xAxisData.xAxisPoints,
+      startX = opts.chartData.xAxisData.startX,
+      endX = opts.chartData.xAxisData.endX,
+      eachSpacing = opts.chartData.xAxisData.eachSpacing;
+    let totalWidth = eachSpacing * (xAxisPoints.length - 1);
+    let screenWidth = endX - startX;
+    offsetLeft = screenWidth - totalWidth;
+    _this.scrollOption.currentOffset = offsetLeft;
+    _this.scrollOption.startTouchX = offsetLeft;
+    _this.scrollOption.distance = 0;
+    _this.scrollOption.lastMoveTime = 0;
+    opts._scrollDistance_ = offsetLeft;
+  }
+
+  if (type === 'pie' || type === 'ring' || type === 'rose') {
+    config._pieTextMaxLength_ = opts.dataLabel === false ? 0 : getPieTextMaxLength(seriesMA, config, context, opts);
+  }
+  
+  switch (type) {
+    case 'word':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawWordCloudDataPoints(series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'map':
+      context.clearRect(0, 0, opts.width, opts.height);
+      drawMapDataPoints(series, opts, config, context);
+      setTimeout(()=>{
+        this.uevent.trigger('renderComplete');
+      },50)
+      break;
+    case 'funnel':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.funnelData = drawFunnelDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'line':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawLineDataPoints = drawLineDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawLineDataPoints.xAxisPoints,
+            calPoints = _drawLineDataPoints.calPoints,
+            eachSpacing = _drawLineDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'scatter':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawScatterDataPoints = drawScatterDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawScatterDataPoints.xAxisPoints,
+            calPoints = _drawScatterDataPoints.calPoints,
+            eachSpacing = _drawScatterDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bubble':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawBubbleDataPoints = drawBubbleDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawBubbleDataPoints.xAxisPoints,
+            calPoints = _drawBubbleDataPoints.calPoints,
+            eachSpacing = _drawBubbleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mix':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawMixDataPoints = drawMixDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawMixDataPoints.xAxisPoints,
+            calPoints = _drawMixDataPoints.calPoints,
+            eachSpacing = _drawMixDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'column':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawColumnDataPoints = drawColumnDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawColumnDataPoints.xAxisPoints,
+            calPoints = _drawColumnDataPoints.calPoints,
+            eachSpacing = _drawColumnDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mount':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawMountDataPoints = drawMountDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawMountDataPoints.xAxisPoints,
+            calPoints = _drawMountDataPoints.calPoints,
+            eachSpacing = _drawMountDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawXAxis(categories, opts, config, context);
+          var _drawBarDataPoints = drawBarDataPoints(series, opts, config, context, process),
+            yAxisPoints = _drawBarDataPoints.yAxisPoints,
+            calPoints = _drawBarDataPoints.calPoints,
+            eachSpacing = _drawBarDataPoints.eachSpacing;
+          opts.chartData.yAxisPoints = yAxisPoints;
+          opts.chartData.xAxisPoints = opts.chartData.xAxisData.xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, yAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'area':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawAreaDataPoints = drawAreaDataPoints(series, opts, config, context, process),
+            xAxisPoints = _drawAreaDataPoints.xAxisPoints,
+            calPoints = _drawAreaDataPoints.calPoints,
+            eachSpacing = _drawAreaDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'ring':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'pie':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'rose':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawRoseDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'radar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.radarData = drawRadarDataPoints(series, opts, config, context, process);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'arcbar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.arcbarData = drawArcbarDataPoints(series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'gauge':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.gaugeData = drawGaugeDataPoints(categories, series, opts, config, context, process);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'candle':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          var _drawCandleDataPoints = drawCandleDataPoints(series, seriesMA, opts, config, context, process),
+            xAxisPoints = _drawCandleDataPoints.xAxisPoints,
+            calPoints = _drawCandleDataPoints.calPoints,
+            eachSpacing = _drawCandleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          if (seriesMA) {
+            drawLegend(seriesMA, opts, config, context, opts.chartData);
+          } else {
+            drawLegend(opts.series, opts, config, context, opts.chartData);
+          }
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+  }
+}
+
+function uChartsEvent() {
+  this.events = {};
+}
+
+uChartsEvent.prototype.addEventListener = function(type, listener) {
+  this.events[type] = this.events[type] || [];
+  this.events[type].push(listener);
+};
+
+uChartsEvent.prototype.delEventListener = function(type) {
+  this.events[type] = [];
+};
+
+uChartsEvent.prototype.trigger = function() {
+  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+    args[_key] = arguments[_key];
+  }
+  var type = args[0];
+  var params = args.slice(1);
+  if (!!this.events[type]) {
+    this.events[type].forEach(function(listener) {
+      try {
+        listener.apply(null, params);
+      } catch (e) {
+          //console.log('[uCharts] '+e);
+      }
+    });
+  }
+};
+
+var uCharts = function uCharts(opts) {
+  opts.pix = opts.pixelRatio ? opts.pixelRatio : 1;
+  opts.fontSize = opts.fontSize ? opts.fontSize : 13;
+  opts.fontColor = opts.fontColor ? opts.fontColor : config.fontColor;
+  if (opts.background == "" || opts.background == "none") {
+    opts.background = "#FFFFFF"
+  }
+  opts.title = assign({}, opts.title);
+  opts.subtitle = assign({}, opts.subtitle);
+  opts.duration = opts.duration ? opts.duration : 1000;
+  opts.yAxis = assign({}, {
+    data: [],
+    showTitle: false,
+    disabled: false,
+    disableGrid: false,
+    gridSet: 'number',
+    splitNumber: 5,
+    gridType: 'solid',
+    dashLength: 4 * opts.pix,
+    gridColor: '#cccccc',
+    padding: 10,
+    fontColor: '#666666'
+  }, opts.yAxis);
+  opts.xAxis = assign({}, {
+    rotateLabel: false,
+    rotateAngle:45,
+    disabled: false,
+    disableGrid: false,
+    splitNumber: 5,
+    calibration:false,
+    fontColor: '#666666',
+    fontSize: 13,
+    lineHeight: 20,
+    marginTop: 0,
+    gridType: 'solid',
+    dashLength: 4,
+    scrollAlign: 'left',
+    boundaryGap: 'center',
+    axisLine: true,
+    axisLineColor: '#cccccc',
+    titleFontSize: 13,
+    titleOffsetY: 0,
+    titleOffsetX: 0,
+    titleFontColor: '#666666'
+  }, opts.xAxis);
+  opts.xAxis.scrollPosition = opts.xAxis.scrollAlign;
+  opts.legend = assign({}, {
+    show: true,
+    position: 'bottom',
+    float: 'center',
+    backgroundColor: 'rgba(0,0,0,0)',
+    borderColor: 'rgba(0,0,0,0)',
+    borderWidth: 0,
+    padding: 5,
+    margin: 5,
+    itemGap: 10,
+    fontSize: opts.fontSize,
+    lineHeight: opts.fontSize,
+    fontColor: opts.fontColor,
+    formatter: {},
+    hiddenColor: '#CECECE'
+  }, opts.legend);
+  opts.extra = assign({
+    tooltip:{
+      legendShape: 'auto'
+    }
+  }, opts.extra);
+  opts.rotate = opts.rotate ? true : false;
+  opts.animation = opts.animation ? true : false;
+  opts.rotate = opts.rotate ? true : false;
+  opts.canvas2d = opts.canvas2d ? true : false;
+  
+  let config$$1 = assign({}, config);
+  config$$1.color = opts.color ? opts.color : config$$1.color;
+  if (opts.type == 'pie') {
+    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.pie.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;
+  }
+  if (opts.type == 'ring') {
+    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.ring.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;
+  }
+  if (opts.type == 'rose') {
+    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.rose.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;
+  }
+  config$$1.pieChartTextPadding = opts.dataLabel === false ? 0 : config$$1.pieChartTextPadding * opts.pix;
+
+  //屏幕旋转
+  config$$1.rotate = opts.rotate;
+  if (opts.rotate) {
+    let tempWidth = opts.width;
+    let tempHeight = opts.height;
+    opts.width = tempHeight;
+    opts.height = tempWidth;
+  }
+
+  //适配高分屏
+  opts.padding = opts.padding ? opts.padding : config$$1.padding;
+  config$$1.yAxisWidth = config.yAxisWidth * opts.pix;
+  config$$1.fontSize = opts.fontSize * opts.pix;
+  config$$1.titleFontSize = config.titleFontSize * opts.pix;
+  config$$1.subtitleFontSize = config.subtitleFontSize * opts.pix;
+  if(!opts.context){
+    throw new Error('[uCharts] 未获取到context!注意:v2.0版本后,需要自行获取canvas的绘图上下文并传入opts.context!');
+  }
+  this.context = opts.context;
+  if (!this.context.setTextAlign) {
+    this.context.setStrokeStyle = function(e) {
+      return this.strokeStyle = e;
+    }
+    this.context.setLineWidth = function(e) {
+      return this.lineWidth = e;
+    }
+    this.context.setLineCap = function(e) {
+      return this.lineCap = e;
+    }
+    this.context.setFontSize = function(e) {
+      return this.font = e + "px sans-serif";
+    }
+    this.context.setFillStyle = function(e) {
+      return this.fillStyle = e;
+    }
+    this.context.setTextAlign = function(e) {
+      return this.textAlign = e;
+    }
+    this.context.setTextBaseline = function(e) {
+      return this.textBaseline = e;
+    }
+    this.context.setShadow = function(offsetX,offsetY,blur,color) {
+      this.shadowColor = color;
+      this.shadowOffsetX = offsetX;
+      this.shadowOffsetY = offsetY;
+      this.shadowBlur = blur;
+    }
+    this.context.draw = function() {}
+  }
+  //兼容NVUEsetLineDash
+  if(!this.context.setLineDash){
+    this.context.setLineDash = function(e) {}
+  }
+  opts.chartData = {};
+  this.uevent = new uChartsEvent();
+  this.scrollOption = {
+    currentOffset: 0,
+    startTouchX: 0,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  this.opts = opts;
+  this.config = config$$1;
+  drawCharts.call(this, opts.type, opts, config$$1, this.context);
+};
+
+uCharts.prototype.updateData = function() {
+  let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+  this.opts = assign({}, this.opts, data);
+  this.opts.updateData = true;
+  let scrollPosition = data.scrollPosition || 'current';
+  switch (scrollPosition) {
+    case 'current':
+      this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+      break;
+    case 'left':
+      this.opts._scrollDistance_ = 0;
+      this.scrollOption = {
+        currentOffset: 0,
+        startTouchX: 0,
+        distance: 0,
+        lastMoveTime: 0
+      };
+      break;
+    case 'right':
+      let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context), yAxisWidth = _calYAxisData.yAxisWidth;
+      this.config.yAxisWidth = yAxisWidth;
+      let offsetLeft = 0;
+      let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config), xAxisPoints = _getXAxisPoints0.xAxisPoints,
+        startX = _getXAxisPoints0.startX,
+        endX = _getXAxisPoints0.endX,
+        eachSpacing = _getXAxisPoints0.eachSpacing;
+      let totalWidth = eachSpacing * (xAxisPoints.length - 1);
+      let screenWidth = endX - startX;
+      offsetLeft = screenWidth - totalWidth;
+      this.scrollOption = {
+        currentOffset: offsetLeft,
+        startTouchX: offsetLeft,
+        distance: 0,
+        lastMoveTime: 0
+      };
+      this.opts._scrollDistance_ = offsetLeft;
+      break;
+  }
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+};
+
+uCharts.prototype.zoom = function() {
+  var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.opts.xAxis.itemCount;
+  if (this.opts.enableScroll !== true) {
+    console.log('[uCharts] 请启用滚动条后使用')
+    return;
+  }
+  //当前屏幕中间点
+  let centerPoint = Math.round(Math.abs(this.scrollOption.currentOffset) / this.opts.chartData.eachSpacing) + Math.round(this.opts.xAxis.itemCount / 2);
+  this.opts.animation = false;
+  this.opts.xAxis.itemCount = val.itemCount;
+  //重新计算x轴偏移距离
+  let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context),
+    yAxisWidth = _calYAxisData.yAxisWidth;
+  this.config.yAxisWidth = yAxisWidth;
+  let offsetLeft = 0;
+  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),
+    xAxisPoints = _getXAxisPoints0.xAxisPoints,
+    startX = _getXAxisPoints0.startX,
+    endX = _getXAxisPoints0.endX,
+    eachSpacing = _getXAxisPoints0.eachSpacing;
+  let centerLeft = eachSpacing * centerPoint;
+  let screenWidth = endX - startX;
+  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+  offsetLeft = screenWidth / 2 - centerLeft;
+  if (offsetLeft > 0) {
+    offsetLeft = 0;
+  }
+  if (offsetLeft < MaxLeft) {
+    offsetLeft = MaxLeft;
+  }
+  this.scrollOption = {
+    currentOffset: offsetLeft,
+    startTouchX: 0,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);
+  this.opts._scrollDistance_ = offsetLeft;
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+};
+
+uCharts.prototype.dobuleZoom = function(e) {
+  if (this.opts.enableScroll !== true) {
+    console.log('[uCharts] 请启用滚动条后使用')
+    return;
+  }
+  const tcs = e.changedTouches;
+  if (tcs.length < 2) {
+    return;
+  }
+  for (var i = 0; i < tcs.length; i++) {
+    tcs[i].x = tcs[i].x ? tcs[i].x : tcs[i].clientX;
+    tcs[i].y = tcs[i].y ? tcs[i].y : tcs[i].clientY;
+  }
+  const ntcs = [getTouches(tcs[0], this.opts, e),getTouches(tcs[1], this.opts, e)]; 
+  const xlength = Math.abs(ntcs[0].x - ntcs[1].x);
+  // 记录初始的两指之间的数据
+  if(!this.scrollOption.moveCount){
+    let cts0 = {changedTouches:[{x:tcs[0].x,y:this.opts.area[0] / this.opts.pix + 2}]};
+    let cts1 = {changedTouches:[{x:tcs[1].x,y:this.opts.area[0] / this.opts.pix + 2}]};
+    if(this.opts.rotate){
+      cts0 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[0].y}]};
+      cts1 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[1].y}]};
+    }
+    const moveCurrent1 = this.getCurrentDataIndex(cts0).index;
+    const moveCurrent2 = this.getCurrentDataIndex(cts1).index;
+    const moveCount = Math.abs(moveCurrent1 - moveCurrent2);
+    this.scrollOption.moveCount = moveCount;
+    this.scrollOption.moveCurrent1 = Math.min(moveCurrent1, moveCurrent2);
+    this.scrollOption.moveCurrent2 = Math.max(moveCurrent1, moveCurrent2);
+    return;
+  }
+  
+  let currentEachSpacing = xlength / this.scrollOption.moveCount;
+  let itemCount = (this.opts.width - this.opts.area[1] - this.opts.area[3]) / currentEachSpacing;
+  itemCount = itemCount <= 2 ? 2 : itemCount;
+  itemCount = itemCount >= this.opts.categories.length ? this.opts.categories.length : itemCount;
+  this.opts.animation = false;
+  this.opts.xAxis.itemCount = itemCount;
+  // 重新计算滚动条偏移距离
+  let offsetLeft = 0;
+  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),
+    xAxisPoints = _getXAxisPoints0.xAxisPoints,
+    startX = _getXAxisPoints0.startX,
+    endX = _getXAxisPoints0.endX,
+    eachSpacing = _getXAxisPoints0.eachSpacing;
+  let currentLeft = eachSpacing * this.scrollOption.moveCurrent1;
+  let screenWidth = endX - startX;
+  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+  offsetLeft = -currentLeft+Math.min(ntcs[0].x,ntcs[1].x)-this.opts.area[3]-eachSpacing;
+  if (offsetLeft > 0) {
+    offsetLeft = 0;
+  }
+  if (offsetLeft < MaxLeft) {
+    offsetLeft = MaxLeft;
+  }
+  this.scrollOption.currentOffset= offsetLeft;
+  this.scrollOption.startTouchX= 0;
+  this.scrollOption.distance=0;
+  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);
+  this.opts._scrollDistance_ = offsetLeft;
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+}
+
+uCharts.prototype.stopAnimation = function() {
+  this.animationInstance && this.animationInstance.stop();
+};
+
+uCharts.prototype.addEventListener = function(type, listener) {
+  this.uevent.addEventListener(type, listener);
+};
+
+uCharts.prototype.delEventListener = function(type) {
+  this.uevent.delEventListener(type);
+};
+
+uCharts.prototype.getCurrentDataIndex = function(e) {
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    if (this.opts.type === 'pie' || this.opts.type === 'ring') {
+      return findPieChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.pieData, this.opts);
+    } else if (this.opts.type === 'rose') {
+      return findRoseChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.pieData, this.opts);
+    } else if (this.opts.type === 'radar') {
+      return findRadarChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.radarData, this.opts.categories.length);
+    } else if (this.opts.type === 'funnel') {
+      return findFunnelChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.funnelData);
+    } else if (this.opts.type === 'map') {
+      return findMapChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts);
+    } else if (this.opts.type === 'word') {
+      return findWordChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.wordCloudData);
+    } else if (this.opts.type === 'bar') {
+      return findBarChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));
+    } else {
+      return findCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));
+    }
+  }
+  return -1;
+};
+
+uCharts.prototype.getLegendDataIndex = function(e) {
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    return findLegendIndex({
+      x: _touches$.x,
+      y: _touches$.y
+    }, this.opts.chartData.legendData);
+  }
+  return -1;
+};
+
+uCharts.prototype.touchLegend = function(e) {
+  var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    var _touches$ = getTouches(touches, this.opts, e);
+    var index = this.getLegendDataIndex(e);
+    if (index >= 0) {
+      if (this.opts.type == 'candle') {
+        this.opts.seriesMA[index].show = !this.opts.seriesMA[index].show;
+      } else {
+        this.opts.series[index].show = !this.opts.series[index].show;
+      }
+      this.opts.animation = option.animation ? true : false;
+      this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+      drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+    }
+  }
+
+};
+
+uCharts.prototype.showToolTip = function(e) {
+  var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (!touches) {
+    console.log("[uCharts] 未获取到event坐标信息");
+  }
+  var _touches$ = getTouches(touches, this.opts, e);
+  var currentOffset = this.scrollOption.currentOffset;
+  var opts = assign({}, this.opts, {
+    _scrollDistance_: currentOffset,
+    animation: false
+  });
+  if (this.opts.type === 'line' || this.opts.type === 'area' || this.opts.type === 'column' || this.opts.type === 'scatter' || this.opts.type === 'bubble') {
+    var current = this.getCurrentDataIndex(e);
+    var index = option.index == undefined ? current.index : option.index;
+    if (index > -1 || index.length>0) {
+      var seriesData = getSeriesDataItem(this.opts.series, index, current.group);
+      if (seriesData.length !== 0) {
+        var _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList !== undefined ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index,
+          group: current.group
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'mount') {
+    var index = option.index == undefined ? this.getCurrentDataIndex(e).index : option.index;
+    if (index > -1) {
+      var opts = assign({}, this.opts, {animation: false});
+      var seriesData = assign({}, opts._series_[index]);
+      var textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      var offset = {
+        x: opts.chartData.calPoints[index].x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'bar') {
+    var current = this.getCurrentDataIndex(e);
+    var index = option.index == undefined ? current.index : option.index;
+    if (index > -1 || index.length>0) {
+      var seriesData = getSeriesDataItem(this.opts.series, index, current.group);
+      if (seriesData.length !== 0) {
+        var _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.x = _touches$.x;
+        opts.tooltip = {
+          textList: option.textList !== undefined ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'mix') {
+    var current = this.getCurrentDataIndex(e);
+    var index = option.index == undefined ? current.index : option.index;
+    if (index > -1) {
+      var currentOffset = this.scrollOption.currentOffset;
+      var opts = assign({}, this.opts, {
+        _scrollDistance_: currentOffset,
+        animation: false
+      });
+      var seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        var _getMixToolTipData = getMixToolTipData(seriesData, this.opts, index, this.opts.categories, option),
+          textList = _getMixToolTipData.textList,
+          offset = _getMixToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'candle') {
+    var current = this.getCurrentDataIndex(e);
+    var index = option.index == undefined ? current.index : option.index;
+    if (index > -1) {
+      var currentOffset = this.scrollOption.currentOffset;
+      var opts = assign({}, this.opts, {
+        _scrollDistance_: currentOffset,
+        animation: false
+      });
+      var seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        var _getToolTipData = getCandleToolTipData(this.opts.series[0].data, seriesData, this.opts, index, this.opts.categories, this.opts.extra.candle, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'pie' || this.opts.type === 'ring' || this.opts.type === 'rose' || this.opts.type === 'funnel') {
+    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      var opts = assign({}, this.opts, {animation: false});
+      var seriesData = assign({}, opts._series_[index]);
+      var textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      var offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'map') {
+    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      var opts = assign({}, this.opts, {animation: false});
+      var seriesData = assign({}, this.opts.series[index]);
+      seriesData.name = seriesData.properties.name
+      var textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      var offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    opts.updateData = false;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'word') {
+    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      var opts = assign({}, this.opts, {animation: false});
+      var seriesData = assign({}, this.opts.series[index]);
+      var textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      var offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    opts.updateData = false;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'radar') {
+    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      var opts = assign({}, this.opts, {animation: false});
+      var seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        var textList = seriesData.map((item) => {
+          return {
+            text: option.formatter ? option.formatter(item, this.opts.categories[index], index, this.opts) : item.name + ': ' + item.data,
+            color: item.color,
+            legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : this.opts.extra.tooltip.legendShape
+          };
+        });
+        var offset = {
+          x: _touches$.x,
+          y: _touches$.y
+        };
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+};
+
+uCharts.prototype.translate = function(distance) {
+  this.scrollOption = {
+    currentOffset: distance,
+    startTouchX: distance,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  let opts = assign({}, this.opts, {
+    _scrollDistance_: distance,
+    animation: false
+  });
+  drawCharts.call(this, this.opts.type, opts, this.config, this.context);
+};
+
+uCharts.prototype.scrollStart = function(e) {
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  var _touches$ = getTouches(touches, this.opts, e);
+  if (touches && this.opts.enableScroll === true) {
+    this.scrollOption.startTouchX = _touches$.x;
+  }
+};
+
+uCharts.prototype.scroll = function(e) {
+  if (this.scrollOption.lastMoveTime === 0) {
+    this.scrollOption.lastMoveTime = Date.now();
+  }
+  let Limit = this.opts.touchMoveLimit || 60;
+  let currMoveTime = Date.now();
+  let duration = currMoveTime - this.scrollOption.lastMoveTime;
+  if (duration < Math.floor(1000 / Limit)) return;
+  if (this.scrollOption.startTouchX == 0) return;
+  this.scrollOption.lastMoveTime = currMoveTime;
+  var touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches && this.opts.enableScroll === true) {
+    var _touches$ = getTouches(touches, this.opts, e);
+    var _distance;
+    _distance = _touches$.x - this.scrollOption.startTouchX;
+    var currentOffset = this.scrollOption.currentOffset;
+    var validDistance = calValidDistance(this, currentOffset + _distance, this.opts.chartData, this.config, this.opts);
+    this.scrollOption.distance = _distance = validDistance - currentOffset;
+    var opts = assign({}, this.opts, {
+      _scrollDistance_: currentOffset + _distance,
+      animation: false
+    });
+		this.opts = opts;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+    return currentOffset + _distance;
+  }
+};
+
+uCharts.prototype.scrollEnd = function(e) {
+  if (this.opts.enableScroll === true) {
+    var _scrollOption = this.scrollOption,
+      currentOffset = _scrollOption.currentOffset,
+      distance = _scrollOption.distance;
+    this.scrollOption.currentOffset = currentOffset + distance;
+    this.scrollOption.distance = 0;
+    this.scrollOption.moveCount = 0;
+  }
+};
+
+export default uCharts;

+ 7 - 0
mini-ui-packages/mini-charts/src/lib/u-charts-original.ts

@@ -0,0 +1,7 @@
+/**
+ * u-charts-original.js 类型导出
+ *
+ * 重新导出 u-charts-original.d.ts 中定义的类型
+ */
+
+export type { ChartsConfig, TouchEvent } from '../types/u-charts-original';

+ 7680 - 0
mini-ui-packages/mini-charts/src/lib/u-charts.ts.backup

@@ -0,0 +1,7680 @@
+// Type definitions will be added in story 016.002
+// @ts-nocheck - Using any types as temporary measure
+// For now, using any types as temporary measure
+
+/*
+ * uCharts (R)
+ * 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360/快手)、Vue、Taro等支持canvas的框架平台
+ * Copyright (C) 2018-2022 QIUN (R) 秋云 https://www.ucharts.cn All rights reserved.
+ * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+ * 复制使用请保留本段注释,感谢支持开源!
+ * 
+ * uCharts (R) 官方网站
+ * https://www.uCharts.cn
+ * 
+ * 开源地址:
+ * https://gitee.com/uCharts/uCharts
+ * 
+ * uni-app插件市场地址:
+ * http://ext.dcloud.net.cn/plugin?id=271
+ * 
+ */
+
+export const config = {
+  version: 'v2.5.0-20230101',
+  yAxisWidth: 15,
+  xAxisHeight: 22,
+  padding: [10, 10, 10, 10],
+  rotate: false,
+  fontSize: 13,
+  fontColor: '#666666',
+  dataPointShape: ['circle', 'circle', 'circle', 'circle'],
+  color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],
+  linearColor: ['#0EE2F8', '#2BDCA8', '#FA7D8D', '#EB88E2', '#2AE3A0', '#0EE2F8', '#EB88E2', '#6773E3', '#F78A85'],
+  pieChartLinePadding: 15,
+  pieChartTextPadding: 5,
+  titleFontSize: 20,
+  subtitleFontSize: 15,
+  radarLabelTextMargin: 13,
+};
+
+export const assign = function(target, ...varArgs) {
+  if (target == null) {
+    throw new TypeError('[uCharts] Cannot convert undefined or null to object');
+  }
+  if (!varArgs || varArgs.length <= 0) {
+    return target;
+  }
+  // 深度合并对象
+  function deepAssign(obj1, obj2) {
+    for (let key in obj2) {
+      obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" ?
+        deepAssign(obj1[key], obj2[key]) : obj1[key] = obj2[key];
+    }
+    return obj1;
+  }
+  varArgs.forEach(val => {
+    target = deepAssign(target, val);
+  });
+  return target;
+};
+
+export const util = {
+  toFixed: function toFixed(num, limit) {
+    limit = limit || 2;
+    if (this.isFloat(num)) {
+      num = num.toFixed(limit);
+    }
+    return num;
+  },
+  isFloat: function isFloat(num) {
+    return num % 1 !== 0;
+  },
+  approximatelyEqual: function approximatelyEqual(num1, num2) {
+    return Math.abs(num1 - num2) < 1e-10;
+  },
+  isSameSign: function isSameSign(num1, num2) {
+    return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2;
+  },
+  isSameXCoordinateArea: function isSameXCoordinateArea(p1, p2) {
+    return this.isSameSign(p1.x, p2.x);
+  },
+  isCollision: function isCollision(obj1, obj2) {
+    obj1.end = {};
+    obj1.end.x = obj1.start.x + obj1.width;
+    obj1.end.y = obj1.start.y - obj1.height;
+    obj2.end = {};
+    obj2.end.x = obj2.start.x + obj2.width;
+    obj2.end.y = obj2.start.y - obj2.height;
+    let flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y;
+    return !flag;
+  }
+};
+
+//兼容H5点击事件
+export function getH5Offset(e) {
+  e.mp = {
+    changedTouches: []
+  };
+  e.mp.changedTouches.push({
+    x: e.offsetX,
+    y: e.offsetY
+  });
+  return e;
+}
+
+// hex 转 rgba
+export function hexToRgb(hexValue, opc) {
+  let rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+  let hex = hexValue.replace(rgx, function(m, r, g, b) {
+    return r + r + g + g + b + b;
+  });
+  let rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+  let r = parseInt(rgb[1], 16);
+  let g = parseInt(rgb[2], 16);
+  let b = parseInt(rgb[3], 16);
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + opc + ')';
+}
+
+export function findRange(num, type, limit) {
+  if (isNaN(num)) {
+    throw new Error('[uCharts] series数据需为Number格式');
+  }
+  limit = limit || 10;
+  type = type ? type : 'upper';
+  let multiple = 1;
+  while (limit < 1) {
+    limit *= 10;
+    multiple *= 10;
+  }
+  if (type === 'upper') {
+    num = Math.ceil(num * multiple);
+  } else {
+    num = Math.floor(num * multiple);
+  }
+  while (num % limit !== 0) {
+    if (type === 'upper') {
+      if (num == num + 1) { //修复数据值过大num++无效的bug by 向日葵 @xrk_jy
+        break;
+      }
+      num++;
+    } else {
+      num--;
+    }
+  }
+  return num / multiple;
+}
+
+export function calCandleMA(dayArr, nameArr, colorArr, kdata) {
+  let seriesTemp = [];
+  for (let k = 0; k < dayArr.length; k++) {
+    let seriesItem = {
+      data: [],
+      name: nameArr[k],
+      color: colorArr[k]
+    };
+    for (let i = 0, len = kdata.length; i < len; i++) {
+      if (i < dayArr[k]) {
+        seriesItem.data.push(null);
+        continue;
+      }
+      let sum = 0;
+      for (let j = 0; j < dayArr[k]; j++) {
+        sum += kdata[i - j][1];
+      }
+      seriesItem.data.push(+(sum / dayArr[k]).toFixed(3));
+    }
+    seriesTemp.push(seriesItem);
+  }
+  return seriesTemp;
+}
+
+export function calValidDistance(self, distance, chartData, config, opts) {
+  let dataChartAreaWidth = opts.width - opts.area[1] - opts.area[3];
+  let dataChartWidth = chartData.eachSpacing * (opts.chartData.xAxisData.xAxisPoints.length - 1);
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    dataChartWidth += (opts.extra.mount.widthRatio - 1)*chartData.eachSpacing;
+  }
+  let validDistance = distance;
+  if (distance >= 0) {
+    validDistance = 0;
+    self.uevent.trigger('scrollLeft');
+    self.scrollOption.position = 'left'
+    opts.xAxis.scrollPosition = 'left';
+  } else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) {
+    validDistance = dataChartAreaWidth - dataChartWidth;
+    self.uevent.trigger('scrollRight');
+    self.scrollOption.position = 'right'
+    opts.xAxis.scrollPosition = 'right';
+  } else {
+    self.scrollOption.position = distance
+    opts.xAxis.scrollPosition = distance;
+  }
+  return validDistance;
+}
+
+export function isInAngleRange(angle, startAngle, endAngle) {
+  function adjust(angle) {
+    while (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+    while (angle > 2 * Math.PI) {
+      angle -= 2 * Math.PI;
+    }
+    return angle;
+  }
+  angle = adjust(angle);
+  startAngle = adjust(startAngle);
+  endAngle = adjust(endAngle);
+  if (startAngle > endAngle) {
+    endAngle += 2 * Math.PI;
+    if (angle < startAngle) {
+      angle += 2 * Math.PI;
+    }
+  }
+  return angle >= startAngle && angle <= endAngle;
+}
+
+export function createCurveControlPoints(points, i) {
+  function isNotMiddlePoint(points, i) {
+    if (points[i - 1] && points[i + 1]) {
+      return points[i].y >= Math.max(points[i - 1].y, points[i + 1].y) || points[i].y <= Math.min(points[i - 1].y,
+        points[i + 1].y);
+    } else {
+      return false;
+    }
+  }
+  function isNotMiddlePointX(points, i) {
+    if (points[i - 1] && points[i + 1]) {
+      return points[i].x >= Math.max(points[i - 1].x, points[i + 1].x) || points[i].x <= Math.min(points[i - 1].x,
+        points[i + 1].x);
+    } else {
+      return false;
+    }
+  }
+  let a = 0.2;
+  let b = 0.2;
+  let pAx = null;
+  let pAy = null;
+  let pBx = null;
+  let pBy = null;
+  if (i < 1) {
+    pAx = points[0].x + (points[1].x - points[0].x) * a;
+    pAy = points[0].y + (points[1].y - points[0].y) * a;
+  } else {
+    pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a;
+    pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a;
+  }
+
+  if (i > points.length - 3) {
+    let last = points.length - 1;
+    pBx = points[last].x - (points[last].x - points[last - 1].x) * b;
+    pBy = points[last].y - (points[last].y - points[last - 1].y) * b;
+  } else {
+    pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b;
+    pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b;
+  }
+  if (isNotMiddlePoint(points, i + 1)) {
+    pBy = points[i + 1].y;
+  }
+  if (isNotMiddlePoint(points, i)) {
+    pAy = points[i].y;
+  }
+  if (isNotMiddlePointX(points, i + 1)) {
+    pBx = points[i + 1].x;
+  }
+  if (isNotMiddlePointX(points, i)) {
+    pAx = points[i].x;
+  }
+  if (pAy >= Math.max(points[i].y, points[i + 1].y) || pAy <= Math.min(points[i].y, points[i + 1].y)) {
+    pAy = points[i].y;
+  }
+  if (pBy >= Math.max(points[i].y, points[i + 1].y) || pBy <= Math.min(points[i].y, points[i + 1].y)) {
+    pBy = points[i + 1].y;
+  }
+  if (pAx >= Math.max(points[i].x, points[i + 1].x) || pAx <= Math.min(points[i].x, points[i + 1].x)) {
+    pAx = points[i].x;
+  }
+  if (pBx >= Math.max(points[i].x, points[i + 1].x) || pBx <= Math.min(points[i].x, points[i + 1].x)) {
+    pBx = points[i + 1].x;
+  }
+  return {
+    ctrA: {
+      x: pAx,
+      y: pAy
+    },
+    ctrB: {
+      x: pBx,
+      y: pBy
+    }
+  };
+}
+
+
+export function convertCoordinateOrigin(x, y, center) {
+  return {
+    x: center.x + x,
+    y: center.y - y
+  };
+}
+
+export function avoidCollision(obj, target) {
+  if (target) {
+    // is collision test
+    while (util.isCollision(obj, target)) {
+      if (obj.start.x > 0) {
+        obj.start.y--;
+      } else if (obj.start.x < 0) {
+        obj.start.y++;
+      } else {
+        if (obj.start.y > 0) {
+          obj.start.y++;
+        } else {
+          obj.start.y--;
+        }
+      }
+    }
+  }
+  return obj;
+}
+
+export function fixPieSeries(series, opts, config){
+  let pieSeriesArr = [];
+  if(series.length>0 && series[0].data.constructor.toString().indexOf('Array') > -1){
+    opts._pieSeries_ = series;
+    let oldseries = series[0].data;
+    for (let i = 0; i < oldseries.length; i++) {
+      oldseries[i].formatter = series[0].formatter;
+      oldseries[i].data = oldseries[i].value;
+      pieSeriesArr.push(oldseries[i]);
+    }
+    opts.series = pieSeriesArr;
+  }else{
+    pieSeriesArr = series;
+  }
+  return pieSeriesArr;
+}
+
+export function fillSeries(series, opts, config) {
+  let index = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    if (!item.color) {
+      item.color = config.color[index];
+      index = (index + 1) % config.color.length;
+    }
+    if (!item.linearIndex) {
+      item.linearIndex = i;
+    }
+    if (!item.index) {
+      item.index = 0;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (typeof item.show == "undefined") {
+      item.show = true;
+    }
+    if (!item.type) {
+      item.type = opts.type;
+    }
+    if (!item.pointShape) {
+      item.pointShape = "circle";
+    }
+    if (!item.legendShape) {
+      switch (item.type) {
+        case 'line':
+          item.legendShape = "line";
+          break;
+        case 'column':
+        case 'bar':
+          item.legendShape = "rect";
+          break;
+        case 'area':
+        case 'mount':
+          item.legendShape = "triangle";
+          break;
+        default:
+          item.legendShape = "circle";
+      }
+    }
+  }
+  return series;
+}
+
+export function fillCustomColor(linearType, customColor, series, config) {
+  let newcolor = customColor || [];
+  if (linearType == 'custom' && newcolor.length == 0 ) {
+    newcolor = config.linearColor;
+  }
+  if (linearType == 'custom' && newcolor.length < series.length) {
+    let chazhi = series.length - newcolor.length;
+    for (let i = 0; i < chazhi; i++) {
+      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);
+    }
+  }
+  return newcolor;
+}
+
+export function getDataRange(minData, maxData) {
+  let limit = 0;
+  let range = maxData - minData;
+  if (range >= 10000) {
+    limit = 1000;
+  } else if (range >= 1000) {
+    limit = 100;
+  } else if (range >= 100) {
+    limit = 10;
+  } else if (range >= 10) {
+    limit = 5;
+  } else if (range >= 1) {
+    limit = 1;
+  } else if (range >= 0.1) {
+    limit = 0.1;
+  } else if (range >= 0.01) {
+    limit = 0.01;
+  } else if (range >= 0.001) {
+    limit = 0.001;
+  } else if (range >= 0.0001) {
+    limit = 0.0001;
+  } else if (range >= 0.00001) {
+    limit = 0.00001;
+  } else {
+    limit = 0.000001;
+  }
+  return {
+    minRange: findRange(minData, 'lower', limit),
+    maxRange: findRange(maxData, 'upper', limit)
+  };
+}
+
+export function measureText(text, fontSize, context) {
+  let width = 0;
+  text = String(text);
+  // #ifdef MP-ALIPAY || MP-BAIDU || APP-NVUE
+  context = false;
+  // #endif
+  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {
+    context.setFontSize(fontSize);
+    return context.measureText(text).width;
+  } else {
+    let text = text.split('');
+    for (let i = 0; i < text.length; i++) {
+      let item = text[i];
+      if (/[a-zA-Z]/.test(item)) {
+        width += 7;
+      } else if (/[0-9]/.test(item)) {
+        width += 5.5;
+      } else if (/\./.test(item)) {
+        width += 2.7;
+      } else if (/-/.test(item)) {
+        width += 3.25;
+      } else if (/:/.test(item)) {
+        width += 2.5;
+      } else if (/[\u4e00-\u9fa5]/.test(item)) {
+        width += 10;
+      } else if (/\(|\)/.test(item)) {
+        width += 3.73;
+      } else if (/\s/.test(item)) {
+        width += 2.5;
+      } else if (/%/.test(item)) {
+        width += 8;
+      } else {
+        width += 10;
+      }
+    }
+    return width * fontSize / 10;
+  }
+}
+
+export function dataCombine(series) {
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data);
+  }, []);
+}
+
+export function dataCombineStack(series, len) {
+  let sum = new Array(len);
+  for (let j = 0; j < sum.length; j++) {
+    sum[j] = 0;
+  }
+  for (let i = 0; i < series.length; i++) {
+    for (let j = 0; j < sum.length; j++) {
+      sum[j] += series[i].data[j];
+    }
+  }
+  return series.reduce(function(a, b) {
+    return (a.data ? a.data : a).concat(b.data).concat(sum);
+  }, []);
+}
+
+export function getTouches(touches, opts, e) {
+  let x, y;
+  if (touches.clientX) {
+    if (opts.rotate) {
+      y = opts.height - touches.clientX * opts.pix;
+      x = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    } else {
+      x = touches.clientX * opts.pix;
+      y = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    }
+  } else {
+    if (opts.rotate) {
+      y = opts.height - touches.x * opts.pix;
+      x = touches.y * opts.pix;
+    } else {
+      x = touches.x * opts.pix;
+      y = touches.y * opts.pix;
+    }
+  }
+  return {
+    x: x,
+    y: y
+  }
+}
+
+function getSeriesDataItem(series, index, group) {
+  let data = [];
+  let newSeries = [];
+  let indexIsArr = index.constructor.toString().indexOf('Array') > -1;
+  if(indexIsArr){
+    let tempSeries = filterSeries(series);
+    for (let i = 0; i < group.length; i++) {
+      newSeries.push(tempSeries[group[i]]);
+    }
+  }else{
+    newSeries = series;
+  };
+  for (let i = 0; i < newSeries.length; i++) {
+    let item = newSeries[i];
+    let tmpindex = -1;
+    if(indexIsArr){
+      tmpindex = index[i];
+    }else{
+      tmpindex = index;
+    }
+    if (item.data[tmpindex] !== null && typeof item.data[tmpindex] !== 'undefined' && item.show) {
+      let seriesItem = {};
+      seriesItem.color = item.color;
+      seriesItem.type = item.type;
+      seriesItem.style = item.style;
+      seriesItem.pointShape = item.pointShape;
+      seriesItem.disableLegend = item.disableLegend;
+      seriesItem.legendShape = item.legendShape;
+      seriesItem.name = item.name;
+      seriesItem.show = item.show;
+      seriesItem.data = item.formatter ? item.formatter(item.data[tmpindex]) : item.data[tmpindex];
+      data.push(seriesItem);
+    }
+  }
+  return data;
+}
+
+function getMaxTextListLength(list, fontSize, context) {
+  let lengthList = list.map(function(item) {
+    return measureText(item, fontSize, context);
+  });
+  return Math.max.apply(null, lengthList);
+}
+
+function getRadarCoordinateSeries(length) {
+  let eachAngle = 2 * Math.PI / length;
+  let CoordinateSeries = [];
+  for (let i = 0; i < length; i++) {
+    CoordinateSeries.push(eachAngle * i);
+  }
+  return CoordinateSeries.map(function(item) {
+    return -1 * item + Math.PI / 2;
+  });
+}
+
+function getToolTipData(seriesData, opts, index, group, categories) {
+  let option = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
+  let calPoints = opts.chartData.calPoints?opts.chartData.calPoints:[];
+  let points = {};
+  if(group.length > 0){
+    let filterPoints = [];
+    for (let i = 0; i < group.length; i++) {
+      filterPoints.push(calPoints[group[i]])
+    }
+    points = filterPoints[0][index[0]];
+  }else{
+    for (let i = 0; i < calPoints.length; i++) {
+      if(calPoints[i][index]){
+        points = calPoints[i][index];
+        break;
+      }
+    }
+  };
+  let textList = seriesData.map(function(item) {
+    let titleText = null;
+    if (opts.categories && opts.categories.length>0) {
+      titleText = categories[index];
+    };
+    return {
+      text: option.formatter ? option.formatter(item, titleText, index, opts) : item.name + ': ' + item.data,
+      color: item.color,
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+  });
+  let offset = {
+    x: Math.round(points.x),
+    y: Math.round(points.y)
+  };
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function getMixToolTipData(seriesData, opts, index, categories) {
+  let option = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
+  let points = opts.chartData.xAxisPoints[index] + opts.chartData.eachSpacing / 2;
+  let textList = seriesData.map(function(item) {
+    return {
+      text: option.formatter ? option.formatter(item, categories[index], index, opts) : item.name + ': ' + item.data,
+      color: item.color,
+      disableLegend: item.disableLegend ? true : false,
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+  });
+  textList = textList.filter(function(item) {
+    if (item.disableLegend !== true) {
+      return item;
+    }
+  });
+  let offset = {
+    x: Math.round(points),
+    y: 0
+  };
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function getCandleToolTipData(series, seriesData, opts, index, categories, extra) {
+  let option = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};
+  let calPoints = opts.chartData.calPoints;
+  let upColor = extra.color.upFill;
+  let downColor = extra.color.downFill;
+  //颜色顺序为开盘,收盘,最低,最高
+  let color = [upColor, upColor, downColor, upColor];
+  let textList = [];
+  seriesData.map(function(item) {
+    if (index == 0) {
+      if (item.data[1] - item.data[0] < 0) {
+        color[1] = downColor;
+      } else {
+        color[1] = upColor;
+      }
+    } else {
+      if (item.data[0] < series[index - 1][1]) {
+        color[0] = downColor;
+      }
+      if (item.data[1] < item.data[0]) {
+        color[1] = downColor;
+      }
+      if (item.data[2] > series[index - 1][1]) {
+        color[2] = upColor;
+      }
+      if (item.data[3] < series[index - 1][1]) {
+        color[3] = downColor;
+      }
+    }
+    let text1 = {
+      text: '开盘:' + item.data[0],
+      color: color[0],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text2 = {
+      text: '收盘:' + item.data[1],
+      color: color[1],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text3 = {
+      text: '最低:' + item.data[2],
+      color: color[2],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    let text4 = {
+      text: '最高:' + item.data[3],
+      color: color[3],
+      legendShape: opts.extra.tooltip.legendShape == 'auto'? item.legendShape : opts.extra.tooltip.legendShape
+    };
+    textList.push(text1, text2, text3, text4);
+  });
+  let validCalPoints = [];
+  let offset = {
+    x: 0,
+    y: 0
+  };
+  for (let i = 0; i < calPoints.length; i++) {
+    let points = calPoints[i];
+    if (typeof points[index] !== 'undefined' && points[index] !== null) {
+      validCalPoints.push(points[index]);
+    }
+  }
+  offset.x = Math.round(validCalPoints[0][0].x);
+  return {
+    textList: textList,
+    offset: offset
+  };
+}
+
+function filterSeries(series) {
+  let tempSeries = [];
+  for (let i = 0; i < series.length; i++) {
+    if (series[i].show == true) {
+      tempSeries.push(series[i])
+    }
+  }
+  return tempSeries;
+}
+
+function findCurrentIndex(currentPoints, calPoints, opts, config) {
+  let offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
+  let current={ index:-1, group:[] };
+  let spacing = opts.chartData.eachSpacing / 2;
+  let xAxisPoints = [];
+  if (calPoints && calPoints.length > 0) {
+    if (!opts.categories) {
+      spacing = 0;
+    }else{
+      for (let i = 1; i < opts.chartData.xAxisPoints.length; i++) {
+        xAxisPoints.push(opts.chartData.xAxisPoints[i] - spacing);
+      }
+      if ((opts.type == 'line' || opts.type == 'area') && opts.xAxis.boundaryGap == 'justify') {
+        xAxisPoints = opts.chartData.xAxisPoints;
+      }
+    }
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      if (!opts.categories) {
+        let timePoints = Array(calPoints.length);
+        for (let i = 0; i < calPoints.length; i++) {
+          timePoints[i] = Array(calPoints[i].length)
+          for (let j = 0; j < calPoints[i].length; j++) {
+            timePoints[i][j] = (Math.abs(calPoints[i][j].x - currentPoints.x));
+          }
+        };
+        let pointValue =  Array(timePoints.length);
+        let pointIndex =  Array(timePoints.length);
+        for (let i = 0; i < timePoints.length; i++) {
+          pointValue[i] = Math.min.apply(null, timePoints[i]);
+          pointIndex[i] = timePoints[i].indexOf(pointValue[i]);
+        }
+        let minValue = Math.min.apply(null, pointValue);
+        current.index = [];
+        for (let i = 0; i < pointValue.length; i++) {
+          if(pointValue[i] == minValue){
+            current.group.push(i);
+            current.index.push(pointIndex[i]);
+          }
+        };
+      }else{
+        xAxisPoints.forEach(function(item, index) {
+          if (currentPoints.x + offset + spacing > item) {
+            current.index = index;
+          }
+        });
+      }
+    }
+  }
+  return current;
+}
+
+function findBarChartCurrentIndex(currentPoints, calPoints, opts, config) {
+  let offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
+  let current={ index:-1, group:[] };
+  let spacing = opts.chartData.eachSpacing / 2;
+  let yAxisPoints = opts.chartData.yAxisPoints;
+  if (calPoints && calPoints.length > 0) {
+    if (isInExactChartArea(currentPoints, opts, config)) {
+      yAxisPoints.forEach(function(item, index) {
+        if (currentPoints.y + offset + spacing > item) {
+          current.index = index;
+        }
+      });
+    }
+  }
+  return current;
+}
+
+function findLegendIndex(currentPoints, legendData, opts) {
+  let currentIndex = -1;
+  let gap = 0;
+  if (isInExactLegendArea(currentPoints, legendData.area)) {
+    let points = legendData.points;
+    let index = -1;
+    for (let i = 0, len = points.length; i < len; i++) {
+      let item = points[i];
+      for (let j = 0; j < item.length; j++) {
+        index += 1;
+        let area = item[j]['area'];
+        if (area && currentPoints.x > area[0] - gap && currentPoints.x < area[2] + gap && currentPoints.y > area[1] - gap && currentPoints.y < area[3] + gap) {
+          currentIndex = index;
+          break;
+        }
+      }
+    }
+    return currentIndex;
+  }
+  return currentIndex;
+}
+
+function isInExactLegendArea(currentPoints, area) {
+  return currentPoints.x > area.start.x && currentPoints.x < area.end.x && currentPoints.y > area.start.y && currentPoints.y < area.end.y;
+}
+
+function isInExactChartArea(currentPoints, opts, config) {
+  return currentPoints.x <= opts.width - opts.area[1] + 10 && currentPoints.x >= opts.area[3] - 10 && currentPoints.y >= opts.area[0] && currentPoints.y <= opts.height - opts.area[2];
+}
+
+function findRadarChartCurrentIndex(currentPoints, radarData, count) {
+  let eachAngleArea = 2 * Math.PI / count;
+  let currentIndex = -1;
+  if (isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)) {
+    let fixAngle = function fixAngle(angle) {
+      if (angle < 0) {
+        angle += 2 * Math.PI;
+      }
+      if (angle > 2 * Math.PI) {
+        angle -= 2 * Math.PI;
+      }
+      return angle;
+    };
+    let angle = Math.atan2(radarData.center.y - currentPoints.y, currentPoints.x - radarData.center.x);
+    angle = -1 * angle;
+    if (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+    let angleList = radarData.angleList.map(function(item) {
+      item = fixAngle(-1 * item);
+      return item;
+    });
+    angleList.forEach(function(item, index) {
+      let rangeStart = fixAngle(item - eachAngleArea / 2);
+      let rangeEnd = fixAngle(item + eachAngleArea / 2);
+      if (rangeEnd < rangeStart) {
+        rangeEnd += 2 * Math.PI;
+      }
+      if (angle >= rangeStart && angle <= rangeEnd || angle + 2 * Math.PI >= rangeStart && angle + 2 * Math.PI <= rangeEnd) {
+        currentIndex = index;
+      }
+    });
+  }
+  return currentIndex;
+}
+
+function findFunnelChartCurrentIndex(currentPoints, funnelData) {
+  let currentIndex = -1;
+  for (let i = 0, len = funnelData.series.length; i < len; i++) {
+    let item = funnelData.series[i];
+    if (currentPoints.x > item.funnelArea[0] && currentPoints.x < item.funnelArea[2] && currentPoints.y > item.funnelArea[1] && currentPoints.y < item.funnelArea[3]) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findWordChartCurrentIndex(currentPoints, wordData) {
+  let currentIndex = -1;
+  for (let i = 0, len = wordData.length; i < len; i++) {
+    let item = wordData[i];
+    if (currentPoints.x > item.area[0] && currentPoints.x < item.area[2] && currentPoints.y > item.area[1] && currentPoints.y < item.area[3]) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findMapChartCurrentIndex(currentPoints, opts) {
+  let currentIndex = -1;
+  let cData = opts.chartData.mapData;
+  let data = opts.series;
+  let tmp = pointToCoordinate(currentPoints.y, currentPoints.x, cData.bounds, cData.scale, cData.xoffset, cData.yoffset);
+  let poi = [tmp.x, tmp.y];
+  for (let i = 0, len = data.length; i < len; i++) {
+    let item = data[i].geometry.coordinates;
+    if (isPoiWithinPoly(poi, item, opts.chartData.mapData.mercator)) {
+      currentIndex = i;
+      break;
+    }
+  }
+  return currentIndex;
+}
+
+function findRoseChartCurrentIndex(currentPoints, pieData, opts) {
+  let currentIndex = -1;
+  let series = getRoseDataPoints(opts._series_, opts.extra.rose.type, pieData.radius, pieData.radius);
+  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {
+    let angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);
+    angle = -angle;
+    if(opts.extra.rose && opts.extra.rose.offsetAngle){
+      angle = angle - opts.extra.rose.offsetAngle * Math.PI / 180;
+    }
+    for (let i = 0, len = series.length; i < len; i++) {
+      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._rose_proportion_ * 2 * Math.PI)) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}
+
+function findPieChartCurrentIndex(currentPoints, pieData, opts) {
+  let currentIndex = -1;
+  let series = getPieDataPoints(pieData.series);
+  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {
+    let angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);
+    angle = -angle;
+    if(opts.extra.pie && opts.extra.pie.offsetAngle){
+      angle = angle - opts.extra.pie.offsetAngle * Math.PI / 180;
+    }
+    if(opts.extra.ring && opts.extra.ring.offsetAngle){
+      angle = angle - opts.extra.ring.offsetAngle * Math.PI / 180;
+    }
+    for (let i = 0, len = series.length; i < len; i++) {
+      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._proportion_ * 2 * Math.PI)) {
+        currentIndex = i;
+        break;
+      }
+    }
+  }
+  return currentIndex;
+}
+
+function isInExactPieChartArea(currentPoints, center, radius) {
+  return Math.pow(currentPoints.x - center.x, 2) + Math.pow(currentPoints.y - center.y, 2) <= Math.pow(radius, 2);
+}
+
+
+function splitPoints(points,eachSeries) {
+  let newPoints = [];
+  let items = [];
+  points.forEach(function(item, index) {
+    if(eachSeries.connectNulls){
+      if (item !== null) {
+        items.push(item);
+      }
+    }else{
+      if (item !== null) {
+        items.push(item);
+      } else {
+        if (items.length) {
+          newPoints.push(items);
+        }
+        items = [];
+      }
+    }
+    
+  });
+  if (items.length) {
+    newPoints.push(items);
+  }
+  return newPoints;
+}
+
+
+function calLegendData(series, opts, config, chartData, context) {
+  let legendData = {
+    area: {
+      start: {
+        x: 0,
+        y: 0
+      },
+      end: {
+        x: 0,
+        y: 0
+      },
+      width: 0,
+      height: 0,
+      wholeWidth: 0,
+      wholeHeight: 0
+    },
+    points: [],
+    widthArr: [],
+    heightArr: []
+  };
+  if (opts.legend.show === false) {
+    chartData.legendData = legendData;
+    return legendData;
+  }
+  let padding = opts.legend.padding * opts.pix;
+  let margin = opts.legend.margin * opts.pix;
+  let fontSize = opts.legend.fontSize ? opts.legend.fontSize * opts.pix : config.fontSize;
+  let shapeWidth = 15 * opts.pix;
+  let shapeRight = 5 * opts.pix;
+  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);
+  if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+    let legendList = [];
+    let widthCount = 0;
+    let widthCountArr = [];
+    let currentRow = [];
+    for (let i = 0; i < series.length; i++) {
+      let item = series[i];
+      const legendText = item.legendText ? item.legendText : item.name;
+      let itemWidth = shapeWidth + shapeRight + measureText(legendText || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;
+      if (widthCount + itemWidth > opts.width - opts.area[1] - opts.area[3]) {
+        legendList.push(currentRow);
+        widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+        widthCount = itemWidth;
+        currentRow = [item];
+      } else {
+        widthCount += itemWidth;
+        currentRow.push(item);
+      }
+    }
+    if (currentRow.length) {
+      legendList.push(currentRow);
+      widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);
+      legendData.widthArr = widthCountArr;
+      let legendWidth = Math.max.apply(null, widthCountArr);
+      switch (opts.legend.float) {
+        case 'left':
+          legendData.area.start.x = opts.area[3];
+          legendData.area.end.x = opts.area[3] + legendWidth + 2 * padding;
+          break;
+        case 'right':
+          legendData.area.start.x = opts.width - opts.area[1] - legendWidth - 2 * padding;
+          legendData.area.end.x = opts.width - opts.area[1];
+          break;
+        default:
+          legendData.area.start.x = (opts.width - legendWidth) / 2 - padding;
+          legendData.area.end.x = (opts.width + legendWidth) / 2 + padding;
+      }
+      legendData.area.width = legendWidth + 2 * padding;
+      legendData.area.wholeWidth = legendWidth + 2 * padding;
+      legendData.area.height = legendList.length * lineHeight + 2 * padding;
+      legendData.area.wholeHeight = legendList.length * lineHeight + 2 * padding + 2 * margin;
+      legendData.points = legendList;
+    }
+  } else {
+    let len = series.length;
+    let maxHeight = opts.height - opts.area[0] - opts.area[2] - 2 * margin - 2 * padding;
+    let maxLength = Math.min(Math.floor(maxHeight / lineHeight), len);
+    legendData.area.height = maxLength * lineHeight + padding * 2;
+    legendData.area.wholeHeight = maxLength * lineHeight + padding * 2;
+    switch (opts.legend.float) {
+      case 'top':
+        legendData.area.start.y = opts.area[0] + margin;
+        legendData.area.end.y = opts.area[0] + margin + legendData.area.height;
+        break;
+      case 'bottom':
+        legendData.area.start.y = opts.height - opts.area[2] - margin - legendData.area.height;
+        legendData.area.end.y = opts.height - opts.area[2] - margin;
+        break;
+      default:
+        legendData.area.start.y = (opts.height - legendData.area.height) / 2;
+        legendData.area.end.y = (opts.height + legendData.area.height) / 2;
+    }
+    let lineNum = len % maxLength === 0 ? len / maxLength : Math.floor((len / maxLength) + 1);
+    let currentRow = [];
+    for (let i = 0; i < lineNum; i++) {
+      let temp = series.slice(i * maxLength, i * maxLength + maxLength);
+      currentRow.push(temp);
+    }
+    legendData.points = currentRow;
+    if (currentRow.length) {
+      for (let i = 0; i < currentRow.length; i++) {
+        let item = currentRow[i];
+        let maxWidth = 0;
+        for (let j = 0; j < item.length; j++) {
+          let itemWidth = shapeWidth + shapeRight + measureText(item[j].name || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;
+          if (itemWidth > maxWidth) {
+            maxWidth = itemWidth;
+          }
+        }
+        legendData.widthArr.push(maxWidth);
+        legendData.heightArr.push(item.length * lineHeight + padding * 2);
+      }
+      let legendWidth = 0
+      for (let i = 0; i < legendData.widthArr.length; i++) {
+        legendWidth += legendData.widthArr[i];
+      }
+      legendData.area.width = legendWidth - opts.legend.itemGap * opts.pix + 2 * padding;
+      legendData.area.wholeWidth = legendData.area.width + padding;
+    }
+  }
+  switch (opts.legend.position) {
+    case 'top':
+      legendData.area.start.y = opts.area[0] + margin;
+      legendData.area.end.y = opts.area[0] + margin + legendData.area.height;
+      break;
+    case 'bottom':
+      legendData.area.start.y = opts.height - opts.area[2] - legendData.area.height - margin;
+      legendData.area.end.y = opts.height - opts.area[2] - margin;
+      break;
+    case 'left':
+      legendData.area.start.x = opts.area[3];
+      legendData.area.end.x = opts.area[3] + legendData.area.width;
+      break;
+    case 'right':
+      legendData.area.start.x = opts.width - opts.area[1] - legendData.area.width;
+      legendData.area.end.x = opts.width - opts.area[1];
+      break;
+  }
+  chartData.legendData = legendData;
+  return legendData;
+}
+
+function calCategoriesData(categories, opts, config, eachSpacing, context) {
+  let result = {
+    angle: 0,
+    xAxisHeight: opts.xAxis.lineHeight * opts.pix + opts.xAxis.marginTop * opts.pix
+  };
+  let fontSize = opts.xAxis.fontSize * opts.pix;
+  let categoriesTextLenth = categories.map(function(item,index) {
+    let xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;
+    return measureText(String(xitem), fontSize, context);
+  });
+  let maxTextLength = Math.max.apply(this, categoriesTextLenth);
+  if (opts.xAxis.rotateLabel == true) {
+    result.angle = opts.xAxis.rotateAngle * Math.PI / 180;
+    let tempHeight = opts.xAxis.marginTop * opts.pix * 2 +  Math.abs(maxTextLength * Math.sin(result.angle))
+    tempHeight = tempHeight < fontSize + opts.xAxis.marginTop * opts.pix * 2 ? tempHeight + opts.xAxis.marginTop * opts.pix * 2 : tempHeight;
+    result.xAxisHeight = tempHeight;
+  }
+  if (opts.enableScroll && opts.xAxis.scrollShow) {
+    result.xAxisHeight += 6 * opts.pix;
+  }
+  if (opts.xAxis.disabled){
+    result.xAxisHeight = 0;
+  }
+  return result;
+}
+
+function getXAxisTextList(series, opts, config, stack) {
+  let index = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1;
+  let data;
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+  let sorted = [];
+  // remove null from data
+  data = data.filter(function(item) {
+    //return item !== null;
+    if (typeof item === 'object' && item !== null) {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.map(function(item) {
+    if (typeof item === 'object') {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        if (opts.type == 'candle') {
+          item.map(function(subitem) {
+            sorted.push(subitem);
+          })
+        } else {
+          sorted.push(item[0]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  })
+
+  let minData = 0;
+  let maxData = 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(this, sorted);
+    maxData = Math.max.apply(this, sorted);
+  }
+  //为了兼容v1.9.0之前的项目
+  if (index > -1) {
+    if (typeof opts.xAxis.data[index].min === 'number') {
+      minData = Math.min(opts.xAxis.data[index].min, minData);
+    }
+    if (typeof opts.xAxis.data[index].max === 'number') {
+      maxData = Math.max(opts.xAxis.data[index].max, maxData);
+    }
+  } else {
+    if (typeof opts.xAxis.min === 'number') {
+      minData = Math.min(opts.xAxis.min, minData);
+    }
+    if (typeof opts.xAxis.max === 'number') {
+      maxData = Math.max(opts.xAxis.max, maxData);
+    }
+  }
+  if (minData === maxData) {
+    let rangeSpan = maxData || 10;
+    maxData += rangeSpan;
+  }
+  //let dataRange = getDataRange(minData, maxData);
+  let minRange = minData;
+  let maxRange = maxData;
+  let range = [];
+  let eachRange = (maxRange - minRange) / opts.xAxis.splitNumber;
+  for (let i = 0; i <= opts.xAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range;
+}
+
+function calXAxisData(series, opts, config, context) {
+  //堆叠图重算Y轴
+  let columnstyle = assign({}, {
+    type: ""
+  }, opts.extra.bar);
+  let result = {
+    angle: 0,
+    xAxisHeight: opts.xAxis.lineHeight * opts.pix + opts.xAxis.marginTop * opts.pix
+  };
+  result.ranges = getXAxisTextList(series, opts, config, columnstyle.type);
+  result.rangesFormat = result.ranges.map(function(item) {
+    //item = opts.xAxis.formatter ? opts.xAxis.formatter(item) : util.toFixed(item, 2);
+    item = util.toFixed(item, 2);
+    return item;
+  });
+  let xAxisScaleValues = result.ranges.map(function(item) {
+    // 如果刻度值是浮点数,则保留两位小数
+    item = util.toFixed(item, 2);
+    // 若有自定义格式则调用自定义的格式化函数
+    //item = opts.xAxis.formatter ? opts.xAxis.formatter(Number(item)) : item;
+    return item;
+  });
+  result = Object.assign(result, getXAxisPoints(xAxisScaleValues, opts, config));
+  // 计算X轴刻度的属性譬如每个刻度的间隔,刻度的起始点\结束点以及总长
+  let eachSpacing = result.eachSpacing;
+  let textLength = xAxisScaleValues.map(function(item) {
+    return measureText(item, opts.xAxis.fontSize * opts.pix, context);
+  });
+  if (opts.xAxis.disabled === true) {
+    result.xAxisHeight = 0;
+  }
+  return result;
+}
+
+function getRadarDataPoints(angleList, center, radius, series, opts) {
+  let radarOption = opts.extra.radar || {};
+  radarOption.max = radarOption.max || 0;
+  let maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));
+  let data = [];
+  for (let i = 0; i < series.length; i++) {
+    let each = series[i];
+    let listItem = {};
+    listItem.color = each.color;
+    listItem.legendShape = each.legendShape;
+    listItem.pointShape = each.pointShape;
+    listItem.data = [];
+    each.data.forEach(function(item, index) {
+      let tmp = {};
+      tmp.angle = angleList[index];
+      tmp.proportion = item / maxData;
+      tmp.value = item;
+      tmp.position = convertCoordinateOrigin(radius * tmp.proportion * process * Math.cos(tmp.angle), radius * tmp.proportion * process * Math.sin(tmp.angle), center);
+      listItem.data.push(tmp);
+    });
+    data.push(listItem);
+  }
+  return data;
+}
+
+function getPieDataPoints(series, radius) {
+  let count = 0;
+  let _start_ = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+    }
+    item._radius_ = radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._proportion_ * Math.PI;
+  }
+  return series;
+}
+
+function getFunnelDataPoints(series, radius, option, eachSpacing) {
+  for (let i = 0; i < series.length; i++) {
+    if(option.type == 'funnel'){
+      series[i].radius = series[i].data / series[0].data * radius * process;
+    }else{
+      series[i].radius =  (eachSpacing * (series.length - i)) / (eachSpacing * series.length) * radius * process;
+    }
+    series[i]._proportion_ = series[i].data / series[0].data;
+  }
+  // if(option.type !== 'pyramid'){
+  //   series.reverse();
+  // }
+  return series;
+}
+
+function getRoseDataPoints(series, type, minRadius, radius) {
+  let count = 0;
+  let _start_ = 0;
+  let dataArr = [];
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    count += item.data;
+    dataArr.push(item.data);
+  }
+  let minData = Math.min.apply(null, dataArr);
+  let maxData = Math.max.apply(null, dataArr);
+  let radiusLength = radius - minRadius;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (count === 0) {
+      item._proportion_ = 1 / series.length * process;
+      item._rose_proportion_ = 1 / series.length * process;
+    } else {
+      item._proportion_ = item.data / count * process;
+      if(type == 'area'){
+        item._rose_proportion_ = 1 / series.length * process;
+      }else{
+        item._rose_proportion_ = item.data / count * process;
+      }
+    }
+    item._radius_ = minRadius + radiusLength * ((item.data - minData) / (maxData - minData)) || radius;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item._start_ = _start_;
+    _start_ += 2 * item._rose_proportion_ * Math.PI;
+  }
+  return series;
+}
+
+function getArcbarDataPoints(series, arcbarOption) {
+  if (process == 1) {
+    process = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if(arcbarOption.direction == 'ccw'){
+        if (arcbarOption.startAngle < arcbarOption.endAngle) {
+          totalAngle = 2 + arcbarOption.startAngle - arcbarOption.endAngle;
+        } else {
+          totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+        }
+      }else{
+        if (arcbarOption.endAngle < arcbarOption.startAngle) {
+          totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;
+        } else {
+          totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+        }
+      }
+    }
+    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;
+    if(arcbarOption.direction == 'ccw'){
+      item._proportion_ = arcbarOption.startAngle - totalAngle * item.data * process ;
+    }
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getGaugeArcbarDataPoints(series, arcbarOption) {
+  if (process == 1) {
+    process = 0.999999;
+  }
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    let totalAngle;
+    if (arcbarOption.type == 'circle') {
+      totalAngle = 2;
+    } else {
+      if (arcbarOption.endAngle < arcbarOption.startAngle) {
+        totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;
+      } else {
+        totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;
+      }
+    }
+    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getGaugeAxisPoints(categories, startAngle, endAngle) {
+  let totalAngle;
+  if (endAngle < startAngle) {
+    totalAngle = 2 + endAngle - startAngle;
+  } else {
+    totalAngle = startAngle - endAngle;
+  }
+  let tempStartAngle = startAngle;
+  for (let i = 0; i < categories.length; i++) {
+    categories[i].value = categories[i].value === null ? 0 : categories[i].value;
+    categories[i]._startAngle_ = tempStartAngle;
+    categories[i]._endAngle_ = totalAngle * categories[i].value + startAngle;
+    if (categories[i]._endAngle_ >= 2) {
+      categories[i]._endAngle_ = categories[i]._endAngle_ % 2;
+    }
+    tempStartAngle = categories[i]._endAngle_;
+  }
+  return categories;
+}
+
+function getGaugeDataPoints(series, categories, gaugeOption) {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    item.data = item.data === null ? 0 : item.data;
+    if (gaugeOption.pointer.color == 'auto') {
+      for (let i = 0; i < categories.length; i++) {
+        if (item.data <= categories[i].value) {
+          item.color = categories[i].color;
+          break;
+        }
+      }
+    } else {
+      item.color = gaugeOption.pointer.color;
+    }
+    let totalAngle;
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    item._endAngle_ = totalAngle * item.data + gaugeOption.startAngle;
+    item._oldAngle_ = gaugeOption.oldAngle;
+    if (gaugeOption.oldAngle < gaugeOption.endAngle) {
+      item._oldAngle_ += 2;
+    }
+    if (item.data >= gaugeOption.oldData) {
+      item._proportion_ = (item._endAngle_ - item._oldAngle_) * process + gaugeOption.oldAngle;
+    } else {
+      item._proportion_ = item._oldAngle_ - (item._oldAngle_ - item._endAngle_) * process;
+    }
+    if (item._proportion_ >= 2) {
+      item._proportion_ = item._proportion_ % 2;
+    }
+  }
+  return series;
+}
+
+function getPieTextMaxLength(series, config, context, opts) {
+  series = getPieDataPoints(series);
+  let maxLength = 0;
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    let text = item.formatter ? item.formatter(+item._proportion_.toFixed(2)) : util.toFixed(item._proportion_ * 100) + '%';
+    maxLength = Math.max(maxLength, measureText(text, item.textSize * opts.pix || config.fontSize, context));
+  }
+  return maxLength;
+}
+
+function fixColumeData(points, eachSpacing, columnLen, index, config, opts) {
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    let seriesGap = 0;
+    let categoryGap = 0;
+    if (opts.type == 'mix') {
+      seriesGap = opts.extra.mix.column.seriesGap * opts.pix || 0;
+      categoryGap = opts.extra.mix.column.categoryGap * opts.pix || 0;
+    } else {
+      seriesGap = opts.extra.column.seriesGap * opts.pix || 0;
+      categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+    }
+    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)
+    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)
+    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);
+    if (opts.extra.mix && opts.extra.mix.column.width && +opts.extra.mix.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.mix.column.width * opts.pix);
+    }
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    item.x += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);
+    return item;
+  });
+}
+
+function fixBarData(points, eachSpacing, columnLen, index, config, opts) {
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    let seriesGap = 0;
+    let categoryGap = 0;
+    seriesGap = opts.extra.bar.seriesGap * opts.pix || 0;
+    categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;
+    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)
+    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)
+    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);
+    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    item.y += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);
+    return item;
+  });
+}
+
+function fixColumeMeterData(points, eachSpacing, columnLen, index, config, opts, border) {
+  let categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+  return points.map(function(item) {
+    if (item === null) {
+      return null;
+    }
+    item.width = eachSpacing - 2 * categoryGap;
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (index > 0) {
+      item.width -= border;
+    }
+    return item;
+  });
+}
+
+function fixColumeStackData(points, eachSpacing, columnLen, index, config, opts, series) {
+  let categoryGap = opts.extra.column.categoryGap * opts.pix || 0;
+  return points.map(function(item, indexn) {
+    if (item === null) {
+      return null;
+    }
+    item.width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    return item;
+  });
+}
+
+function fixBarStackData(points, eachSpacing, columnLen, index, config, opts, series) {
+  let categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;
+  return points.map(function(item, indexn) {
+    if (item === null) {
+      return null;
+    }
+    item.width = Math.ceil(eachSpacing - 2 * categoryGap);
+    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {
+      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);
+    }
+    if (item.width <= 0) {
+      item.width = 1;
+    }
+    return item;
+  });
+}
+
+function getXAxisPoints(categories, opts, config) {
+  let spacingValid = opts.width - opts.area[1] - opts.area[3];
+  let dataCount = opts.enableScroll ? Math.min(opts.xAxis.itemCount, categories.length) : categories.length;
+  if ((opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' || opts.type == 'bar') && dataCount > 1 && opts.xAxis.boundaryGap == 'justify') {
+    dataCount -= 1;
+  }
+  let widthRatio = 0;
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    widthRatio = opts.extra.mount.widthRatio - 1;
+    dataCount += widthRatio;
+  }
+  let eachSpacing = spacingValid / dataCount;
+  let xAxisPoints = [];
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  categories.forEach(function(item, index) {
+    xAxisPoints.push(startX + widthRatio / 2 * eachSpacing + index * eachSpacing);
+  });
+  if (opts.xAxis.boundaryGap !== 'justify') {
+    if (opts.enableScroll === true) {
+      xAxisPoints.push(startX + widthRatio * eachSpacing + categories.length * eachSpacing);
+    } else {
+      xAxisPoints.push(endX);
+    }
+  }
+  return {
+    xAxisPoints: xAxisPoints,
+    startX: startX,
+    endX: endX,
+    eachSpacing: eachSpacing
+  };
+}
+
+function getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let cPoints = [];
+      item.forEach(function(items, indexs) {
+        let point = {};
+        point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+        let value = items.value || items;
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height - Math.round(height) - opts.area[2];
+        cPoints.push(point);
+      });
+      points.push(cPoints);
+    }
+  });
+  return points;
+}
+
+function getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {
+  let boundaryGap = 'center';
+  if (opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' ) {
+    boundaryGap = opts.xAxis.boundaryGap;
+  }
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      let value = item;
+      if (typeof item === 'object' && item !== null) {
+        if (item.constructor.toString().indexOf('Array') > -1) {
+          let xranges, xminRange, xmaxRange;
+          xranges = [].concat(opts.chartData.xAxisData.ranges);
+          xminRange = xranges.shift();
+          xmaxRange = xranges.pop();
+          value = item[1];
+          point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          if(opts.type == 'bubble'){
+            point.r = item[2];
+            point.t = item[3];
+          }
+        } else {
+          value = item.value;
+        }
+      }
+      if (boundaryGap == 'center') {
+        point.x += eachSpacing / 2;
+      }
+      let height = validHeight * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getLineDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, lineOption, chartProcess){
+  let boundaryGap = opts.xAxis.boundaryGap;
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      if(lineOption.animation == 'vertical'){
+        point.x = xAxisPoints[index];
+        let value = item;
+        if (typeof item === 'object' && item !== null) {
+          if (item.constructor.toString().indexOf('Array') > -1) {
+            let xranges, xminRange, xmaxRange;
+            xranges = [].concat(opts.chartData.xAxisData.ranges);
+            xminRange = xranges.shift();
+            xmaxRange = xranges.pop();
+            value = item[1];
+            point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+          } else {
+            value = item.value;
+          }
+        }
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        height *= process;
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }else{
+        point.x = xAxisPoints[0] + eachSpacing * index * process;
+        let value = item;
+        if (boundaryGap == 'center') {
+          point.x += eachSpacing / 2;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        point.y = opts.height - height - opts.area[2];
+        points.push(point);
+      }
+    }
+  });
+  return points;
+}
+
+function getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, zeroPoints, chartProcess){
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      let value = item;
+      if (typeof item === 'object' && item !== null) {
+        if (item.constructor.toString().indexOf('Array') > -1) {
+          let xranges, xminRange, xmaxRange;
+          xranges = [].concat(opts.chartData.xAxisData.ranges);
+          xminRange = xranges.shift();
+          xmaxRange = xranges.pop();
+          value = item[1];
+          point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);
+        } else {
+          value = item.value;
+        }
+      }
+      point.x += eachSpacing / 2;
+      let height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints) {
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  let mountWidth = eachSpacing * mountOption.widthRatio;
+  series.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index];
+      point.x += eachSpacing / 2;
+      let value = item.data;
+      let height = validHeight * (value * process - minRange) / (maxRange - minRange);
+      point.y = opts.height - height - opts.area[2];
+      point.value = value;
+      point.width = mountWidth;
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config) {
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  let validWidth = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.y = yAxisPoints[index];
+      let value = item;
+      if (typeof item === 'object' && item !== null) {
+        value = item.value;
+      }
+      let height = validWidth * (value - minRange) / (maxRange - minRange);
+      height *= process;
+      point.height = height;
+      point.value = value;
+      point.x = height + opts.area[3];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {
+  let points = [];
+  let validHeight = opts.height - opts.area[0] - opts.area[2];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);
+
+      if (seriesIndex > 0) {
+        let value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index];
+        }
+        let value0 = value - item;
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        let height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        let value = item;
+        if (typeof item === 'object' && item !== null) {
+          value = item.value;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        let height0 = 0;
+      }
+      let heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.y = opts.height - Math.round(height) - opts.area[2];
+      point.y0 = opts.height - Math.round(heightc) - opts.area[2];
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {
+  let points = [];
+  let validHeight = opts.width - opts.area[1] - opts.area[3];
+  data.forEach(function(item, index) {
+    if (item === null) {
+      points.push(null);
+    } else {
+      let point = {};
+      point.color = item.color;
+      point.y = yAxisPoints[index];
+      if (seriesIndex > 0) {
+        let value = 0;
+        for (let i = 0; i <= seriesIndex; i++) {
+          value += stackSeries[i].data[index];
+        }
+        let value0 = value - item;
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        let height0 = validHeight * (value0 - minRange) / (maxRange - minRange);
+      } else {
+        let value = item;
+        if (typeof item === 'object' && item !== null) {
+          value = item.value;
+        }
+        let height = validHeight * (value - minRange) / (maxRange - minRange);
+        let height0 = 0;
+      }
+      let heightc = height0;
+      height *= process;
+      heightc *= process;
+      point.height = height - heightc;
+      point.x = opts.area[3] + height;
+      point.x0 = opts.area[3] + heightc;
+      points.push(point);
+    }
+  });
+  return points;
+}
+
+function getYAxisTextList(series, opts, config, stack, yData) {
+  let index = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : -1;
+  let data;
+  if (stack == 'stack') {
+    data = dataCombineStack(series, opts.categories.length);
+  } else {
+    data = dataCombine(series);
+  }
+  let sorted = [];
+  // remove null from data
+  data = data.filter(function(item) {
+    //return item !== null;
+    if (typeof item === 'object' && item !== null) {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        return item !== null;
+      } else {
+        return item.value !== null;
+      }
+    } else {
+      return item !== null;
+    }
+  });
+  data.map(function(item) {
+    if (typeof item === 'object') {
+      if (item.constructor.toString().indexOf('Array') > -1) {
+        if (opts.type == 'candle') {
+          item.map(function(subitem) {
+            sorted.push(subitem);
+          })
+        } else {
+          sorted.push(item[1]);
+        }
+      } else {
+        sorted.push(item.value);
+      }
+    } else {
+      sorted.push(item);
+    }
+  })
+  let minData = yData.min || 0;
+  let maxData = yData.max || 0;
+  if (sorted.length > 0) {
+    minData = Math.min.apply(this, sorted);
+    maxData = Math.max.apply(this, sorted);
+  }
+  if (minData === maxData) {
+    if(maxData == 0){
+      maxData = 10;
+    }else{
+      minData = 0;
+    }
+  }
+  let dataRange = getDataRange(minData, maxData);
+  let minRange = (yData.min === undefined || yData.min === null) ? dataRange.minRange : yData.min;
+  let maxRange = (yData.max === undefined || yData.max === null) ? dataRange.maxRange : yData.max;
+  let eachRange = (maxRange - minRange) / opts.yAxis.splitNumber;
+  let range = [];
+  for (let i = 0; i <= opts.yAxis.splitNumber; i++) {
+    range.push(minRange + eachRange * i);
+  }
+  return range.reverse();
+}
+
+function calYAxisData(series, opts, config, context) {
+  //堆叠图重算Y轴
+  let columnstyle = assign({}, {
+    type: ""
+  }, opts.extra.column);
+  //如果是多Y轴,重新计算
+  let YLength = opts.yAxis.data.length;
+  let newSeries = new Array(YLength);
+  if (YLength > 0) {
+    for (let i = 0; i < YLength; i++) {
+      newSeries[i] = [];
+      for (let j = 0; j < series.length; j++) {
+        if (series[j].index == i) {
+          newSeries[i].push(series[j]);
+        }
+      }
+    }
+    let rangesArr = new Array(YLength);
+    let rangesFormatArr = new Array(YLength);
+    let yAxisWidthArr = new Array(YLength);
+
+    for (let i = 0; i < YLength; i++) {
+      let yData = opts.yAxis.data[i];
+      //如果总开关不显示,强制每个Y轴为不显示
+      if (opts.yAxis.disabled == true) {
+        yData.disabled = true;
+      }
+      if(yData.type === 'categories'){
+        if(!yData.formatter){
+          yData.formatter = (val,index,opts) => {return val + (yData.unit || '')};
+        }
+        yData.categories = yData.categories || opts.categories;
+        rangesArr[i] = yData.categories;
+      }else{
+        if(!yData.formatter){
+          yData.formatter = (val,index,opts) => {return util.toFixed(val, yData.tofix || 0) + (yData.unit || '')};
+        }
+        rangesArr[i] = getYAxisTextList(newSeries[i], opts, config, columnstyle.type, yData, i);
+      }
+      let yAxisFontSizes = yData.fontSize * opts.pix || config.fontSize;
+      yAxisWidthArr[i] = {
+        position: yData.position ? yData.position : 'left',
+        width: 0
+      };
+      rangesFormatArr[i] = rangesArr[i].map(function(items,index) {
+        items = yData.formatter(items,index,opts);
+        yAxisWidthArr[i].width = Math.max(yAxisWidthArr[i].width, measureText(items, yAxisFontSizes, context) + 5);
+        return items;
+      });
+      let calibration = yData.calibration ? 4 * opts.pix : 0;
+      yAxisWidthArr[i].width += calibration + 3 * opts.pix;
+      if (yData.disabled === true) {
+        yAxisWidthArr[i].width = 0;
+      }
+    }
+  } else {
+    let rangesArr = new Array(1);
+    let rangesFormatArr = new Array(1);
+    let yAxisWidthArr = new Array(1);
+    if(opts.type === 'bar'){
+      rangesArr[0] = opts.categories;
+      if(!opts.yAxis.formatter){
+        opts.yAxis.formatter = (val,index,opts) => {return val + (opts.yAxis.unit || '')}
+      }
+    }else{
+      if(!opts.yAxis.formatter){
+        opts.yAxis.formatter = (val,index,opts) => {return val.toFixed(opts.yAxis.tofix ) + (opts.yAxis.unit || '')}
+      }
+      rangesArr[0] = getYAxisTextList(series, opts, config, columnstyle.type, {});
+    }
+    yAxisWidthArr[0] = {
+      position: 'left',
+      width: 0
+    };
+    let yAxisFontSize = opts.yAxis.fontSize * opts.pix || config.fontSize;
+    rangesFormatArr[0] = rangesArr[0].map(function(item,index) {
+      item = opts.yAxis.formatter(item,index,opts);
+      yAxisWidthArr[0].width = Math.max(yAxisWidthArr[0].width, measureText(item, yAxisFontSize, context) + 5);
+      return item;
+    });
+    yAxisWidthArr[0].width += 3 * opts.pix;
+    if (opts.yAxis.disabled === true) {
+      yAxisWidthArr[0] = {
+        position: 'left',
+        width: 0
+      };
+      opts.yAxis.data[0] = {
+        disabled: true
+      };
+    } else {
+      opts.yAxis.data[0] = {
+        disabled: false,
+        position: 'left',
+        max: opts.yAxis.max,
+        min: opts.yAxis.min,
+        formatter: opts.yAxis.formatter
+      };
+      if(opts.type === 'bar'){
+        opts.yAxis.data[0].categories = opts.categories;
+        opts.yAxis.data[0].type = 'categories';
+      }
+    }
+  }
+  return {
+    rangesFormat: rangesFormatArr,
+    ranges: rangesArr,
+    yAxisWidth: yAxisWidthArr
+  };
+}
+
+function calTooltipYAxisData(point, series, opts, config, eachSpacing) {
+  let ranges = [].concat(opts.chartData.yAxisData.ranges);
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  let minAxis = opts.area[0];
+  let items = [];
+  for (let i = 0; i < ranges.length; i++) {
+    let maxVal = Math.max.apply(this, ranges[i]);
+    let minVal = Math.min.apply(this, ranges[i]);
+    let item = maxVal - (maxVal - minVal) * (point - minAxis) / spacingValid;
+    item = opts.yAxis.data && opts.yAxis.data[i].formatter ? opts.yAxis.data[i].formatter(item, i, opts) : item.toFixed(0);
+    items.push(String(item))
+  }
+  return items;
+}
+
+function calMarkLineData(points, opts) {
+  let minRange, maxRange;
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  for (let i = 0; i < points.length; i++) {
+    points[i].yAxisIndex = points[i].yAxisIndex ? points[i].yAxisIndex : 0;
+    let range = [].concat(opts.chartData.yAxisData.ranges[points[i].yAxisIndex]);
+    minRange = range.pop();
+    maxRange = range.shift();
+    let height = spacingValid * (points[i].value - minRange) / (maxRange - minRange);
+    points[i].y = opts.height - Math.round(height) - opts.area[2];
+  }
+  return points;
+}
+
+function contextRotate(context, opts) {
+  if (opts.rotateLock !== true) {
+    context.translate(opts.height, 0);
+    context.rotate(90 * Math.PI / 180);
+  } else if (opts._rotate_ !== true) {
+    context.translate(opts.height, 0);
+    context.rotate(90 * Math.PI / 180);
+    opts._rotate_ = true;
+  }
+}
+
+function drawPointShape(points, color, shape, context, opts) {
+  context.beginPath();
+  if (opts.dataPointShapeType == 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+function drawActivePoint(points, color, shape, context, opts, option, seriesIndex) {
+  if(!opts.tooltip){
+    return
+  }
+  if(opts.tooltip.group.length>0 && opts.tooltip.group.includes(seriesIndex) == false){
+    return
+  }
+  let pointIndex = typeof opts.tooltip.index === 'number' ? opts.tooltip.index : opts.tooltip.index[opts.tooltip.group.indexOf(seriesIndex)];
+  context.beginPath();
+  if (option.activeType == 'hollow') {
+    context.setStrokeStyle(color);
+    context.setFillStyle(opts.background);
+    context.setLineWidth(2 * opts.pix);
+  } else {
+    context.setStrokeStyle("#ffffff");
+    context.setFillStyle(color);
+    context.setLineWidth(1 * opts.pix);
+  }
+  if (shape === 'diamond') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index ) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y);
+        context.lineTo(item.x, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'circle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x + 2.5 * opts.pix, item.y);
+        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+      }
+    });
+  } else if (shape === 'square') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x - 3.5, item.y - 3.5);
+        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+      }
+    });
+  } else if (shape === 'triangle') {
+    points.forEach(function(item, index) {
+      if (item !== null && pointIndex == index) {
+        context.moveTo(item.x, item.y - 4.5);
+        context.lineTo(item.x - 4.5, item.y + 4.5);
+        context.lineTo(item.x + 4.5, item.y + 4.5);
+        context.lineTo(item.x, item.y - 4.5);
+      }
+    });
+  } else if (shape === 'none') {
+    return;
+  }
+  context.closePath();
+  context.fill();
+  context.stroke();
+}
+
+function drawRingTitle(opts, config, context, center) {
+  let titlefontSize = opts.title.fontSize || config.titleFontSize;
+  let subtitlefontSize = opts.subtitle.fontSize || config.subtitleFontSize;
+  let title = opts.title.name || '';
+  let subtitle = opts.subtitle.name || '';
+  let titleFontColor = opts.title.color || opts.fontColor;
+  let subtitleFontColor = opts.subtitle.color || opts.fontColor;
+  let titleHeight = title ? titlefontSize : 0;
+  let subtitleHeight = subtitle ? subtitlefontSize : 0;
+  let margin = 5;
+  if (subtitle) {
+    let textWidth = measureText(subtitle, subtitlefontSize * opts.pix, context);
+    let startX = center.x - textWidth / 2 + (opts.subtitle.offsetX|| 0) * opts.pix ;
+    let startY = center.y + subtitlefontSize * opts.pix / 2 + (opts.subtitle.offsetY || 0) * opts.pix;
+    if (title) {
+      startY += (titleHeight * opts.pix + margin) / 2;
+    }
+    context.beginPath();
+    context.setFontSize(subtitlefontSize * opts.pix);
+    context.setFillStyle(subtitleFontColor);
+    context.fillText(subtitle, startX, startY);
+    context.closePath();
+    context.stroke();
+  }
+  if (title) {
+    let _textWidth = measureText(title, titlefontSize * opts.pix, context);
+    let _startX = center.x - _textWidth / 2 + (opts.title.offsetX || 0);
+    let _startY = center.y + titlefontSize * opts.pix / 2 + (opts.title.offsetY || 0) * opts.pix;
+    if (subtitle) {
+      _startY -= (subtitleHeight * opts.pix + margin) / 2;
+    }
+    context.beginPath();
+    context.setFontSize(titlefontSize * opts.pix);
+    context.setFillStyle(titleFontColor);
+    context.fillText(title, _startX, _startY);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+function drawPointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  let data = series.data;
+  let textOffset = series.textOffset ? series.textOffset : 0;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      let fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      let value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (data[index].constructor.toString().indexOf('Array')>-1) {
+          value = data[index][1];
+        } else {
+          value = data[index].value
+        }
+      }
+      let formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      context.fillText(String(formatVal), item.x, item.y - 4 + textOffset * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawColumePointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  let data = series.data;
+  let textOffset = series.textOffset ? series.textOffset : 0;
+  let Position = opts.extra.column.labelPosition;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      let fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      let value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        if (data[index].constructor.toString().indexOf('Array')>-1) {
+          value = data[index][1];
+        } else {
+          value = data[index].value
+        }
+      }
+      let formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      let startY = item.y - 4 * opts.pix + textOffset * opts.pix;
+      if(item.y > series.zeroPoints){
+        startY = item.y + textOffset * opts.pix + fontSize;
+      }
+      if(Position == 'insideTop'){
+        startY = item.y + fontSize + textOffset * opts.pix;
+        if(item.y > series.zeroPoints){
+          startY = item.y - textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+      if(Position == 'center'){
+        startY = item.y + textOffset * opts.pix + (opts.height - opts.area[2] - item.y + fontSize)/2;
+        if(series.zeroPoints < opts.height - opts.area[2]){
+          startY = item.y + textOffset * opts.pix + (series.zeroPoints - item.y + fontSize)/2;
+        }
+        if(item.y > series.zeroPoints){
+          startY = item.y - textOffset * opts.pix - (item.y - series.zeroPoints - fontSize)/2;
+        }
+        if(opts.extra.column.type == 'stack'){
+          startY = item.y + textOffset * opts.pix + (item.y0 - item.y + fontSize)/2;
+        }
+      }
+      if(Position == 'bottom'){
+        startY = opts.height - opts.area[2] + textOffset * opts.pix - 4 * opts.pix;
+        if(series.zeroPoints < opts.height - opts.area[2]){
+          startY = series.zeroPoints + textOffset * opts.pix - 4 * opts.pix;
+        }
+        if(item.y > series.zeroPoints){
+          startY = series.zeroPoints - textOffset * opts.pix + fontSize + 2 * opts.pix;
+        }
+        if(opts.extra.column.type == 'stack'){
+          startY = item.y0 + textOffset * opts.pix - 4 * opts.pix;
+        }
+      }
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawMountPointText(points, series, config, context, opts, zeroPoints) {
+  // 绘制数据文案
+  let data = series.data;
+  let textOffset = series.textOffset ? series.textOffset : 0;
+  let Position = opts.extra.mount.labelPosition;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      let fontSize = series[index].textSize ? series[index].textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series[index].textColor || opts.fontColor);
+      let value = item.value
+      let formatVal = series[index].formatter ? series[index].formatter(value,index,series,opts) : value;
+      context.setTextAlign('center');
+      let startY = item.y - 4 * opts.pix + textOffset * opts.pix;
+      if(item.y > zeroPoints){
+        startY = item.y + textOffset * opts.pix + fontSize;
+      }
+      context.fillText(String(formatVal), item.x, startY);
+      context.closePath();
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  });
+}
+
+function drawBarPointText(points, series, config, context, opts) {
+  // 绘制数据文案
+  let data = series.data;
+  let textOffset = series.textOffset ? series.textOffset : 0;
+  points.forEach(function(item, index) {
+    if (item !== null) {
+      context.beginPath();
+      let fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(series.textColor || opts.fontColor);
+      let value = data[index]
+      if (typeof data[index] === 'object' && data[index] !== null) {
+        value = data[index].value ;
+      }
+      let formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;
+      context.setTextAlign('left');
+      context.fillText(String(formatVal), item.x + 4 * opts.pix , item.y + fontSize / 2 - 3 );
+      context.closePath();
+      context.stroke();
+    }
+  });
+}
+
+function drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context) {
+  radius -= gaugeOption.width / 2 + gaugeOption.labelOffset * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  let totalAngle;
+  if (gaugeOption.endAngle < gaugeOption.startAngle) {
+    totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+  } else {
+    totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+  }
+  let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+  let totalNumber = gaugeOption.endNumber - gaugeOption.startNumber;
+  let splitNumber = totalNumber / gaugeOption.splitLine.splitNumber;
+  let nowAngle = gaugeOption.startAngle;
+  let nowNumber = gaugeOption.startNumber;
+  for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {
+    let pos = {
+      x: radius * Math.cos(nowAngle * Math.PI),
+      y: radius * Math.sin(nowAngle * Math.PI)
+    };
+    let labelText = gaugeOption.formatter ? gaugeOption.formatter(nowNumber,i,opts) : nowNumber;
+    pos.x += centerPosition.x - measureText(labelText, config.fontSize, context) / 2;
+    pos.y += centerPosition.y;
+    let startX = pos.x;
+    let startY = pos.y;
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(gaugeOption.labelColor || opts.fontColor);
+    context.fillText(labelText, startX, startY + config.fontSize / 2);
+    context.closePath();
+    context.stroke();
+    nowAngle += splitAngle;
+    if (nowAngle >= 2) {
+      nowAngle = nowAngle % 2;
+    }
+    nowNumber += splitNumber;
+  }
+}
+
+function drawRadarLabel(angleList, radius, centerPosition, opts, config, context) {
+  let radarOption = opts.extra.radar || {};
+  angleList.forEach(function(angle, index) {
+    if(radarOption.labelPointShow === true && opts.categories[index] !== ''){
+      let posPoint = {
+        x: radius * Math.cos(angle),
+        y: radius * Math.sin(angle)
+      };
+      let posPointAxis = convertCoordinateOrigin(posPoint.x, posPoint.y, centerPosition);
+      context.setFillStyle(radarOption.labelPointColor);
+      context.beginPath();
+      context.arc(posPointAxis.x, posPointAxis.y, radarOption.labelPointRadius * opts.pix, 0, 2 * Math.PI, false);
+      context.closePath();
+      context.fill();
+    }
+    if(radarOption.labelShow === true){
+      let pos = {
+        x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),
+        y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)
+      };
+      let posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);
+      let startX = posRelativeCanvas.x;
+      let startY = posRelativeCanvas.y;
+      if (util.approximatelyEqual(pos.x, 0)) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context) / 2;
+      } else if (pos.x < 0) {
+        startX -= measureText(opts.categories[index] || '', config.fontSize, context);
+      }
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(radarOption.labelColor || opts.fontColor);
+      context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2);
+      context.closePath();
+      context.stroke();
+    }
+  });
+
+}
+
+function drawPieText(series, opts, config, context, radius, center) {
+  let lineRadius = config.pieChartLinePadding;
+  let textObjectCollection = [];
+  let lastTextObject = null;
+  let seriesConvert = series.map(function(item,index) {
+    let text = item.formatter ? item.formatter(item,index,series,opts) : util.toFixed(item._proportion_.toFixed(4) * 100) + '%';
+    text = item.labelText ? item.labelText : text;
+    let arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._proportion_ / 2);
+    if (item._rose_proportion_) {
+      arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._rose_proportion_ / 2);
+    }
+    let color = item.color;
+    let radius = item._radius_;
+    return {
+      arc: arc,
+      text: text,
+      color: color,
+      radius: radius,
+      textColor: item.textColor,
+      textSize: item.textSize,
+      labelShow: item.labelShow
+    };
+  });
+  for (let i = 0; i < seriesConvert.length; i++) {
+    let item = seriesConvert[i];
+    // line end
+    let orginX1 = Math.cos(item.arc) * (item.radius + lineRadius);
+    let orginY1 = Math.sin(item.arc) * (item.radius + lineRadius);
+    // line start
+    let orginX2 = Math.cos(item.arc) * item.radius;
+    let orginY2 = Math.sin(item.arc) * item.radius;
+    // text start
+    let orginX3 = orginX1 >= 0 ? orginX1 + config.pieChartTextPadding : orginX1 - config.pieChartTextPadding;
+    let orginY3 = orginY1;
+    let textWidth = measureText(item.text, item.textSize * opts.pix || config.fontSize, context);
+    let startY = orginY3;
+    if (lastTextObject && util.isSameXCoordinateArea(lastTextObject.start, {
+        x: orginX3
+      })) {
+      if (orginX3 > 0) {
+        startY = Math.min(orginY3, lastTextObject.start.y);
+      } else if (orginX1 < 0) {
+        startY = Math.max(orginY3, lastTextObject.start.y);
+      } else {
+        if (orginY3 > 0) {
+          startY = Math.max(orginY3, lastTextObject.start.y);
+        } else {
+          startY = Math.min(orginY3, lastTextObject.start.y);
+        }
+      }
+    }
+    if (orginX3 < 0) {
+      orginX3 -= textWidth;
+    }
+    let textObject = {
+      lineStart: {
+        x: orginX2,
+        y: orginY2
+      },
+      lineEnd: {
+        x: orginX1,
+        y: orginY1
+      },
+      start: {
+        x: orginX3,
+        y: startY
+      },
+      width: textWidth,
+      height: config.fontSize,
+      text: item.text,
+      color: item.color,
+      textColor: item.textColor,
+      textSize: item.textSize
+    };
+    lastTextObject = avoidCollision(textObject, lastTextObject);
+    textObjectCollection.push(lastTextObject);
+  }
+  for (let i = 0; i < textObjectCollection.length; i++) {
+    if(seriesConvert[i].labelShow === false){
+      continue;
+    }
+    let item = textObjectCollection[i];
+    let lineStartPoistion = convertCoordinateOrigin(item.lineStart.x, item.lineStart.y, center);
+    let lineEndPoistion = convertCoordinateOrigin(item.lineEnd.x, item.lineEnd.y, center);
+    let textPosition = convertCoordinateOrigin(item.start.x, item.start.y, center);
+    context.setLineWidth(1 * opts.pix);
+    context.setFontSize(item.textSize * opts.pix || config.fontSize);
+    context.beginPath();
+    context.setStrokeStyle(item.color);
+    context.setFillStyle(item.color);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+    let curveStartX = item.start.x < 0 ? textPosition.x + item.width : textPosition.x;
+    let textStartX = item.start.x < 0 ? textPosition.x - 5 : textPosition.x + 5;
+    context.quadraticCurveTo(lineEndPoistion.x, lineEndPoistion.y, curveStartX, textPosition.y);
+    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);
+    context.stroke();
+    context.closePath();
+    context.beginPath();
+    context.moveTo(textPosition.x + item.width, textPosition.y);
+    context.arc(curveStartX, textPosition.y, 2 * opts.pix, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+    context.beginPath();
+    context.setFontSize(item.textSize * opts.pix || config.fontSize);
+    context.setFillStyle(item.textColor || opts.fontColor);
+    context.fillText(item.text, textStartX, textPosition.y + 3);
+    context.closePath();
+    context.stroke();
+    context.closePath();
+  }
+}
+
+function drawToolTipSplitLine(offsetX, opts, config, context) {
+  let toolTipOption = opts.extra.tooltip || {};
+  toolTipOption.gridType = toolTipOption.gridType == undefined ? 'solid' : toolTipOption.gridType;
+  toolTipOption.dashLength = toolTipOption.dashLength == undefined ? 4 : toolTipOption.dashLength;
+  let startY = opts.area[0];
+  let endY = opts.height - opts.area[2];
+  if (toolTipOption.gridType == 'dash') {
+    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);
+  }
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(offsetX, startY);
+  context.lineTo(offsetX, endY);
+  context.stroke();
+  context.setLineDash([]);
+  if (toolTipOption.xAxisLabel) {
+    let labelText = opts.categories[opts.tooltip.index];
+    context.setFontSize(config.fontSize);
+    let textWidth = measureText(labelText, config.fontSize, context);
+    let textX = offsetX - 0.5 * textWidth;
+    let textY = endY + 2 * opts.pix;
+    context.beginPath();
+    context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+    context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+    context.setLineWidth(1 * opts.pix);
+    context.rect(textX - toolTipOption.boxPadding * opts.pix, textY, textWidth + 2 * toolTipOption.boxPadding * opts.pix, config.fontSize + 2 * toolTipOption.boxPadding * opts.pix);
+    context.closePath();
+    context.stroke();
+    context.fill();
+    context.beginPath();
+    context.setFontSize(config.fontSize);
+    context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+    context.fillText(String(labelText), textX, textY + toolTipOption.boxPadding * opts.pix + config.fontSize);
+    context.closePath();
+    context.stroke();
+  }
+}
+
+function drawMarkLine(opts, config, context) {
+  let markLineOption = assign({}, {
+    type: 'solid',
+    dashLength: 4,
+    data: []
+  }, opts.extra.markLine);
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  let points = calMarkLineData(markLineOption.data, opts);
+  for (let i = 0; i < points.length; i++) {
+    let item = assign({}, {
+      lineColor: '#DE4A42',
+      showLabel: false,
+      labelFontSize: 13,
+      labelPadding: 6,
+      labelFontColor: '#666666',
+      labelBgColor: '#DFE8FF',
+      labelBgOpacity: 0.8,
+      labelAlign: 'left',
+      labelOffsetX: 0,
+      labelOffsetY: 0,
+    }, points[i]);
+    if (markLineOption.type == 'dash') {
+      context.setLineDash([markLineOption.dashLength, markLineOption.dashLength]);
+    }
+    context.setStrokeStyle(item.lineColor);
+    context.setLineWidth(1 * opts.pix);
+    context.beginPath();
+    context.moveTo(startX, item.y);
+    context.lineTo(endX, item.y);
+    context.stroke();
+    context.setLineDash([]);
+    if (item.showLabel) {
+      let fontSize = item.labelFontSize * opts.pix;
+      let labelText = item.labelText ? item.labelText : item.value;
+      context.setFontSize(fontSize);
+      let textWidth = measureText(labelText, fontSize, context);
+      let bgWidth = textWidth + item.labelPadding * opts.pix * 2;
+      let bgStartX = item.labelAlign == 'left' ? opts.area[3] - bgWidth : opts.width - opts.area[1];
+      bgStartX += item.labelOffsetX;
+      let bgStartY = item.y - 0.5 * fontSize - item.labelPadding * opts.pix;
+      bgStartY += item.labelOffsetY;
+      let textX = bgStartX + item.labelPadding * opts.pix;
+      let textY = item.y;
+      context.setFillStyle(hexToRgb(item.labelBgColor, item.labelBgOpacity));
+      context.setStrokeStyle(item.labelBgColor);
+      context.setLineWidth(1 * opts.pix);
+      context.beginPath();
+      context.rect(bgStartX, bgStartY, bgWidth, fontSize + 2 * item.labelPadding * opts.pix);
+      context.closePath();
+      context.stroke();
+      context.fill();
+      context.setFontSize(fontSize);
+      context.setTextAlign('left');
+      context.setFillStyle(item.labelFontColor);
+      context.fillText(String(labelText), textX, bgStartY + fontSize + item.labelPadding * opts.pix/2);
+      context.stroke();
+      context.setTextAlign('left');
+    }
+  }
+}
+
+function drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints) {
+  let toolTipOption = assign({}, {
+    gridType: 'solid',
+    dashLength: 4
+  }, opts.extra.tooltip);
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  if (toolTipOption.gridType == 'dash') {
+    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);
+  }
+  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');
+  context.setLineWidth(1 * opts.pix);
+  context.beginPath();
+  context.moveTo(startX, opts.tooltip.offset.y);
+  context.lineTo(endX, opts.tooltip.offset.y);
+  context.stroke();
+  context.setLineDash([]);
+  if (toolTipOption.yAxisLabel) {
+    let boxPadding = toolTipOption.boxPadding * opts.pix;
+    let labelText = calTooltipYAxisData(opts.tooltip.offset.y, opts.series, opts, config, eachSpacing);
+    let widthArr = opts.chartData.yAxisData.yAxisWidth;
+    let tStartLeft = opts.area[3];
+    let tStartRight = opts.width - opts.area[1];
+    for (let i = 0; i < labelText.length; i++) {
+      context.setFontSize(toolTipOption.fontSize * opts.pix);
+      let textWidth = measureText(labelText[i], toolTipOption.fontSize * opts.pix, context);
+      let bgStartX, bgEndX, bgWidth;
+      if (widthArr[i].position == 'left') {
+        bgStartX = tStartLeft - (textWidth + boxPadding * 2) - 2 * opts.pix;
+        bgEndX = Math.max(bgStartX, bgStartX + textWidth + boxPadding * 2);
+      } else {
+        bgStartX = tStartRight + 2 * opts.pix;
+        bgEndX = Math.max(bgStartX + widthArr[i].width, bgStartX + textWidth + boxPadding * 2);
+      }
+      bgWidth = bgEndX - bgStartX;
+      let textX = bgStartX + (bgWidth - textWidth) / 2;
+      let textY = opts.tooltip.offset.y;
+      context.beginPath();
+      context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));
+      context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);
+      context.setLineWidth(1 * opts.pix);
+      context.rect(bgStartX, textY - 0.5 * config.fontSize - boxPadding, bgWidth, config.fontSize + 2 * boxPadding);
+      context.closePath();
+      context.stroke();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(config.fontSize);
+      context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);
+      context.fillText(labelText[i], textX, textY + 0.5 * config.fontSize);
+      context.closePath();
+      context.stroke();
+      if (widthArr[i].position == 'left') {
+        tStartLeft -= (widthArr[i].width + opts.yAxis.padding * opts.pix);
+      } else {
+        tStartRight += widthArr[i].width + opts.yAxis.padding * opts.pix;
+      }
+    }
+  }
+}
+
+function drawToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {
+  let toolTipOption = assign({}, {
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08,
+    activeWidth: eachSpacing
+  }, opts.extra.column);
+  toolTipOption.activeWidth = toolTipOption.activeWidth > eachSpacing ? eachSpacing : toolTipOption.activeWidth;
+  let startY = opts.area[0];
+  let endY = opts.height - opts.area[2];
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect(offsetX - toolTipOption.activeWidth / 2, startY, toolTipOption.activeWidth, endY - startY);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+function drawBarToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {
+  let toolTipOption = assign({}, {
+    activeBgColor: '#000000',
+    activeBgOpacity: 0.08
+  }, opts.extra.bar);
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));
+  context.rect( startX ,offsetX - eachSpacing / 2 ,  endX - startX,eachSpacing);
+  context.closePath();
+  context.fill();
+  context.setFillStyle("#FFFFFF");
+}
+
+
+function drawToolTip(textList, offset, opts, config, context, eachSpacing, xAxisPoints) {
+  let toolTipOption = assign({}, {
+    showBox: true,
+    showArrow: true,
+    showCategory: false,
+    bgColor: '#000000',
+    bgOpacity: 0.7,
+    borderColor: '#000000',
+    borderWidth: 0,
+    borderRadius: 0,
+    borderOpacity: 0.7,
+    boxPadding: 3,
+    fontColor: '#FFFFFF',
+    fontSize: 13,
+    lineHeight: 20,
+    legendShow: true,
+    legendShape: 'auto',
+    splitLine: true,
+  }, opts.extra.tooltip);
+  if(toolTipOption.showCategory==true && opts.categories){
+    textList.unshift({text:opts.categories[opts.tooltip.index],color:null})
+  }
+  let fontSize = toolTipOption.fontSize * opts.pix;
+  let lineHeight = toolTipOption.lineHeight * opts.pix;
+  let boxPadding = toolTipOption.boxPadding * opts.pix;
+  let legendWidth = fontSize;
+  let legendMarginRight = 5 * opts.pix;
+  if(toolTipOption.legendShow == false){
+    legendWidth = 0;
+    legendMarginRight = 0;
+  }
+  let arrowWidth = toolTipOption.showArrow ? 8 * opts.pix : 0;
+  let isOverRightBorder = false;
+  if (opts.type == 'line' || opts.type == 'mount' || opts.type == 'area' || opts.type == 'candle' || opts.type == 'mix') {
+    if (toolTipOption.splitLine == true) {
+      drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context);
+    }
+  }
+  offset = assign({
+    x: 0,
+    y: 0
+  }, offset);
+  offset.y -= 8 * opts.pix;
+  let textWidth = textList.map(function(item) {
+    return measureText(item.text, fontSize, context);
+  });
+  let toolTipWidth = legendWidth + legendMarginRight + 4 * boxPadding + Math.max.apply(null, textWidth);
+  let toolTipHeight = 2 * boxPadding + textList.length * lineHeight;
+  if (toolTipOption.showBox == false) {
+    return
+  }
+  // if beyond the right border
+  if (offset.x - Math.abs(opts._scrollDistance_ || 0) + arrowWidth + toolTipWidth > opts.width) {
+    isOverRightBorder = true;
+  }
+  if (toolTipHeight + offset.y > opts.height) {
+    offset.y = opts.height - toolTipHeight;
+  }
+  // draw background rect
+  context.beginPath();
+  context.setFillStyle(hexToRgb(toolTipOption.bgColor, toolTipOption.bgOpacity));
+  context.setLineWidth(toolTipOption.borderWidth * opts.pix);
+  context.setStrokeStyle(hexToRgb(toolTipOption.borderColor, toolTipOption.borderOpacity));
+  let radius = toolTipOption.borderRadius;
+  if (isOverRightBorder) {
+    // 增加左侧仍然超出的判断
+    if(toolTipWidth + arrowWidth > opts.width){
+      offset.x = opts.width + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width)
+    }
+    if(toolTipWidth > offset.x){
+      offset.x = opts.width + Math.abs(opts._scrollDistance_ || 0) + arrowWidth + (toolTipWidth - opts.width)
+    }
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+    }
+    context.arc(offset.x - arrowWidth - radius, offset.y + toolTipHeight - radius, radius, 0, Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + toolTipHeight - radius, radius,
+      Math.PI / 2, Math.PI, false);
+    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x - arrowWidth - radius, offset.y + radius, radius, -Math.PI / 2, 0, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  } else {
+    if (toolTipOption.showArrow) {
+      context.moveTo(offset.x, offset.y + 10 * opts.pix);
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);
+    }
+    context.arc(offset.x + arrowWidth + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + radius, radius, -Math.PI / 2, 0,
+      false);
+    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + toolTipHeight - radius, radius, 0,
+      Math.PI / 2, false);
+    context.arc(offset.x + arrowWidth + radius, offset.y + toolTipHeight - radius, radius, Math.PI / 2, Math.PI, false);
+    if (toolTipOption.showArrow) {
+      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);
+      context.lineTo(offset.x, offset.y + 10 * opts.pix);
+    }
+  }
+  context.closePath();
+  context.fill();
+  if (toolTipOption.borderWidth > 0) {
+    context.stroke();
+  }
+  // draw legend
+  if(toolTipOption.legendShow){
+    textList.forEach(function(item, index) {
+      if (item.color !== null) {
+        context.beginPath();
+        context.setFillStyle(item.color);
+        let startX = offset.x + arrowWidth + 2 * boxPadding;
+        let startY = offset.y + (lineHeight - fontSize) / 2 + lineHeight * index + boxPadding + 1;
+        if (isOverRightBorder) {
+          startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding;
+        }
+        switch (item.legendShape) {
+          case 'line':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 2 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 2 * opts.pix, legendWidth, 4 * opts.pix);
+            break;
+          case 'triangle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'diamond':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth + 5 * opts.pix);
+            context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            break;
+          case 'circle':
+            context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth);
+            context.arc(startX + 7.5 * opts.pix, startY + 0.5 * legendWidth, 5 * opts.pix, 0, 2 * Math.PI);
+            break;
+          case 'rect':
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+            break;
+          case 'square':
+            context.moveTo(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX + 2 * opts.pix, startY + 0.5 * legendWidth - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+            break;
+          default:
+            context.moveTo(startX, startY + 0.5 * legendWidth - 5 * opts.pix);
+            context.fillRect(startX, startY + 0.5 * legendWidth - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+        }
+        context.closePath();
+        context.fill();
+      }
+    });
+  }
+  
+  // draw text list
+  textList.forEach(function(item, index) {
+    let startX = offset.x + arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    if (isOverRightBorder) {
+      startX = offset.x - toolTipWidth - arrowWidth + 2 * boxPadding + legendWidth + legendMarginRight;
+    }
+    let startY = offset.y + lineHeight * index + (lineHeight - fontSize)/2 - 1 + boxPadding + fontSize;
+    context.beginPath();
+    context.setFontSize(fontSize);
+    context.setTextBaseline('normal');
+    context.setFillStyle(toolTipOption.fontColor);
+    context.fillText(item.text, startX, startY);
+    context.closePath();
+    context.stroke();
+  });
+}
+
+function drawColumnDataPoints(series, opts, config, context) {
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let columnOption = assign({}, {
+    type: 'group',
+    width: eachSpacing / 2,
+    meterBorder: 4,
+    meterFillColor: '#FFFFFF',
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+    labelPosition: 'outside'
+  }, opts.extra.column);
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+  }
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTipSplitArea(opts.tooltip.offset.x, opts, config, context, eachSpacing);
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    
+    // 计算0轴坐标
+    let spacingValid = opts.height - opts.area[0] - opts.area[2];
+    let zeroHeight = spacingValid * (0 - minRange) / (maxRange - minRange);
+    let zeroPoints = opts.height - Math.round(zeroHeight) - opts.area[2];
+    eachSeries.zeroPoints = zeroPoints;
+    let data = eachSeries.data;
+    switch (columnOption.type) {
+      case 'group':
+        let points = getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, zeroPoints, chartProcess);
+        let tooltipPoints = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+        calPoints.push(tooltipPoints);
+        points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          //fix issues/I27B1N yyoinge & Joeshu
+          if (item !== null && i > leftNum && i < rightNum) {
+            let startX = item.x - item.width / 2;
+            let height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color
+            let strokeColor = item.color || eachSeries.color
+            if (columnOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+              const left = startX;
+              const top = item.y > zeroPoints ? zeroPoints : item.y;
+              const width = item.width;
+              const height = Math.abs(zeroPoints - item.y);
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              if(item.y > zeroPoints){
+                columnOption.barBorderRadius = [0, 0,width / 2, width / 2];
+              }
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width, item.y);
+              context.lineTo(startX + item.width, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+              context.setLineWidth(1)
+              context.setStrokeStyle(strokeColor);
+            }
+            context.setFillStyle(fillColor);
+            context.closePath();
+            //context.stroke();
+            context.fill();
+          }
+        };
+        break;
+      case 'stack':
+        // 绘制堆叠数据图
+        let points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+        calPoints.push(points);
+        points = fixColumeStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color;
+            let startX = item.x - item.width / 2 + 1;
+            let height = opts.height - item.y - opts.area[2];
+            let height0 = opts.height - item.y0 - opts.area[2];
+            if (seriesIndex > 0) {
+              height -= height0;
+            }
+            context.setFillStyle(fillColor);
+            context.moveTo(startX, item.y);
+            context.fillRect(startX, item.y, item.width, height);
+            context.closePath();
+            context.fill();
+          }
+        };
+        break;
+      case 'meter':
+        // 绘制温度计数据图
+        let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+        calPoints.push(points);
+        points = fixColumeMeterData(points, eachSpacing, series.length, seriesIndex, config, opts, columnOption.meterBorder);
+          for (let i = 0; i < points.length; i++) {
+            let item = points[i];
+            if (item !== null && i > leftNum && i < rightNum) {
+              //画背景颜色
+              context.beginPath();
+              if (seriesIndex == 0 && columnOption.meterBorder > 0) {
+                context.setStrokeStyle(eachSeries.color);
+                context.setLineWidth(columnOption.meterBorder * opts.pix);
+              }
+              if(seriesIndex == 0){
+                context.setFillStyle(columnOption.meterFillColor);
+              }else{
+                context.setFillStyle(item.color || eachSeries.color);
+              }
+              let startX = item.x - item.width / 2;
+              let height = opts.height - item.y - opts.area[2];
+              if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+                const left = startX;
+                const top = item.y;
+                const width = item.width;
+                const height = zeroPoints - item.y;
+                if (columnOption.barBorderCircle) {
+                  columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+                }
+                let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+                let minRadius = Math.min(width/2,height/2);
+                r0 = r0 > minRadius ? minRadius : r0;
+                r1 = r1 > minRadius ? minRadius : r1;
+                r2 = r2 > minRadius ? minRadius : r2;
+                r3 = r3 > minRadius ? minRadius : r3;
+                r0 = r0 < 0 ? 0 : r0;
+                r1 = r1 < 0 ? 0 : r1;
+                r2 = r2 < 0 ? 0 : r2;
+                r3 = r3 < 0 ? 0 : r3;
+                context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+                context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+                context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+                context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+                context.fill();
+              }else{
+                context.moveTo(startX, item.y);
+                context.lineTo(startX + item.width, item.y);
+                context.lineTo(startX + item.width, zeroPoints);
+                context.lineTo(startX, zeroPoints);
+                context.lineTo(startX, item.y);
+                context.fill();
+              }
+              if (seriesIndex == 0 && columnOption.meterBorder > 0) {
+                context.closePath();
+                context.stroke();
+              }
+            }
+          }
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      switch (columnOption.type) {
+        case 'group':
+          let points = getColumnDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+          points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        case 'stack':
+          let points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+        case 'meter':
+          let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+          drawColumePointText(points, eachSeries, config, context, opts);
+          break;
+      }
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawMountDataPoints(series, opts, config, context) {
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let mountOption = assign({}, {
+    type: 'mount',
+    widthRatio: 1,
+    borderWidth: 1,
+    barBorderCircle: false,
+    barBorderRadius: [],
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.mount);
+  mountOption.widthRatio = mountOption.widthRatio <= 0 ? 0 : mountOption.widthRatio;
+  mountOption.widthRatio = mountOption.widthRatio >= 2 ? 2 : mountOption.widthRatio;
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+  }
+  mountOption.customColor = fillCustomColor(mountOption.linearType, mountOption.customColor, series, config);
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    
+    // 计算0轴坐标
+    let spacingValid = opts.height - opts.area[0] - opts.area[2];
+    let zeroHeight = spacingValid * (0 - minRange) / (maxRange - minRange);
+    let zeroPoints = opts.height - Math.round(zeroHeight) - opts.area[2];
+    
+    let points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints, chartProcess);
+    switch (mountOption.type) {
+      case 'bar':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            let startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            let height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            let fillColor = item.color || series[i].color
+            let strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((mountOption.barBorderRadius && mountOption.barBorderRadius.length === 4) || mountOption.barBorderCircle === true) {
+              const left = startX;
+              const top = item.y > zeroPoints ? zeroPoints : item.y;
+              const width = item.width;
+              const height = Math.abs(zeroPoints - item.y);
+              if (mountOption.barBorderCircle) {
+                mountOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              if(item.y > zeroPoints){
+                mountOption.barBorderRadius = [0, 0,width / 2, width / 2];
+              }
+              let [r0, r1, r2, r3] = mountOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, item.y);
+              context.lineTo(startX + item.width, item.y);
+              context.lineTo(startX + item.width, zeroPoints);
+              context.lineTo(startX, zeroPoints);
+              context.lineTo(startX, item.y);
+            }
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.closePath();
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'triangle':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            let startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            let height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            let fillColor = item.color || series[i].color
+            let strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.lineTo(item.x, item.y);
+            context.lineTo(startX + item.width, zeroPoints);
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'mount':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            let startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            let height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            let fillColor = item.color || series[i].color
+            let strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.bezierCurveTo(item.x - item.width/4, zeroPoints, item.x - item.width/4, item.y, item.x, item.y);
+            context.bezierCurveTo(item.x + item.width/4, item.y, item.x + item.width/4, zeroPoints, startX + item.width, zeroPoints);
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+      case 'sharp':
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            let startX = item.x - eachSpacing*mountOption.widthRatio/2;
+            let height = opts.height - item.y - opts.area[2];
+            context.beginPath();
+            let fillColor = item.color || series[i].color
+            let strokeColor = item.color || series[i].color
+            if (mountOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
+              //透明渐变
+              if (mountOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));
+                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            context.moveTo(startX, zeroPoints);
+            context.quadraticCurveTo(item.x - 0, zeroPoints - height/4, item.x, item.y);
+            context.quadraticCurveTo(item.x + 0, zeroPoints - height/4, startX + item.width, zeroPoints)
+            context.setStrokeStyle(strokeColor);
+            context.setFillStyle(fillColor);
+            if(mountOption.borderWidth > 0){
+              context.setLineWidth(mountOption.borderWidth * opts.pix);
+              context.stroke();
+            }
+            context.fill();
+          }
+        };
+        break;
+    }
+
+  if (opts.dataLabel !== false && process === 1) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints, chartProcess);
+    drawMountPointText(points, series, config, context, opts, zeroPoints);
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: points,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawBarDataPoints(series, opts, config, context) {
+  let yAxisPoints = [];
+  let eachSpacing = (opts.height - opts.area[0] - opts.area[2])/opts.categories.length;
+  for (let i = 0; i < opts.categories.length; i++) {
+    yAxisPoints.push(opts.area[0] + eachSpacing / 2 + eachSpacing * i);
+  }
+  let columnOption = assign({}, {
+    type: 'group',
+    width: eachSpacing / 2,
+    meterBorder: 4,
+    meterFillColor: '#FFFFFF',
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.bar);
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = yAxisPoints.length + 2;
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawBarToolTipSplitArea(opts.tooltip.offset.y, opts, config, context, eachSpacing);
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.xAxisData.ranges);
+    maxRange = ranges.pop();
+    minRange = ranges.shift();
+    let data = eachSeries.data;
+    switch (columnOption.type) {
+      case 'group':
+        let points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, chartProcess);
+        let tooltipPoints = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+        calPoints.push(tooltipPoints);
+        points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          //fix issues/I27B1N yyoinge & Joeshu
+          if (item !== null && i > leftNum && i < rightNum) {
+            //let startX = item.x - item.width / 2;
+            let startX = opts.area[3];
+            let startY = item.y - item.width / 2;
+            let height = item.height;
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color
+            let strokeColor = item.color || eachSeries.color
+            if (columnOption.linearType !== 'none') {
+              let grd = context.createLinearGradient(startX, item.y, item.x, item.y);
+              //透明渐变
+              if (columnOption.linearType == 'opacity') {
+                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              } else {
+                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));
+                grd.addColorStop(1, hexToRgb(fillColor, 1));
+              }
+              fillColor = grd
+            }
+            // 圆角边框
+            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {
+              const left = startX;
+              const width = item.width;
+              const top = item.y - item.width / 2;
+              const height = item.height;
+              if (columnOption.barBorderCircle) {
+                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+              }
+              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+              let minRadius = Math.min(width/2,height/2);
+              r0 = r0 > minRadius ? minRadius : r0;
+              r1 = r1 > minRadius ? minRadius : r1;
+              r2 = r2 > minRadius ? minRadius : r2;
+              r3 = r3 > minRadius ? minRadius : r3;
+              r0 = r0 < 0 ? 0 : r0;
+              r1 = r1 < 0 ? 0 : r1;
+              r2 = r2 < 0 ? 0 : r2;
+              r3 = r3 < 0 ? 0 : r3;
+              
+              context.arc(left + r3, top + r3, r3, -Math.PI, -Math.PI / 2);
+              context.arc(item.x - r0, top + r0, r0, -Math.PI / 2, 0);
+              context.arc(item.x - r1, top + width - r1, r1, 0, Math.PI / 2);
+              context.arc(left + r2, top + width - r2, r2, Math.PI / 2, Math.PI);
+            } else {
+              context.moveTo(startX, startY);
+              context.lineTo(item.x, startY);
+              context.lineTo(item.x, startY + item.width);
+              context.lineTo(startX, startY + item.width);
+              context.lineTo(startX, startY);
+              context.setLineWidth(1)
+              context.setStrokeStyle(strokeColor);
+            }
+            context.setFillStyle(fillColor);
+            context.closePath();
+            //context.stroke();
+            context.fill();
+          }
+        };
+        break;
+      case 'stack':
+        // 绘制堆叠数据图
+        let points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+        calPoints.push(points);
+        points = fixBarStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);
+        for (let i = 0; i < points.length; i++) {
+          let item = points[i];
+          if (item !== null && i > leftNum && i < rightNum) {
+            context.beginPath();
+            let fillColor = item.color || eachSeries.color;
+            let startX = item.x0;
+            context.setFillStyle(fillColor);
+            context.moveTo(startX, item.y - item.width/2);
+            context.fillRect(startX, item.y - item.width/2, item.height , item.width);
+            context.closePath();
+            context.fill();
+          }
+        };
+        break;
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.xAxisData.ranges);
+      maxRange = ranges.pop();
+      minRange = ranges.shift();
+      let data = eachSeries.data;
+      switch (columnOption.type) {
+        case 'group':
+          let points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, chartProcess);
+          points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
+          drawBarPointText(points, eachSeries, config, context, opts);
+          break;
+        case 'stack':
+          let points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, chartProcess);
+          drawBarPointText(points, eachSeries, config, context, opts);
+          break;
+      }
+    });
+  }
+  return {
+    yAxisPoints: yAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawCandleDataPoints(series, seriesMA, opts, config, context) {
+  let candleOption = assign({}, {
+    color: {},
+    average: {}
+  }, opts.extra.candle);
+  candleOption.color = assign({}, {
+    upLine: '#f04864',
+    upFill: '#f04864',
+    downLine: '#2fc25b',
+    downFill: '#2fc25b'
+  }, candleOption.color);
+  candleOption.average = assign({}, {
+    show: false,
+    name: [],
+    day: [],
+    color: config.color
+  }, candleOption.average);
+  opts.extra.candle = candleOption;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let calPoints = [];
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  //画均线
+  if (candleOption.average.show || seriesMA) { //Merge pull request !12 from 邱贵翔
+    seriesMA.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+      let splitPointList = splitPoints(points,eachSeries);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(1);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              let ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x,
+                item.y);
+            }
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+  //画K线
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+    calPoints.push(points);
+    let splitPointList = splitPoints(points,eachSeries);
+    for (let i = 0; i < splitPointList[0].length; i++) {
+      if (i > leftNum && i < rightNum) {
+        let item = splitPointList[0][i];
+        context.beginPath();
+        //如果上涨
+        if (data[i][1] - data[i][0] > 0) {
+          context.setStrokeStyle(candleOption.color.upLine);
+          context.setFillStyle(candleOption.color.upFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); //顶点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[2].x, item[2].y); //底点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.moveTo(item[3].x, item[3].y); //顶点
+        } else {
+          context.setStrokeStyle(candleOption.color.downLine);
+          context.setFillStyle(candleOption.color.downFill);
+          context.setLineWidth(1 * opts.pix);
+          context.moveTo(item[3].x, item[3].y); //顶点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点
+          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[2].x, item[2].y); //底点
+          context.lineTo(item[1].x, item[1].y); //收盘中间点
+          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点
+          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点
+          context.lineTo(item[0].x, item[0].y); //开盘中间点
+          context.moveTo(item[3].x, item[3].y); //顶点
+        }
+        context.closePath();
+        context.fill();
+        context.stroke();
+      }
+    }
+  });
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawAreaDataPoints(series, opts, config, context) {
+  let areaOption = assign({}, {
+    type: 'straight',
+    opacity: 0.2,
+    addLine: false,
+    width: 2,
+    gradient: false,
+    activeType: 'none'
+  }, opts.extra.area);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let endY = opts.height - opts.area[2];
+  let calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+    calPoints.push(points);
+    let splitPointList = splitPoints(points,eachSeries);
+    for (let i = 0; i < splitPointList.length; i++) {
+      let points = splitPointList[i];
+      // 绘制区域数
+      context.beginPath();
+      context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+      if (areaOption.gradient) {
+        let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
+        gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
+        gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+        context.setFillStyle(gradient);
+      } else {
+        context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+      }
+      context.setLineWidth(areaOption.width * opts.pix);
+      if (points.length > 1) {
+        let firstPoint = points[0];
+        let lastPoint = points[points.length - 1];
+        context.moveTo(firstPoint.x, firstPoint.y);
+        let startPoint = 0;
+        if (areaOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              let ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          };
+        } 
+        if (areaOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        if (areaOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        context.lineTo(lastPoint.x, endY);
+        context.lineTo(firstPoint.x, endY);
+        context.lineTo(firstPoint.x, firstPoint.y);
+      } else {
+        let item = points[0];
+        context.moveTo(item.x - eachSpacing / 2, item.y);
+        // context.lineTo(item.x + eachSpacing / 2, item.y);
+        // context.lineTo(item.x + eachSpacing / 2, endY);
+        // context.lineTo(item.x - eachSpacing / 2, endY);
+        // context.moveTo(item.x - eachSpacing / 2, item.y);
+      }
+      context.closePath();
+      context.fill();
+      //画连线
+      if (areaOption.addLine) {
+        if (eachSeries.lineType == 'dash') {
+          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+          dashLength *= opts.pix;
+          context.setLineDash([dashLength, dashLength]);
+        }
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(areaOption.width * opts.pix);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          if (areaOption.type === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            };
+          }
+          if (areaOption.type === 'straight') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          if (areaOption.type === 'step') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, points[j - 1].y);
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.stroke();
+        context.setLineDash([]);
+      }
+    }
+    //画点
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+    drawActivePoint(points, eachSeries.color, eachSeries.pointShape, context, opts, areaOption,seriesIndex);
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawScatterDataPoints(series, opts, config, context) {
+  let scatterOption = assign({}, {
+    type: 'circle'
+  }, opts.extra.scatter);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setFillStyle(eachSeries.color);
+    context.setLineWidth(1 * opts.pix);
+    let shape = eachSeries.pointShape;
+    if (shape === 'diamond') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y);
+          context.lineTo(item.x, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else if (shape === 'circle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x + 2.5 * opts.pix, item.y);
+          context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);
+        }
+      });
+    } else if (shape === 'square') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x - 3.5, item.y - 3.5);
+          context.rect(item.x - 3.5, item.y - 3.5, 7, 7);
+        }
+      });
+    } else if (shape === 'triangle') {
+      points.forEach(function(item, index) {
+        if (item !== null) {
+          context.moveTo(item.x, item.y - 4.5);
+          context.lineTo(item.x - 4.5, item.y + 4.5);
+          context.lineTo(item.x + 4.5, item.y + 4.5);
+          context.lineTo(item.x, item.y - 4.5);
+        }
+      });
+    } else if (shape === 'triangle') {
+      return;
+    }
+    context.closePath();
+    context.fill();
+    context.stroke();
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawBubbleDataPoints(series, opts, config, context) {
+  let bubbleOption = assign({}, {
+    opacity: 1,
+    border:2
+  }, opts.extra.bubble);
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.setLineWidth(bubbleOption.border * opts.pix);
+    context.setFillStyle(hexToRgb(eachSeries.color, bubbleOption.opacity));
+    points.forEach(function(item, index) {
+      context.moveTo(item.x + item.r, item.y);
+      context.arc(item.x, item.y, item.r * opts.pix, 0, 2 * Math.PI, false);
+    });
+    context.closePath();
+    context.fill();
+    context.stroke();
+    
+    if (opts.dataLabel !== false && process === 1) {
+      points.forEach(function(item, index) {
+        context.beginPath();
+        let fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+        context.setFontSize(fontSize);
+        context.setFillStyle(eachSeries.textColor || "#FFFFFF");
+        context.setTextAlign('center');
+        context.fillText(String(item.t), item.x, item.y + fontSize/2);
+        context.closePath();
+        context.stroke();
+        context.setTextAlign('left');
+      });
+    }
+  });
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawLineDataPoints(series, opts, config, context) {
+  let lineOption = assign({}, {
+    type: 'straight',
+    width: 2,
+    activeType: 'none',
+    linearType: 'none',
+    onShadow: false,
+    animation: 'vertical',
+  }, opts.extra.line);
+  lineOption.width *= opts.pix;
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let calPoints = [];
+  context.save();
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  series.forEach(function(eachSeries, seriesIndex) {
+    // 这段很神奇的代码用于解决ios16的setStrokeStyle失效的bug
+    context.beginPath();
+    context.setStrokeStyle(eachSeries.color);
+    context.moveTo(-10000, -10000);
+    context.lineTo(-10001, -10001);
+    context.stroke();
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getLineDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, lineOption, chartProcess);
+    calPoints.push(points);
+    let splitPointList = splitPoints(points,eachSeries);
+    if (eachSeries.lineType == 'dash') {
+      let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+      dashLength *= opts.pix;
+      context.setLineDash([dashLength, dashLength]);
+    }
+    context.beginPath();
+    let strokeColor = eachSeries.color;
+    if (lineOption.linearType !== 'none' && eachSeries.linearColor && eachSeries.linearColor.length > 0) {
+      let grd = context.createLinearGradient(opts.chartData.xAxisData.startX, opts.height/2, opts.chartData.xAxisData.endX, opts.height/2);
+      for (let i = 0; i < eachSeries.linearColor.length; i++) {
+        grd.addColorStop(eachSeries.linearColor[i][0], hexToRgb(eachSeries.linearColor[i][1], 1));
+      }
+      strokeColor = grd
+    }
+    context.setStrokeStyle(strokeColor);
+    if (lineOption.onShadow == true && eachSeries.setShadow && eachSeries.setShadow.length > 0) {
+      context.setShadow(eachSeries.setShadow[0], eachSeries.setShadow[1], eachSeries.setShadow[2], eachSeries.setShadow[3]);
+    }else{
+      context.setShadow(0, 0, 0, 'rgba(0,0,0,0)');
+    }
+    context.setLineWidth(lineOption.width);
+    splitPointList.forEach(function(points, index) {
+      if (points.length === 1) {
+        context.moveTo(points[0].x, points[0].y);
+        // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+      } else {
+        context.moveTo(points[0].x, points[0].y);
+        let startPoint = 0;
+        if (lineOption.type === 'curve') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              let ctrlPoint = createCurveControlPoints(points, j - 1);
+              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+            }
+          };
+        }
+        if (lineOption.type === 'straight') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        if (lineOption.type === 'step') {
+          for (let j = 0; j < points.length; j++) {
+            let item = points[j];
+            if (startPoint == 0 && item.x > leftSpace) {
+              context.moveTo(item.x, item.y);
+              startPoint = 1;
+            }
+            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+              context.lineTo(item.x, points[j - 1].y);
+              context.lineTo(item.x, item.y);
+            }
+          };
+        }
+        context.moveTo(points[0].x, points[0].y);
+      }
+    });
+    context.stroke();
+    context.setLineDash([]);
+    if (opts.dataPointShape !== false) {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+    drawActivePoint(points, eachSeries.color, eachSeries.pointShape, context, opts, lineOption);
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+      drawPointText(points, eachSeries, config, context, opts);
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing
+  };
+}
+
+function drawMixDataPoints(series, opts, config, context) {
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    eachSpacing = xAxisData.eachSpacing;
+  let columnOption = assign({}, {
+    width: eachSpacing / 2,
+    barBorderCircle: false,
+    barBorderRadius: [],
+    seriesGap: 2,
+    linearType: 'none',
+    linearOpacity: 1,
+    customColor: [],
+    colorStop: 0,
+  }, opts.extra.mix.column);
+  let areaOption = assign({}, {
+    opacity: 0.2,
+    gradient: false
+  }, opts.extra.mix.area);
+  let lineOption = assign({}, {
+    width: 2
+  }, opts.extra.mix.line);
+  let endY = opts.height - opts.area[2];
+  let calPoints = [];
+  let columnIndex = 0;
+  let columnLength = 0;
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (eachSeries.type == 'column') {
+      columnLength += 1;
+    }
+  });
+  context.save();
+  let leftNum = -2;
+  let rightNum = xAxisPoints.length + 2;
+  let leftSpace = 0;
+  let rightSpace = opts.width + eachSpacing;
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
+    rightNum = leftNum + opts.xAxis.itemCount + 4;
+    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];
+    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;
+  }
+  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);
+  series.forEach(function(eachSeries, seriesIndex) {
+    let ranges, minRange, maxRange;
+    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+    minRange = ranges.pop();
+    maxRange = ranges.shift();
+    let data = eachSeries.data;
+    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+    calPoints.push(points);
+    // 绘制柱状数据图
+    if (eachSeries.type == 'column') {
+      points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);
+      for (let i = 0; i < points.length; i++) {
+        let item = points[i];
+        if (item !== null && i > leftNum && i < rightNum) {
+          let startX = item.x - item.width / 2;
+          let height = opts.height - item.y - opts.area[2];
+          context.beginPath();
+          let fillColor = item.color || eachSeries.color
+          let strokeColor = item.color || eachSeries.color
+          if (columnOption.linearType !== 'none') {
+            let grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);
+            //透明渐变
+            if (columnOption.linearType == 'opacity') {
+              grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            } else {
+              grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+              grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));
+              grd.addColorStop(1, hexToRgb(fillColor, 1));
+            }
+            fillColor = grd
+          }
+          // 圆角边框
+          if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle) {
+            const left = startX;
+            const top = item.y;
+            const width = item.width;
+            const height = opts.height - opts.area[2] - item.y;
+            if (columnOption.barBorderCircle) {
+              columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
+            }
+            let [r0, r1, r2, r3] = columnOption.barBorderRadius;
+            let minRadius = Math.min(width/2,height/2);
+            r0 = r0 > minRadius ? minRadius : r0;
+            r1 = r1 > minRadius ? minRadius : r1;
+            r2 = r2 > minRadius ? minRadius : r2;
+            r3 = r3 > minRadius ? minRadius : r3;
+            r0 = r0 < 0 ? 0 : r0;
+            r1 = r1 < 0 ? 0 : r1;
+            r2 = r2 < 0 ? 0 : r2;
+            r3 = r3 < 0 ? 0 : r3;
+            context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
+            context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
+            context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
+            context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
+          } else {
+            context.moveTo(startX, item.y);
+            context.lineTo(startX + item.width, item.y);
+            context.lineTo(startX + item.width, opts.height - opts.area[2]);
+            context.lineTo(startX, opts.height - opts.area[2]);
+            context.lineTo(startX, item.y);
+            context.setLineWidth(1)
+            context.setStrokeStyle(strokeColor);
+          }
+          context.setFillStyle(fillColor);
+          context.closePath();
+          context.fill();
+        }
+      }
+      columnIndex += 1;
+    }
+    //绘制区域图数据
+    if (eachSeries.type == 'area') {
+      let splitPointList = splitPoints(points,eachSeries);
+      for (let i = 0; i < splitPointList.length; i++) {
+        let points = splitPointList[i];
+        // 绘制区域数据
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+        if (areaOption.gradient) {
+          let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);
+          gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));
+          gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+          context.setFillStyle(gradient);
+        } else {
+          context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));
+        }
+        context.setLineWidth(2 * opts.pix);
+        if (points.length > 1) {
+          let firstPoint = points[0];
+          let lastPoint = points[points.length - 1];
+          context.moveTo(firstPoint.x, firstPoint.y);
+          let startPoint = 0;
+          if (eachSeries.style === 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
+              }
+            };
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            };
+          }
+          context.lineTo(lastPoint.x, endY);
+          context.lineTo(firstPoint.x, endY);
+          context.lineTo(firstPoint.x, firstPoint.y);
+        } else {
+          let item = points[0];
+          context.moveTo(item.x - eachSpacing / 2, item.y);
+          // context.lineTo(item.x + eachSpacing / 2, item.y);
+          // context.lineTo(item.x + eachSpacing / 2, endY);
+          // context.lineTo(item.x - eachSpacing / 2, endY);
+          // context.moveTo(item.x - eachSpacing / 2, item.y);
+        }
+        context.closePath();
+        context.fill();
+      }
+    }
+    // 绘制折线数据图
+    if (eachSeries.type == 'line') {
+      let splitPointList = splitPoints(points,eachSeries);
+      splitPointList.forEach(function(points, index) {
+        if (eachSeries.lineType == 'dash') {
+          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;
+          dashLength *= opts.pix;
+          context.setLineDash([dashLength, dashLength]);
+        }
+        context.beginPath();
+        context.setStrokeStyle(eachSeries.color);
+        context.setLineWidth(lineOption.width * opts.pix);
+        if (points.length === 1) {
+          context.moveTo(points[0].x, points[0].y);
+          // context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);
+        } else {
+          context.moveTo(points[0].x, points[0].y);
+          let startPoint = 0;
+          if (eachSeries.style == 'curve') {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                let ctrlPoint = createCurveControlPoints(points, j - 1);
+                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y,
+                  item.x, item.y);
+              }
+            }
+          } else {
+            for (let j = 0; j < points.length; j++) {
+              let item = points[j];
+              if (startPoint == 0 && item.x > leftSpace) {
+                context.moveTo(item.x, item.y);
+                startPoint = 1;
+              }
+              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
+                context.lineTo(item.x, item.y);
+              }
+            }
+          }
+          context.moveTo(points[0].x, points[0].y);
+        }
+        context.stroke();
+        context.setLineDash([]);
+      });
+    }
+    // 绘制点数据图
+    if (eachSeries.type == 'point') {
+      eachSeries.addPoint = true;
+    }
+    if (eachSeries.addPoint == true && eachSeries.type !== 'column') {
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+  });
+  if (opts.dataLabel !== false && process === 1) {
+    let columnIndex = 0;
+    series.forEach(function(eachSeries, seriesIndex) {
+      let ranges, minRange, maxRange;
+      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);
+      minRange = ranges.pop();
+      maxRange = ranges.shift();
+      let data = eachSeries.data;
+      let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, chartProcess);
+      if (eachSeries.type !== 'column') {
+        drawPointText(points, eachSeries, config, context, opts);
+      } else {
+        points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);
+        drawPointText(points, eachSeries, config, context, opts);
+        columnIndex += 1;
+      }
+    });
+  }
+  context.restore();
+  return {
+    xAxisPoints: xAxisPoints,
+    calPoints: calPoints,
+    eachSpacing: eachSpacing,
+  }
+}
+
+
+function drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints) {
+  let toolTipOption = opts.extra.tooltip || {};
+  if (toolTipOption.horizentalLine && opts.tooltip && process === 1 && (opts.type == 'line' || opts.type == 'area' || opts.type == 'column' || opts.type == 'mount' || opts.type == 'candle' || opts.type == 'mix')) {
+    drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints)
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
+    drawToolTip(opts.tooltip.textList, opts.tooltip.offset, opts, config, context, eachSpacing, xAxisPoints);
+  }
+  context.restore();
+
+}
+
+function drawXAxis(categories, opts, config, context) {
+
+  let xAxisData = opts.chartData.xAxisData,
+    xAxisPoints = xAxisData.xAxisPoints,
+    startX = xAxisData.startX,
+    endX = xAxisData.endX,
+    eachSpacing = xAxisData.eachSpacing;
+  let boundaryGap = 'center';
+  if (opts.type == 'bar' || opts.type == 'line' || opts.type == 'area'|| opts.type == 'scatter' || opts.type == 'bubble') {
+    boundaryGap = opts.xAxis.boundaryGap;
+  }
+  let startY = opts.height - opts.area[2];
+  let endY = opts.area[0];
+
+  //绘制滚动条
+  if (opts.enableScroll && opts.xAxis.scrollShow) {
+    let scrollY = opts.height - opts.area[2] + config.xAxisHeight;
+    let scrollScreenWidth = endX - startX;
+    let scrollTotalWidth = eachSpacing * (xAxisPoints.length - 1);
+    if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+      if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+      scrollTotalWidth += (opts.extra.mount.widthRatio - 1)*eachSpacing;
+    }
+    let scrollWidth = scrollScreenWidth * scrollScreenWidth / scrollTotalWidth;
+    let scrollLeft = 0;
+    if (opts._scrollDistance_) {
+      scrollLeft = -opts._scrollDistance_ * (scrollScreenWidth) / scrollTotalWidth;
+    }
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollBackgroundColor || "#EFEBEF");
+    context.moveTo(startX, scrollY);
+    context.lineTo(endX, scrollY);
+    context.stroke();
+    context.closePath();
+    context.beginPath();
+    context.setLineCap('round');
+    context.setLineWidth(6 * opts.pix);
+    context.setStrokeStyle(opts.xAxis.scrollColor || "#A6A6A6");
+    context.moveTo(startX + scrollLeft, scrollY);
+    context.lineTo(startX + scrollLeft + scrollWidth, scrollY);
+    context.stroke();
+    context.closePath();
+    context.setLineCap('butt');
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  //绘制X轴刻度线
+  if (opts.xAxis.calibration === true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    xAxisPoints.forEach(function(item, index) {
+      if (index > 0) {
+        context.beginPath();
+        context.moveTo(item - eachSpacing / 2, startY);
+        context.lineTo(item - eachSpacing / 2, startY + 3 * opts.pix);
+        context.closePath();
+        context.stroke();
+      }
+    });
+  }
+  //绘制X轴网格
+  if (opts.xAxis.disableGrid !== true) {
+    context.setStrokeStyle(opts.xAxis.gridColor || "#cccccc");
+    context.setLineCap('butt');
+    context.setLineWidth(1 * opts.pix);
+    if (opts.xAxis.gridType == 'dash') {
+      context.setLineDash([opts.xAxis.dashLength * opts.pix, opts.xAxis.dashLength * opts.pix]);
+    }
+    opts.xAxis.gridEval = opts.xAxis.gridEval || 1;
+    xAxisPoints.forEach(function(item, index) {
+      if (index % opts.xAxis.gridEval == 0) {
+        context.beginPath();
+        context.moveTo(item, startY);
+        context.lineTo(item, endY);
+        context.stroke();
+      }
+    });
+    context.setLineDash([]);
+  }
+  //绘制X轴文案
+  if (opts.xAxis.disabled !== true) {
+    // 对X轴列表做抽稀处理
+    //默认全部显示X轴标签
+    let maxXAxisListLength = categories.length;
+    //如果设置了X轴单屏数量
+    if (opts.xAxis.labelCount) {
+      //如果设置X轴密度
+      if (opts.xAxis.itemCount) {
+        maxXAxisListLength = Math.ceil(categories.length / opts.xAxis.itemCount * opts.xAxis.labelCount);
+      } else {
+        maxXAxisListLength = opts.xAxis.labelCount;
+      }
+      maxXAxisListLength -= 1;
+    }
+
+    let ratio = Math.ceil(categories.length / maxXAxisListLength);
+
+    let newCategories = [];
+    let cgLength = categories.length;
+    for (let i = 0; i < cgLength; i++) {
+      if (i % ratio !== 0) {
+        newCategories.push("");
+      } else {
+        newCategories.push(categories[i]);
+      }
+    }
+    newCategories[cgLength - 1] = categories[cgLength - 1];
+    let xAxisFontSize = opts.xAxis.fontSize * opts.pix || config.fontSize;
+    if (config._xAxisTextAngle_ === 0) {
+      newCategories.forEach(function(item, index) {
+        let xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;
+        let offset = -measureText(String(xitem), xAxisFontSize, context) / 2;
+        if (boundaryGap == 'center') {
+          offset += eachSpacing / 2;
+        }
+        let scrollHeight = 0;
+        if (opts.xAxis.scrollShow) {
+          scrollHeight = 6 * opts.pix;
+        }
+        // 如果在主视图区域内
+        let _scrollDistance_ = opts._scrollDistance_ || 0;
+        let truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if((truePoints - Math.abs(_scrollDistance_)) >= (opts.area[3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width - opts.area[1] + 1)){
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);
+          context.fillText(String(xitem), xAxisPoints[index] + offset, startY + opts.xAxis.marginTop * opts.pix + (opts.xAxis.lineHeight - opts.xAxis.fontSize) * opts.pix / 2 + opts.xAxis.fontSize * opts.pix);
+          context.closePath();
+          context.stroke();
+        }
+      });
+    } else {
+      newCategories.forEach(function(item, index) {
+        let xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item) : item;
+        // 如果在主视图区域内
+        let _scrollDistance_ = opts._scrollDistance_ || 0;
+        let truePoints = boundaryGap == 'center' ? xAxisPoints[index] + eachSpacing / 2 : xAxisPoints[index];
+        if((truePoints - Math.abs(_scrollDistance_)) >= (opts.area[3] - 1) && (truePoints - Math.abs(_scrollDistance_)) <= (opts.width - opts.area[1] + 1)){
+          context.save();
+          context.beginPath();
+          context.setFontSize(xAxisFontSize);
+          context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);
+          let textWidth = measureText(String(xitem), xAxisFontSize, context);
+          let offsetX = xAxisPoints[index];
+          if (boundaryGap == 'center') {
+            offsetX = xAxisPoints[index] + eachSpacing / 2;
+          }
+          let scrollHeight = 0;
+          if (opts.xAxis.scrollShow) {
+            scrollHeight = 6 * opts.pix;
+          }
+          let offsetY = startY + opts.xAxis.marginTop * opts.pix + xAxisFontSize - xAxisFontSize * Math.abs(Math.sin(config._xAxisTextAngle_));
+          if(opts.xAxis.rotateAngle < 0){
+            offsetX -= xAxisFontSize / 2;
+            textWidth = 0;
+          }else{
+            offsetX += xAxisFontSize / 2;
+            textWidth = -textWidth;
+          }
+          context.translate(offsetX, offsetY);
+          context.rotate(-1 * config._xAxisTextAngle_);
+          context.fillText(String(xitem), textWidth , 0 );
+          context.closePath();
+          context.stroke();
+          context.restore();
+        }
+      });
+    }
+  }
+  context.restore();
+  
+  //画X轴标题
+  if (opts.xAxis.title) {
+    context.beginPath();
+    context.setFontSize(opts.xAxis.titleFontSize * opts.pix);
+    context.setFillStyle(opts.xAxis.titleFontColor);
+    context.fillText(String(opts.xAxis.title), opts.width - opts.area[1] + opts.xAxis.titleOffsetX * opts.pix,opts.height - opts.area[2] + opts.xAxis.marginTop * opts.pix + (opts.xAxis.lineHeight - opts.xAxis.titleFontSize) * opts.pix / 2 + (opts.xAxis.titleFontSize + opts.xAxis.titleOffsetY) * opts.pix);
+    context.closePath();
+    context.stroke();
+  }
+  
+  //绘制X轴轴线
+  if (opts.xAxis.axisLine) {
+    context.beginPath();
+    context.setStrokeStyle(opts.xAxis.axisLineColor);
+    context.setLineWidth(1 * opts.pix);
+    context.moveTo(startX, opts.height - opts.area[2]);
+    context.lineTo(endX, opts.height - opts.area[2]);
+    context.stroke();
+  }
+}
+
+function drawYAxisGrid(categories, opts, config, context) {
+  if (opts.yAxis.disableGrid === true) {
+    return;
+  }
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  let eachSpacing = spacingValid / opts.yAxis.splitNumber;
+  let startX = opts.area[3];
+  let xAxisPoints = opts.chartData.xAxisData.xAxisPoints,
+    xAxiseachSpacing = opts.chartData.xAxisData.eachSpacing;
+  let TotalWidth = xAxiseachSpacing * (xAxisPoints.length - 1);
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1 ){
+    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2
+    TotalWidth += (opts.extra.mount.widthRatio - 1) * xAxiseachSpacing;
+  }
+  let endX = startX + TotalWidth;
+  let points = [];
+  let startY = 1
+  if (opts.xAxis.axisLine === false) {
+    startY = 0
+  }
+  for (let i = startY; i < opts.yAxis.splitNumber + 1; i++) {
+    points.push(opts.height - opts.area[2] - eachSpacing * i);
+  }
+  context.save();
+  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {
+    context.translate(opts._scrollDistance_, 0);
+  }
+  if (opts.yAxis.gridType == 'dash') {
+    context.setLineDash([opts.yAxis.dashLength * opts.pix, opts.yAxis.dashLength * opts.pix]);
+  }
+  context.setStrokeStyle(opts.yAxis.gridColor);
+  context.setLineWidth(1 * opts.pix);
+  points.forEach(function(item, index) {
+    context.beginPath();
+    context.moveTo(startX, item);
+    context.lineTo(endX, item);
+    context.stroke();
+  });
+  context.setLineDash([]);
+  context.restore();
+}
+
+function drawYAxis(series, opts, config, context) {
+  if (opts.yAxis.disabled === true) {
+    return;
+  }
+  let spacingValid = opts.height - opts.area[0] - opts.area[2];
+  let eachSpacing = spacingValid / opts.yAxis.splitNumber;
+  let startX = opts.area[3];
+  let endX = opts.width - opts.area[1];
+  let endY = opts.height - opts.area[2];
+  // set YAxis background
+  context.beginPath();
+  context.setFillStyle(opts.background);
+  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'left') {
+    context.fillRect(0, 0, startX, endY + 2 * opts.pix);
+  }
+  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'right') {
+    context.fillRect(endX, 0, opts.width, endY + 2 * opts.pix);
+  }
+  context.closePath();
+  context.stroke();
+  
+  let tStartLeft = opts.area[3];
+  let tStartRight = opts.width - opts.area[1];
+  let tStartCenter = opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2;
+  if (opts.yAxis.data) {
+    for (let i = 0; i < opts.yAxis.data.length; i++) {
+      let yData = opts.yAxis.data[i];
+      let points = [];
+      if(yData.type === 'categories'){
+        for (let i = 0; i <= yData.categories.length; i++) {
+          points.push(opts.area[0] + spacingValid / yData.categories.length / 2 + spacingValid / yData.categories.length * i);
+        }
+      }else{
+        for (let i = 0; i <= opts.yAxis.splitNumber; i++) {
+          points.push(opts.area[0] + eachSpacing * i);
+        }
+      }
+      if (yData.disabled !== true) {
+        let rangesFormat = opts.chartData.yAxisData.rangesFormat[i];
+        let yAxisFontSize = yData.fontSize ? yData.fontSize * opts.pix : config.fontSize;
+        let yAxisWidth = opts.chartData.yAxisData.yAxisWidth[i];
+        let textAlign = yData.textAlign || "right";
+        //画Y轴刻度及文案
+        rangesFormat.forEach(function(item, index) {
+          let pos = points[index];
+          context.beginPath();
+          context.setFontSize(yAxisFontSize);
+          context.setLineWidth(1 * opts.pix);
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setFillStyle(yData.fontColor || opts.fontColor);
+          let tmpstrat = 0;
+          let gapwidth = 4 * opts.pix;
+          if (yAxisWidth.position == 'left') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartLeft, pos);
+              context.lineTo(tStartLeft - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            //画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartLeft - yAxisWidth.width
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartLeft - gapwidth
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartLeft - yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+
+          } else if (yAxisWidth.position == 'right') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartRight, pos);
+              context.lineTo(tStartRight + 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartRight + gapwidth
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartRight + yAxisWidth.width
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartRight + yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            //画刻度线
+            if (yData.calibration == true) {
+              context.moveTo(tStartCenter, pos);
+              context.lineTo(tStartCenter - 3 * opts.pix, pos);
+              gapwidth += 3 * opts.pix;
+            }
+            //画文字
+            switch (textAlign) {
+              case "left":
+                context.setTextAlign('left');
+                tmpstrat = tStartCenter - yAxisWidth.width
+                break;
+              case "right":
+                context.setTextAlign('right');
+                tmpstrat = tStartCenter - gapwidth
+                break;
+              default:
+                context.setTextAlign('center');
+                tmpstrat = tStartCenter - yAxisWidth.width / 2
+            }
+            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+          context.setTextAlign('left');
+        });
+        //画Y轴轴线
+        if (yData.axisLine !== false) {
+          context.beginPath();
+          context.setStrokeStyle(yData.axisLineColor || '#cccccc');
+          context.setLineWidth(1 * opts.pix);
+          if (yAxisWidth.position == 'left') {
+            context.moveTo(tStartLeft, opts.height - opts.area[2]);
+            context.lineTo(tStartLeft, opts.area[0]);
+          } else if (yAxisWidth.position == 'right') {
+            context.moveTo(tStartRight, opts.height - opts.area[2]);
+            context.lineTo(tStartRight, opts.area[0]);
+          } else if (yAxisWidth.position == 'center') {
+            context.moveTo(tStartCenter, opts.height - opts.area[2]);
+            context.lineTo(tStartCenter, opts.area[0]);
+          }
+          context.stroke();
+        }
+        //画Y轴标题
+        if (opts.yAxis.showTitle) {
+          let titleFontSize = yData.titleFontSize * opts.pix || config.fontSize;
+          let title = yData.title;
+          context.beginPath();
+          context.setFontSize(titleFontSize);
+          context.setFillStyle(yData.titleFontColor || opts.fontColor);
+          if (yAxisWidth.position == 'left') {
+            context.fillText(title, tStartLeft - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'right') {
+            context.fillText(title, tStartRight - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          } else if (yAxisWidth.position == 'center') {
+            context.fillText(title, tStartCenter - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);
+          }
+          context.closePath();
+          context.stroke();
+        }
+        if (yAxisWidth.position == 'left') {
+          tStartLeft -= (yAxisWidth.width + opts.yAxis.padding * opts.pix);
+        } else {
+          tStartRight += yAxisWidth.width + opts.yAxis.padding * opts.pix;
+        }
+      }
+    }
+  }
+
+}
+
+function drawLegend(series, opts, config, context, chartData) {
+  if (opts.legend.show === false) {
+    return;
+  }
+  let legendData = chartData.legendData;
+  let legendList = legendData.points;
+  let legendArea = legendData.area;
+  let padding = opts.legend.padding * opts.pix;
+  let fontSize = opts.legend.fontSize * opts.pix;
+  let shapeWidth = 15 * opts.pix;
+  let shapeRight = 5 * opts.pix;
+  let itemGap = opts.legend.itemGap * opts.pix;
+  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);
+  //画背景及边框
+  context.beginPath();
+  context.setLineWidth(opts.legend.borderWidth * opts.pix);
+  context.setStrokeStyle(opts.legend.borderColor);
+  context.setFillStyle(opts.legend.backgroundColor);
+  context.moveTo(legendArea.start.x, legendArea.start.y);
+  context.rect(legendArea.start.x, legendArea.start.y, legendArea.width, legendArea.height);
+  context.closePath();
+  context.fill();
+  context.stroke();
+  legendList.forEach(function(itemList, listIndex) {
+    let width = 0;
+    let height = 0;
+    width = legendData.widthArr[listIndex];
+    height = legendData.heightArr[listIndex];
+    let startX = 0;
+    let startY = 0;
+    if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+      switch (opts.legend.float) {
+        case 'left':
+          startX = legendArea.start.x + padding;
+        break;
+        case 'right':
+          startX = legendArea.start.x + legendArea.width - width;
+        break;
+        default:
+        startX = legendArea.start.x + (legendArea.width - width) / 2;
+      }
+      startY = legendArea.start.y + padding + listIndex * lineHeight;
+    } else {
+      if (listIndex == 0) {
+        width = 0;
+      } else {
+        width = legendData.widthArr[listIndex - 1];
+      }
+      startX = legendArea.start.x + padding + width;
+      startY = legendArea.start.y + padding + (legendArea.height - height) / 2;
+    }
+    context.setFontSize(config.fontSize);
+    for (let i = 0; i < itemList.length; i++) {
+      let item = itemList[i];
+      item.area = [0, 0, 0, 0];
+      item.area[0] = startX;
+      item.area[1] = startY;
+      item.area[3] = startY + lineHeight;
+      context.beginPath();
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.show ? item.color : opts.legend.hiddenColor);
+      context.setFillStyle(item.show ? item.color : opts.legend.hiddenColor);
+      switch (item.legendShape) {
+        case 'line':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 2 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 2 * opts.pix, 15 * opts.pix, 4 * opts.pix);
+          break;
+        case 'triangle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'diamond':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);
+          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          break;
+        case 'circle':
+          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight);
+          context.arc(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight, 5 * opts.pix, 0, 2 * Math.PI);
+          break;
+        case 'rect':
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+          break;
+        case 'square':
+          context.moveTo(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);
+          break;
+        case 'none':
+          break;
+        default:
+          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);
+          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);
+      }
+      context.closePath();
+      context.fill();
+      context.stroke();
+      startX += shapeWidth + shapeRight;
+      let fontTrans = 0.5 * lineHeight + 0.5 * fontSize - 2;
+      const legendText = item.legendText ? item.legendText : item.name;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.show ? opts.legend.fontColor : opts.legend.hiddenColor);
+      context.fillText(legendText, startX, startY + fontTrans);
+      context.closePath();
+      context.stroke();
+      if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {
+        startX += measureText(legendText, fontSize, context) + itemGap;
+        item.area[2] = startX;
+      } else {
+        item.area[2] = startX + measureText(legendText, fontSize, context) + itemGap;;
+        startX -= shapeWidth + shapeRight;
+        startY += lineHeight;
+      }
+    }
+  });
+}
+
+function drawPieDataPoints(series, opts, config, context) {
+  let pieOption = assign({}, {
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    ringWidth: 30,
+    customRadius: 0,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    centerColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.type == "pie" ? opts.extra.pie : opts.extra.ring);
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = pieOption.activeRadius * opts.pix;
+  }
+
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+  if (pieOption.customRadius > 0) {
+    radius = pieOption.customRadius * opts.pix;
+  }
+  series = getPieDataPoints(series, radius, chartProcess);
+  let activeRadius = pieOption.activeRadius * opts.pix;
+  pieOption.customColor = fillCustomColor(pieOption.linearType, pieOption.customColor, series, config);
+  series = series.map(function(eachSeries) {
+    eachSeries._start_ += (pieOption.offsetAngle) * Math.PI / 180;
+    return eachSeries;
+  });
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color, pieOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_ + activeRadius, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(pieOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(pieOption.borderColor);
+    let fillcolor = eachSeries.color;
+    if (pieOption.linearType == 'custom') {
+      let grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }
+      grd.addColorStop(0, hexToRgb(pieOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))
+      fillcolor = grd
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);
+    context.closePath();
+    context.fill();
+    if (pieOption.border == true) {
+      context.stroke();
+    }
+  });
+  if (opts.type === 'ring') {
+    let innerPieWidth = radius * 0.6;
+    if (typeof pieOption.ringWidth === 'number' && pieOption.ringWidth > 0) {
+      innerPieWidth = Math.max(0, radius - pieOption.ringWidth * opts.pix);
+    }
+    context.beginPath();
+    context.setFillStyle(pieOption.centerColor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, innerPieWidth, 0, 2 * Math.PI);
+    context.closePath();
+    context.fill();
+  }
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+  if (process === 1 && opts.type === 'ring') {
+    drawRingTitle(opts, config, context, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawRoseDataPoints(series, opts, config, context) {
+  let roseOption = assign({}, {
+    type: 'area',
+    activeOpacity: 0.5,
+    activeRadius: 10,
+    offsetAngle: 0,
+    labelWidth: 15,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.rose);
+  if (config.pieChartLinePadding == 0) {
+    config.pieChartLinePadding = roseOption.activeRadius * opts.pix;
+  }
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);
+  radius = radius < 10 ? 10 : radius;
+  let minRadius = roseOption.minRadius || radius * 0.5;
+  if(radius < minRadius){
+    radius = minRadius + 10;
+  }
+  series = getRoseDataPoints(series, roseOption.type, minRadius, radius, chartProcess);
+  let activeRadius = roseOption.activeRadius * opts.pix;
+  roseOption.customColor = fillCustomColor(roseOption.linearType, roseOption.customColor, series, config);
+  series = series.map(function(eachSeries) {
+    eachSeries._start_ += (roseOption.offsetAngle || 0) * Math.PI / 180;
+    return eachSeries;
+  });
+  series.forEach(function(eachSeries, seriesIndex) {
+    if (opts.tooltip) {
+      if (opts.tooltip.index == seriesIndex) {
+        context.beginPath();
+        context.setFillStyle(hexToRgb(eachSeries.color, roseOption.activeOpacity || 0.5));
+        context.moveTo(centerPosition.x, centerPosition.y);
+        context.arc(centerPosition.x, centerPosition.y, activeRadius + eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);
+        context.closePath();
+        context.fill();
+      }
+    }
+    context.beginPath();
+    context.setLineWidth(roseOption.borderWidth * opts.pix);
+    context.lineJoin = "round";
+    context.setStrokeStyle(roseOption.borderColor);
+    let fillcolor = eachSeries.color;
+    if (roseOption.linearType == 'custom') {
+      let grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)
+      }
+      grd.addColorStop(0, hexToRgb(roseOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))
+      fillcolor = grd
+    }
+    context.setFillStyle(fillcolor);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);
+    context.closePath();
+    context.fill();
+    if (roseOption.border == true) {
+      context.stroke();
+    }
+  });
+
+  if (opts.dataLabel !== false && process === 1) {
+    drawPieText(series, opts, config, context, radius, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawArcbarDataPoints(series, opts, config, context) {
+  let arcbarOption = assign({}, {
+    startAngle: 0.75,
+    endAngle: 0.25,
+    type: 'default',
+    direction: 'cw',
+    lineCap: 'round',
+    width: 12 ,
+    gap: 2 ,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.arcbar);
+  series = getArcbarDataPoints(series, arcbarOption, chartProcess);
+  let centerPosition;
+  if (arcbarOption.centerX || arcbarOption.centerY) {
+    centerPosition = {
+      x: arcbarOption.centerX ? arcbarOption.centerX : opts.width / 2,
+      y: arcbarOption.centerY ? arcbarOption.centerY : opts.height / 2
+    };
+  } else {
+    centerPosition = {
+      x: opts.width / 2,
+      y: opts.height / 2
+    };
+  }
+  let radius;
+  if (arcbarOption.radius) {
+    radius = arcbarOption.radius;
+  } else {
+    radius = Math.min(centerPosition.x, centerPosition.y);
+    radius -= 5 * opts.pix;
+    radius -= arcbarOption.width / 2;
+  }
+  radius = radius < 10 ? 10 : radius;
+  arcbarOption.customColor = fillCustomColor(arcbarOption.linearType, arcbarOption.customColor, series, config);
+  
+  for (let i = 0; i < series.length; i++) {
+    let eachSeries = series[i];
+    //背景颜色
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(arcbarOption.backgroundColor || '#E9E9E9');
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    if (arcbarOption.type == 'default') {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, arcbarOption.endAngle * Math.PI, arcbarOption.direction == 'ccw');
+    } else {
+      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, 0, 2 * Math.PI, arcbarOption.direction == 'ccw');
+    }
+    context.stroke();
+    //进度条
+    let fillColor = eachSeries.color
+    if(arcbarOption.linearType == 'custom'){
+      let grd = context.createLinearGradient(centerPosition.x - radius, centerPosition.y, centerPosition.x + radius, centerPosition.y);
+      grd.addColorStop(1, hexToRgb(arcbarOption.customColor[eachSeries.linearIndex], 1))
+      grd.addColorStop(0, hexToRgb(eachSeries.color, 1))
+      fillColor = grd;
+    }
+    context.setLineWidth(arcbarOption.width * opts.pix);
+    context.setStrokeStyle(fillColor);
+    context.setLineCap(arcbarOption.lineCap);
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, eachSeries._proportion_ * Math.PI, arcbarOption.direction == 'ccw');
+    context.stroke();
+  }
+  drawRingTitle(opts, config, context, centerPosition);
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: series
+  };
+}
+
+function drawGaugeDataPoints(categories, series, opts, config, context) {
+  let gaugeOption = assign({}, {
+    type: 'default',
+    startAngle: 0.75,
+    endAngle: 0.25,
+    width: 15,
+    labelOffset:13,
+    splitLine: {
+      fixRadius: 0,
+      splitNumber: 10,
+      width: 15,
+      color: '#FFFFFF',
+      childNumber: 5,
+      childWidth: 5
+    },
+    pointer: {
+      width: 15,
+      color: 'auto'
+    }
+  }, opts.extra.gauge);
+  if (gaugeOption.oldAngle == undefined) {
+    gaugeOption.oldAngle = gaugeOption.startAngle;
+  }
+  if (gaugeOption.oldData == undefined) {
+    gaugeOption.oldData = 0;
+  }
+  categories = getGaugeAxisPoints(categories, gaugeOption.startAngle, gaugeOption.endAngle);
+  let centerPosition = {
+    x: opts.width / 2,
+    y: opts.height / 2
+  };
+  let radius = Math.min(centerPosition.x, centerPosition.y);
+  radius -= 5 * opts.pix;
+  radius -= gaugeOption.width / 2;
+  radius = radius < 10 ? 10 : radius;
+  let innerRadius = radius - gaugeOption.width;
+  let totalAngle = 0;
+  //判断仪表盘的样式:default百度样式,progress新样式
+  if (gaugeOption.type == 'progress') {
+    //## 第一步画中心圆形背景和进度条背景
+    //中心圆形背景
+    let pieRadius = radius - gaugeOption.width * 3;
+    context.beginPath();
+    let gradient = context.createLinearGradient(centerPosition.x, centerPosition.y - pieRadius, centerPosition.x, centerPosition.y + pieRadius);
+    //配置渐变填充(起点:中心点向上减半径;结束点中心点向下加半径)
+    gradient.addColorStop('0', hexToRgb(series[0].color, 0.3));
+    gradient.addColorStop('1.0', hexToRgb("#FFFFFF", 0.1));
+    context.setFillStyle(gradient);
+    context.arc(centerPosition.x, centerPosition.y, pieRadius, 0, 2 * Math.PI, false);
+    context.fill();
+    //画进度条背景
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(hexToRgb(series[0].color, 0.3));
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, gaugeOption.endAngle * Math.PI, false);
+    context.stroke();
+    //## 第二步画刻度线
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;
+    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;
+    let endX = -radius - gaugeOption.width - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    let len = gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1;
+    let proc = series[0].data * process;
+    for (let i = 0; i < len; i++) {
+      context.beginPath();
+      //刻度线随进度变色
+      if (proc > (i / len)) {
+        context.setStrokeStyle(hexToRgb(series[0].color, 1));
+      } else {
+        context.setStrokeStyle(hexToRgb(series[0].color, 0.3));
+      }
+      context.setLineWidth(3 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(endX, 0);
+      context.stroke();
+      context.rotate(childAngle * Math.PI);
+    }
+    context.restore();
+    //## 第三步画进度条
+    series = getGaugeArcbarDataPoints(series, gaugeOption, chartProcess);
+    context.setLineWidth(gaugeOption.width);
+    context.setStrokeStyle(series[0].color);
+    context.setLineCap('round');
+    context.beginPath();
+    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, series[0]._proportion_ * Math.PI, false);
+    context.stroke();
+    //## 第四步画指针
+    let pointerRadius = radius - gaugeOption.width * 2.5;
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((series[0]._proportion_ - 1) * Math.PI);
+    context.beginPath();
+    context.setLineWidth(gaugeOption.width / 3);
+    let gradient3 = context.createLinearGradient(0, -pointerRadius * 0.6, 0, pointerRadius * 0.6);
+    gradient3.addColorStop('0', hexToRgb('#FFFFFF', 0));
+    gradient3.addColorStop('0.5', hexToRgb(series[0].color, 1));
+    gradient3.addColorStop('1.0', hexToRgb('#FFFFFF', 0));
+    context.setStrokeStyle(gradient3);
+    context.arc(0, 0, pointerRadius, 0.85 * Math.PI, 1.15 * Math.PI, false);
+    context.stroke();
+    context.beginPath();
+    context.setLineWidth(1);
+    context.setStrokeStyle(series[0].color);
+    context.setFillStyle(series[0].color);
+    context.moveTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2 - 4, 0);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, 4);
+    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);
+    context.stroke();
+    context.fill();
+    context.restore();
+    //default百度样式
+  } else {
+    //画背景
+    context.setLineWidth(gaugeOption.width);
+    context.setLineCap('butt');
+    for (let i = 0; i < categories.length; i++) {
+      let eachCategories = categories[i];
+      context.beginPath();
+      context.setStrokeStyle(eachCategories.color);
+      context.arc(centerPosition.x, centerPosition.y, radius, eachCategories._startAngle_ * Math.PI, eachCategories._endAngle_ * Math.PI, false);
+      context.stroke();
+    }
+    context.save();
+    //画刻度线
+    if (gaugeOption.endAngle < gaugeOption.startAngle) {
+      totalAngle = 2 + gaugeOption.endAngle - gaugeOption.startAngle;
+    } else {
+      totalAngle = gaugeOption.startAngle - gaugeOption.endAngle;
+    }
+    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;
+    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;
+    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;
+    let endX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;
+    let childendX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.childWidth;
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {
+      context.beginPath();
+      context.setStrokeStyle(gaugeOption.splitLine.color);
+      context.setLineWidth(2 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(endX, 0);
+      context.stroke();
+      context.rotate(splitAngle * Math.PI);
+    }
+    context.restore();
+    context.save();
+    context.translate(centerPosition.x, centerPosition.y);
+    context.rotate((gaugeOption.startAngle - 1) * Math.PI);
+    for (let i = 0; i < gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1; i++) {
+      context.beginPath();
+      context.setStrokeStyle(gaugeOption.splitLine.color);
+      context.setLineWidth(1 * opts.pix);
+      context.moveTo(startX, 0);
+      context.lineTo(childendX, 0);
+      context.stroke();
+      context.rotate(childAngle * Math.PI);
+    }
+    context.restore();
+    //画指针
+    series = getGaugeDataPoints(series, categories, gaugeOption, chartProcess);
+    for (let i = 0; i < series.length; i++) {
+      let eachSeries = series[i];
+      context.save();
+      context.translate(centerPosition.x, centerPosition.y);
+      context.rotate((eachSeries._proportion_ - 1) * Math.PI);
+      context.beginPath();
+      context.setFillStyle(eachSeries.color);
+      context.moveTo(gaugeOption.pointer.width, 0);
+      context.lineTo(0, -gaugeOption.pointer.width / 2);
+      context.lineTo(-innerRadius, 0);
+      context.lineTo(0, gaugeOption.pointer.width / 2);
+      context.lineTo(gaugeOption.pointer.width, 0);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFillStyle('#FFFFFF');
+      context.arc(0, 0, gaugeOption.pointer.width / 6, 0, 2 * Math.PI, false);
+      context.fill();
+      context.restore();
+    }
+    if (opts.dataLabel !== false) {
+      drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context);
+    }
+  }
+  //画仪表盘标题,副标题
+  drawRingTitle(opts, config, context, centerPosition);
+  if (process === 1 && opts.type === 'gauge') {
+    opts.extra.gauge.oldAngle = series[0]._proportion_;
+    opts.extra.gauge.oldData = series[0].data;
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    innerRadius: innerRadius,
+    categories: categories,
+    totalAngle: totalAngle
+  };
+}
+
+function drawRadarDataPoints(series, opts, config, context) {
+  let radarOption = assign({}, {
+    gridColor: '#cccccc',
+    gridType: 'radar',
+    gridEval:1,
+    axisLabel:false,
+    axisLabelTofix:0,
+    labelShow:true,
+    labelColor:'#666666',
+    labelPointShow:false,
+    labelPointRadius:3,
+    labelPointColor:'#cccccc',
+    opacity: 0.2,
+    gridCount: 3,
+    border:false,
+    borderWidth:2,
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.radar);
+  let coordinateAngle = getRadarCoordinateSeries(opts.categories.length);
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2
+  };
+  let xr = (opts.width - opts.area[1] - opts.area[3]) / 2
+  let yr = (opts.height - opts.area[0] - opts.area[2]) / 2
+  let radius = Math.min(xr - (getMaxTextListLength(opts.categories, config.fontSize, context) + config.radarLabelTextMargin), yr - config.radarLabelTextMargin);
+  radius -= config.radarLabelTextMargin * opts.pix;
+  radius = radius < 10 ? 10 : radius;
+  radius = radarOption.radius ? radarOption.radius : radius;
+  // 画分割线
+  context.beginPath();
+  context.setLineWidth(1 * opts.pix);
+  context.setStrokeStyle(radarOption.gridColor);
+  coordinateAngle.forEach(function(angle,index) {
+    let pos = convertCoordinateOrigin(radius * Math.cos(angle), radius * Math.sin(angle), centerPosition);
+    context.moveTo(centerPosition.x, centerPosition.y);
+    if (index % radarOption.gridEval == 0) {
+      context.lineTo(pos.x, pos.y);
+    }
+  });
+  context.stroke();
+  context.closePath();
+  
+  // 画背景网格
+  let _loop = function _loop(i) {
+    let startPos = {};
+    context.beginPath();
+    context.setLineWidth(1 * opts.pix);
+    context.setStrokeStyle(radarOption.gridColor);
+    if (radarOption.gridType == 'radar') {
+      coordinateAngle.forEach(function(angle, index) {
+        let pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(angle), radius /
+          radarOption.gridCount * i * Math.sin(angle), centerPosition);
+        if (index === 0) {
+          startPos = pos;
+          context.moveTo(pos.x, pos.y);
+        } else {
+          context.lineTo(pos.x, pos.y);
+        }
+      });
+      context.lineTo(startPos.x, startPos.y);
+    } else {
+      let pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(1.5), radius / radarOption.gridCount * i * Math.sin(1.5), centerPosition);
+      context.arc(centerPosition.x, centerPosition.y, centerPosition.y - pos.y, 0, 2 * Math.PI, false);
+    }
+    context.stroke();
+    context.closePath();
+  };
+  for (let i = 1; i <= radarOption.gridCount; i++) {
+    _loop(i);
+  }
+  radarOption.customColor = fillCustomColor(radarOption.linearType, radarOption.customColor, series, config);
+  let radarDataPoints = getRadarDataPoints(coordinateAngle, centerPosition, radius, series, opts, chartProcess);
+  radarDataPoints.forEach(function(eachSeries, seriesIndex) {
+    // 绘制区域数据
+    context.beginPath();
+    context.setLineWidth(radarOption.borderWidth * opts.pix);
+    context.setStrokeStyle(eachSeries.color);
+    
+    let fillcolor = hexToRgb(eachSeries.color, radarOption.opacity);
+    if (radarOption.linearType == 'custom') {
+      let grd;
+      if(context.createCircularGradient){
+        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, radius)
+      }else{
+        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, radius)
+      }
+      grd.addColorStop(0, hexToRgb(radarOption.customColor[series[seriesIndex].linearIndex], radarOption.opacity))
+      grd.addColorStop(1, hexToRgb(eachSeries.color, radarOption.opacity))
+      fillcolor = grd
+    }
+    
+    context.setFillStyle(fillcolor);
+    eachSeries.data.forEach(function(item, index) {
+      if (index === 0) {
+        context.moveTo(item.position.x, item.position.y);
+      } else {
+        context.lineTo(item.position.x, item.position.y);
+      }
+    });
+    context.closePath();
+    context.fill();
+    if(radarOption.border === true){
+      context.stroke();
+    }
+    context.closePath();
+    if (opts.dataPointShape !== false) {
+      let points = eachSeries.data.map(function(item) {
+        return item.position;
+      });
+      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);
+    }
+  });
+  // 画刻度值
+  if(radarOption.axisLabel === true){
+    const maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));
+    const stepLength = radius / radarOption.gridCount;
+    const fontSize = opts.fontSize * opts.pix;
+    context.setFontSize(fontSize);
+    context.setFillStyle(opts.fontColor);
+    context.setTextAlign('left');
+    for (let i = 0; i < radarOption.gridCount + 1; i++) {
+      let label = i * maxData / radarOption.gridCount;
+      label = label.toFixed(radarOption.axisLabelTofix);
+      context.fillText(String(label), centerPosition.x + 3 * opts.pix, centerPosition.y - i * stepLength + fontSize / 2);
+    }
+  }
+  
+  // draw label text
+  drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context);
+  
+  // draw dataLabel
+  if (opts.dataLabel !== false && process === 1) {
+    radarDataPoints.forEach(function(eachSeries, seriesIndex) {
+      context.beginPath();
+      let fontSize = eachSeries.textSize * opts.pix || config.fontSize;
+      context.setFontSize(fontSize);
+      context.setFillStyle(eachSeries.textColor || opts.fontColor);
+      eachSeries.data.forEach(function(item, index) {
+        //如果是中心点垂直的上下点位
+        if(Math.abs(item.position.x - centerPosition.x)<2){
+          //如果在上面
+          if(item.position.y < centerPosition.y){
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y - 4);
+          }else{
+            context.setTextAlign('center');
+            context.fillText(item.value, item.position.x, item.position.y + fontSize + 2);
+          }
+        }else{
+          //如果在左侧
+          if(item.position.x < centerPosition.x){
+            context.setTextAlign('right');
+            context.fillText(item.value, item.position.x - 4, item.position.y + fontSize / 2 - 2);
+          }else{
+            context.setTextAlign('left');
+            context.fillText(item.value, item.position.x + 4, item.position.y + fontSize / 2 - 2);
+          }
+        }
+      });
+      context.closePath();
+      context.stroke();
+    });
+    context.setTextAlign('left');
+  }
+  
+  return {
+    center: centerPosition,
+    radius: radius,
+    angleList: coordinateAngle
+  };
+}
+
+// 经纬度转墨卡托
+function lonlat2mercator(longitude, latitude) {
+  let mercator = Array(2);
+  let x = longitude * 20037508.34 / 180;
+  let y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180);
+  y = y * 20037508.34 / 180;
+  mercator[0] = x;
+  mercator[1] = y;
+  return mercator;
+}
+
+// 墨卡托转经纬度
+function mercator2lonlat(longitude, latitude) {
+  let lonlat = Array(2)
+  let x = longitude / 20037508.34 * 180;
+  let y = latitude / 20037508.34 * 180;
+  y = 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);
+  lonlat[0] = x;
+  lonlat[1] = y;
+  return lonlat;
+}
+
+function getBoundingBox(data) {
+  let bounds = {},coords;
+  bounds.xMin = 180;
+  bounds.xMax = 0;
+  bounds.yMin = 90;
+  bounds.yMax = 0
+  for (let i = 0; i < data.length; i++) {
+    let coorda = data[i].geometry.coordinates
+    for (let k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0]
+      }
+      for (let j = 0; j < coords.length; j++) {
+        let longitude = coords[j][0];
+        let latitude = coords[j][1];
+        let point = {
+          x: longitude,
+          y: latitude
+        }
+        bounds.xMin = bounds.xMin < point.x ? bounds.xMin : point.x;
+        bounds.xMax = bounds.xMax > point.x ? bounds.xMax : point.x;
+        bounds.yMin = bounds.yMin < point.y ? bounds.yMin : point.y;
+        bounds.yMax = bounds.yMax > point.y ? bounds.yMax : point.y;
+      }
+    }
+  }
+  return bounds;
+}
+
+function coordinateToPoint(latitude, longitude, bounds, scale, xoffset, yoffset) {
+  return {
+    x: (longitude - bounds.xMin) * scale + xoffset,
+    y: (bounds.yMax - latitude) * scale + yoffset
+  };
+}
+
+function pointToCoordinate(pointY, pointX, bounds, scale, xoffset, yoffset) {
+  return {
+    x: (pointX - xoffset) / scale + bounds.xMin,
+    y: bounds.yMax - (pointY - yoffset) / scale
+  };
+}
+
+function isRayIntersectsSegment(poi, s_poi, e_poi) {
+  if (s_poi[1] == e_poi[1]) {
+    return false;
+  }
+  if (s_poi[1] > poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[1] < poi[1] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  if (s_poi[1] == poi[1] && e_poi[1] > poi[1]) {
+    return false;
+  }
+  if (e_poi[1] == poi[1] && s_poi[1] > poi[1]) {
+    return false;
+  }
+  if (s_poi[0] < poi[0] && e_poi[1] < poi[1]) {
+    return false;
+  }
+  let xseg = e_poi[0] - (e_poi[0] - s_poi[0]) * (e_poi[1] - poi[1]) / (e_poi[1] - s_poi[1]);
+  if (xseg < poi[0]) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+function isPoiWithinPoly(poi, poly, mercator) {
+  let sinsc = 0;
+  for (let i = 0; i < poly.length; i++) {
+    let epoly = poly[i][0];
+    if (poly.length == 1) {
+      epoly = poly[i][0]
+    }
+    for (let j = 0; j < epoly.length - 1; j++) {
+      let s_poi = epoly[j];
+      let e_poi = epoly[j + 1];
+      if (mercator) {
+        s_poi = lonlat2mercator(epoly[j][0], epoly[j][1]);
+        e_poi = lonlat2mercator(epoly[j + 1][0], epoly[j + 1][1]);
+      }
+      if (isRayIntersectsSegment(poi, s_poi, e_poi)) {
+        sinsc += 1;
+      }
+    }
+  }
+  if (sinsc % 2 == 1) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function drawMapDataPoints(series, opts, config, context) {
+  let mapOption = assign({}, {
+    border: true,
+    mercator: false,
+    borderWidth: 1,
+    active:true,
+    borderColor: '#666666',
+    fillOpacity: 0.6,
+    activeBorderColor: '#f04864',
+    activeFillColor: '#facc14',
+    activeFillOpacity: 1
+  }, opts.extra.map);
+  let coords, point;
+  let data = series;
+  let bounds = getBoundingBox(data);
+  if (mapOption.mercator) {
+    let max = lonlat2mercator(bounds.xMax, bounds.yMax)
+    let min = lonlat2mercator(bounds.xMin, bounds.yMin)
+    bounds.xMax = max[0]
+    bounds.yMax = max[1]
+    bounds.xMin = min[0]
+    bounds.yMin = min[1]
+  }
+  let xScale = opts.width / Math.abs(bounds.xMax - bounds.xMin);
+  let yScale = opts.height / Math.abs(bounds.yMax - bounds.yMin);
+  let scale = xScale < yScale ? xScale : yScale;
+  let xoffset = opts.width / 2 - Math.abs(bounds.xMax - bounds.xMin) / 2 * scale;
+  let yoffset = opts.height / 2 - Math.abs(bounds.yMax - bounds.yMin) / 2 * scale;
+  for (let i = 0; i < data.length; i++) {
+    context.beginPath();
+    context.setLineWidth(mapOption.borderWidth * opts.pix);
+    context.setStrokeStyle(mapOption.borderColor);
+    context.setFillStyle(hexToRgb(series[i].color, series[i].fillOpacity||mapOption.fillOpacity));
+    if (mapOption.active == true && opts.tooltip) {
+      if (opts.tooltip.index == i) {
+        context.setStrokeStyle(mapOption.activeBorderColor);
+        context.setFillStyle(hexToRgb(mapOption.activeFillColor, mapOption.activeFillOpacity));
+      }
+    }
+    let coorda = data[i].geometry.coordinates
+    for (let k = 0; k < coorda.length; k++) {
+      coords = coorda[k];
+      if (coords.length == 1) {
+        coords = coords[0]
+      }
+      for (let j = 0; j < coords.length; j++) {
+        let gaosi = Array(2);
+        if (mapOption.mercator) {
+          gaosi = lonlat2mercator(coords[j][0], coords[j][1])
+        } else {
+          gaosi = coords[j]
+        }
+        point = coordinateToPoint(gaosi[1], gaosi[0], bounds, scale, xoffset, yoffset)
+        if (j === 0) {
+          context.beginPath();
+          context.moveTo(point.x, point.y);
+        } else {
+          context.lineTo(point.x, point.y);
+        }
+      }
+      context.fill();
+      if (mapOption.border == true) {
+        context.stroke();
+      }
+    }
+  }
+  if (opts.dataLabel == true) {
+    for (let i = 0; i < data.length; i++) {
+      let centerPoint = data[i].properties.centroid;
+      if (centerPoint) {
+        if (mapOption.mercator) {
+          centerPoint = lonlat2mercator(data[i].properties.centroid[0], data[i].properties.centroid[1])
+        }
+        point = coordinateToPoint(centerPoint[1], centerPoint[0], bounds, scale, xoffset, yoffset);
+        let fontSize = data[i].textSize * opts.pix || config.fontSize;
+        let fontColor = data[i].textColor || opts.fontColor;
+        if(mapOption.active && mapOption.activeTextColor && opts.tooltip && opts.tooltip.index == i){
+          fontColor = mapOption.activeTextColor;
+        }
+        let text = data[i].properties.name;
+        context.beginPath();
+        context.setFontSize(fontSize)
+        context.setFillStyle(fontColor)
+        context.fillText(text, point.x - measureText(text, fontSize, context) / 2, point.y + fontSize / 2);
+        context.closePath();
+        context.stroke();
+      }
+    }
+  }
+  opts.chartData.mapData = {
+    bounds: bounds,
+    scale: scale,
+    xoffset: xoffset,
+    yoffset: yoffset,
+    mercator: mapOption.mercator
+  }
+  drawToolTipBridge(opts, config, context, 1);
+  context.draw();
+}
+
+function normalInt(min, max, iter) {
+  iter = iter == 0 ? 1 : iter;
+  let arr = [];
+  for (let i = 0; i < iter; i++) {
+    arr[i] = Math.random();
+  };
+  return Math.floor(arr.reduce(function(i, j) {
+    return i + j
+  }) / iter * (max - min)) + min;
+};
+
+function collisionNew(area, points, width, height) {
+  let isIn = false;
+  for (let i = 0; i < points.length; i++) {
+    if (points[i].area) {
+      if (area[3] < points[i].area[1] || area[0] > points[i].area[2] || area[1] > points[i].area[3] || area[2] < points[i].area[0]) {
+        if (area[0] < 0 || area[1] < 0 || area[2] > width || area[3] > height) {
+          isIn = true;
+          break;
+        } else {
+          isIn = false;
+        }
+      } else {
+        isIn = true;
+        break;
+      }
+    }
+  }
+  return isIn;
+};
+
+function getWordCloudPoint(opts, type, context) {
+  let points = opts.series;
+  switch (type) {
+    case 'normal':
+      for (let i = 0; i < points.length; i++) {
+        let text = points[i].name;
+        let tHeight = points[i].textSize * opts.pix;
+        let tWidth = measureText(text, tHeight, context);
+        let x, y;
+        let area;
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+          y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+          area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 +
+            opts.height / 2
+          ];
+          let isCollision = collisionNew(area, points, opts.width, opts.height);
+          if (!isCollision) break;
+          if (breaknum == 1000) {
+            area = [-100, -100, -100, -100];
+            break;
+          }
+        };
+        points[i].area = area;
+      }
+      break;
+    case 'vertical':
+      function Spin() {
+        //获取均匀随机值,是否旋转,旋转的概率为(1-0.5)
+        if (Math.random() > 0.7) {
+          return true;
+        } else {
+          return false
+        };
+      };
+      for (let i = 0; i < points.length; i++) {
+        let text = points[i].name;
+        let tHeight = points[i].textSize * opts.pix;
+        let tWidth = measureText(text, tHeight, context);
+        let isSpin = Spin();
+        let x, y, area, areav;
+        let breaknum = 0;
+        while (true) {
+          breaknum++;
+          let isCollision;
+          if (isSpin) {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [y - 5 - tWidth + opts.width / 2, (-x - 5 + opts.height / 2), y + 5 + opts.width / 2, (-x + tHeight + 5 + opts.height / 2)];
+            areav = [opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) - 5, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) - 5, opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) + tHeight, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) + tWidth + 5];
+            isCollision = collisionNew(areav, points, opts.height, opts.width);
+          } else {
+            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;
+            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;
+            area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 + opts.height / 2];
+            isCollision = collisionNew(area, points, opts.width, opts.height);
+          }
+          if (!isCollision) break;
+          if (breaknum == 1000) {
+            area = [-1000, -1000, -1000, -1000];
+            break;
+          }
+        };
+        if (isSpin) {
+          points[i].area = areav;
+          points[i].areav = area;
+        } else {
+          points[i].area = area;
+        }
+        points[i].rotate = isSpin;
+      };
+      break;
+  }
+  return points;
+}
+
+function drawWordCloudDataPoints(series, opts, config, context) {
+  let wordOption = assign({}, {
+    type: 'normal',
+    autoColors: true
+  }, opts.extra.word);
+  if (!opts.chartData.wordCloudData) {
+    opts.chartData.wordCloudData = getWordCloudPoint(opts, wordOption.type, context);
+  }
+  context.beginPath();
+  context.setFillStyle(opts.background);
+  context.rect(0, 0, opts.width, opts.height);
+  context.fill();
+  context.save();
+  let points = opts.chartData.wordCloudData;
+  context.translate(opts.width / 2, opts.height / 2);
+  for (let i = 0; i < points.length; i++) {
+    context.save();
+    if (points[i].rotate) {
+      context.rotate(90 * Math.PI / 180);
+    }
+    let text = points[i].name;
+    let tHeight = points[i].textSize * opts.pix;
+    let tWidth = measureText(text, tHeight, context);
+    context.beginPath();
+    context.setStrokeStyle(points[i].color);
+    context.setFillStyle(points[i].color);
+    context.setFontSize(tHeight);
+    if (points[i].rotate) {
+      if (points[i].areav[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    } else {
+      if (points[i].area[0] > 0) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.strokeText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+          } else {
+            context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+          }
+        } else {
+          context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);
+        }
+      }
+    }
+    context.stroke();
+    context.restore();
+  }
+  context.restore();
+}
+
+function drawFunnelDataPoints(series, opts, config, context) {
+  let funnelOption = assign({}, {
+    type:'funnel',
+    activeWidth: 10,
+    activeOpacity: 0.3,
+    border: false,
+    borderWidth: 2,
+    borderColor: '#FFFFFF',
+    fillOpacity: 1,
+    minSize: 0,
+    labelAlign: 'right',
+    linearType: 'none',
+    customColor: [],
+  }, opts.extra.funnel);
+  let eachSpacing = (opts.height - opts.area[0] - opts.area[2]) / series.length;
+  let centerPosition = {
+    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,
+    y: opts.height - opts.area[2]
+  };
+  let activeWidth = funnelOption.activeWidth * opts.pix;
+  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - activeWidth, (opts.height - opts.area[0] - opts.area[2]) / 2 - activeWidth);
+  let seriesNew = getFunnelDataPoints(series, radius, funnelOption, eachSpacing, chartProcess);
+  context.save();
+  context.translate(centerPosition.x, centerPosition.y);
+  funnelOption.customColor = fillCustomColor(funnelOption.linearType, funnelOption.customColor, series, config);
+  if(funnelOption.type == 'pyramid'){
+    for (let i = 0; i < seriesNew.length; i++) {
+      if (i == seriesNew.length -1) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(-activeWidth, -eachSpacing);
+            context.lineTo(-seriesNew[i].radius - activeWidth, 0);
+            context.lineTo(seriesNew[i].radius + activeWidth, 0);
+            context.lineTo(activeWidth, -eachSpacing);
+            context.lineTo(-activeWidth, -eachSpacing);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * i];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        let fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          let grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, -eachSpacing);
+        context.lineTo(-seriesNew[i].radius, 0);
+        context.lineTo(seriesNew[i].radius, 0);
+        context.lineTo(0, -eachSpacing);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      } else {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(0, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, 0);
+            context.lineTo(-seriesNew[i + 1].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i + 1].radius + activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, 0);
+            context.lineTo(0, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * i];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        let fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          let grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-seriesNew[i].radius, 0);
+        context.lineTo(-seriesNew[i + 1].radius, -eachSpacing);
+        context.lineTo(seriesNew[i + 1].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      }
+      context.translate(0, -eachSpacing)
+    }
+  }else{
+    context.translate(0, - (seriesNew.length - 1) * eachSpacing);
+    for (let i = 0; i < seriesNew.length; i++) {
+      if (i == seriesNew.length - 1) {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(-activeWidth - funnelOption.minSize/2, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, -eachSpacing);
+            context.lineTo(activeWidth + funnelOption.minSize/2, 0);
+            context.lineTo(-activeWidth - funnelOption.minSize/2, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing, centerPosition.x + seriesNew[i].radius, centerPosition.y ];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        let fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          let grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-funnelOption.minSize/2, 0);
+        context.lineTo(-seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, -eachSpacing);
+        context.lineTo(funnelOption.minSize/2, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      } else {
+        if (opts.tooltip) {
+          if (opts.tooltip.index == i) {
+            context.beginPath();
+            context.setFillStyle(hexToRgb(seriesNew[i].color, funnelOption.activeOpacity));
+            context.moveTo(0, 0);
+            context.lineTo(-seriesNew[i + 1].radius - activeWidth, 0);
+            context.lineTo(-seriesNew[i].radius - activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i].radius + activeWidth, -eachSpacing);
+            context.lineTo(seriesNew[i + 1].radius + activeWidth, 0);
+            context.lineTo(0, 0);
+            context.closePath();
+            context.fill();
+          }
+        }
+        seriesNew[i].funnelArea = [centerPosition.x - seriesNew[i].radius, centerPosition.y - eachSpacing * (seriesNew.length - i), centerPosition.x + seriesNew[i].radius, centerPosition.y - eachSpacing * (seriesNew.length - i - 1)];
+        context.beginPath();
+        context.setLineWidth(funnelOption.borderWidth * opts.pix);
+        context.setStrokeStyle(funnelOption.borderColor);
+        let fillColor = hexToRgb(seriesNew[i].color, funnelOption.fillOpacity);
+        if (funnelOption.linearType == 'custom') {
+          let grd = context.createLinearGradient(seriesNew[i].radius, -eachSpacing, -seriesNew[i].radius, -eachSpacing);
+          grd.addColorStop(0, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[seriesNew[i].linearIndex], funnelOption.fillOpacity));
+          grd.addColorStop(1, hexToRgb(seriesNew[i].color, funnelOption.fillOpacity));
+          fillColor = grd
+        }
+        context.setFillStyle(fillColor);
+        context.moveTo(0, 0);
+        context.lineTo(-seriesNew[i + 1].radius, 0);
+        context.lineTo(-seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i].radius, -eachSpacing);
+        context.lineTo(seriesNew[i + 1].radius, 0);
+        context.lineTo(0, 0);
+        context.closePath();
+        context.fill();
+        if (funnelOption.border == true) {
+          context.stroke();
+        }
+      }
+      context.translate(0, eachSpacing)
+    }
+  }
+  
+  context.restore();
+  if (opts.dataLabel !== false && process === 1) {
+    drawFunnelText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+  if (process === 1) {
+    drawFunnelCenterText(seriesNew, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);
+  }
+  return {
+    center: centerPosition,
+    radius: radius,
+    series: seriesNew
+  };
+}
+
+function drawFunnelText(series, opts, context, eachSpacing, labelAlign, activeWidth, centerPosition) {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    if(item.labelShow === false){
+      continue;
+    }
+    let startX, endX, startY, fontSize;
+    let text = item.formatter ? item.formatter(item,i,series,opts) : util.toFixed(item._proportion_ * 100) + '%';
+    text = item.labelText ? item.labelText : text;
+    if (labelAlign == 'right') {
+      if (i == series.length -1) {
+        startX = (item.funnelArea[2] + centerPosition.x) / 2;
+      } else {
+        startX = (item.funnelArea[2] + series[i + 1].funnelArea[2]) / 2;
+      }
+      endX = startX + activeWidth * 2;
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.color);
+      context.setFillStyle(item.color);
+      context.beginPath();
+      context.moveTo(startX, startY);
+      context.lineTo(endX, startY);
+      context.stroke();
+      context.closePath();
+      context.beginPath();
+      context.moveTo(endX, startY);
+      context.arc(endX, startY, 2 * opts.pix, 0, 2 * Math.PI);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.textColor || opts.fontColor);
+      context.fillText(text, endX + 5, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+    if (labelAlign == 'left') {
+      if (i == series.length -1) {
+        startX = (item.funnelArea[0] + centerPosition.x) / 2;
+      } else {
+        startX = (item.funnelArea[0] + series[i + 1].funnelArea[0]) / 2;
+      }
+      endX = startX - activeWidth * 2;
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;
+      context.setLineWidth(1 * opts.pix);
+      context.setStrokeStyle(item.color);
+      context.setFillStyle(item.color);
+      context.beginPath();
+      context.moveTo(startX, startY);
+      context.lineTo(endX, startY);
+      context.stroke();
+      context.closePath();
+      context.beginPath();
+      context.moveTo(endX, startY);
+      context.arc(endX, startY, 2, 0, 2 * Math.PI);
+      context.closePath();
+      context.fill();
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.textColor || opts.fontColor);
+      context.fillText(text, endX - 5 - measureText(text, fontSize, context), startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+  }
+}
+
+function drawFunnelCenterText(series, opts, context, eachSpacing, labelAlign, activeWidth, centerPosition) {
+  for (let i = 0; i < series.length; i++) {
+    let item = series[i];
+    let startY, fontSize;
+    if (item.centerText) {
+      startY = item.funnelArea[1] + eachSpacing / 2;
+      fontSize = item.centerTextSize * opts.pix || opts.fontSize * opts.pix;
+      context.beginPath();
+      context.setFontSize(fontSize);
+      context.setFillStyle(item.centerTextColor || "#FFFFFF");
+      context.fillText(item.centerText, centerPosition.x - measureText(item.centerText, fontSize, context) / 2, startY + fontSize / 2 - 2);
+      context.closePath();
+      context.stroke();
+      context.closePath();
+    }
+  }
+}
+
+
+function drawCanvas(opts, context) {
+  context.save();
+  context.translate(0, 0.5);
+  context.restore();
+  context.draw();
+}
+
+let Timing = {
+  easeIn: function easeIn(pos) {
+    return Math.pow(pos, 3);
+  },
+  easeOut: function easeOut(pos) {
+    return Math.pow(pos - 1, 3) + 1;
+  },
+  easeInOut: function easeInOut(pos) {
+    if ((pos /= 0.5) < 1) {
+      return 0.5 * Math.pow(pos, 3);
+    } else {
+      return 0.5 * (Math.pow(pos - 2, 3) + 2);
+    }
+  },
+  linear: function linear(pos) {
+    return pos;
+  }
+};
+
+function Animation(opts) {
+  this.isStop = false;
+  opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration;
+  opts.timing = opts.timing || 'easeInOut';
+  let delay = 17;
+  function createAnimationFrame() {
+    if (typeof setTimeout !== 'undefined') {
+      return function(step, delay) {
+        setTimeout(function() {
+          let timeStamp = +new Date();
+          step(timeStamp);
+        }, delay);
+      };
+    } else if (typeof requestAnimationFrame !== 'undefined') {
+      return requestAnimationFrame;
+    } else {
+      return function(step) {
+        step(null);
+      };
+    }
+  };
+  let animationFrame = createAnimationFrame();
+  let startTimeStamp = null;
+  let _step = function step(timestamp) {
+    if (timestamp === null || this.isStop === true) {
+      opts.onProcess && opts.onProcess(1);
+      opts.onAnimationFinish && opts.onAnimationFinish();
+      return;
+    }
+    if (startTimeStamp === null) {
+      startTimeStamp = timestamp;
+    }
+    if (timestamp - startTimeStamp < opts.duration) {
+      let process = (timestamp - startTimeStamp) / opts.duration;
+      let timingFunction = Timing[opts.timing];
+      process = timingFunction(process);
+      opts.onProcess && opts.onProcess(process);
+      animationFrame(_step, delay);
+    } else {
+      opts.onProcess && opts.onProcess(1);
+      opts.onAnimationFinish && opts.onAnimationFinish();
+    }
+  };
+  _step = _step.bind(this);
+  animationFrame(_step, delay);
+}
+
+Animation.prototype.stop = function() {
+  this.isStop = true;
+};
+
+function drawCharts(type, opts, config, context) {
+  let _this = this;
+  let series = opts.series;
+  //兼容ECharts饼图类数据格式
+  if (type === 'pie' || type === 'ring' || type === 'mount' || type === 'rose' || type === 'funnel') {
+    series = fixPieSeries(series, opts, config);
+  }
+  let categories = opts.categories;
+  if (type === 'mount') {
+    categories = [];
+    for (let j = 0; j < series.length; j++) {
+      if(series[j].show !== false) categories.push(series[j].name)
+    }
+    opts.categories = categories;
+  }
+  series = fillSeries(series, opts, config);
+  let duration = opts.animation ? opts.duration : 0;
+  _this.animationInstance && _this.animationInstance.stop();
+  let seriesMA = null;
+  if (type == 'candle') {
+    let average = assign({}, opts.extra.candle.average);
+    if (average.show) {
+      seriesMA = calCandleMA(average.day, average.name, average.color, series[0].data);
+      seriesMA = fillSeries(seriesMA, opts, config);
+      opts.seriesMA = seriesMA;
+    } else if (opts.seriesMA) {
+      seriesMA = opts.seriesMA = fillSeries(opts.seriesMA, opts, config);
+    } else {
+      seriesMA = series;
+    }
+  } else {
+    seriesMA = series;
+  }
+  /* 过滤掉show=false的series */
+  opts._series_ = series = filterSeries(series);
+  //重新计算图表区域
+  opts.area = new Array(4);
+  //复位绘图区域
+  for (let j = 0; j < 4; j++) {
+    opts.area[j] = opts.padding[j] * opts.pix;
+  }
+  //通过计算三大区域:图例、X轴、Y轴的大小,确定绘图区域
+  let _calLegendData = calLegendData(seriesMA, opts, config, opts.chartData, context),
+    legendHeight = _calLegendData.area.wholeHeight,
+    legendWidth = _calLegendData.area.wholeWidth;
+
+  switch (opts.legend.position) {
+    case 'top':
+      opts.area[0] += legendHeight;
+      break;
+    case 'bottom':
+      opts.area[2] += legendHeight;
+      break;
+    case 'left':
+      opts.area[3] += legendWidth;
+      break;
+    case 'right':
+      opts.area[1] += legendWidth;
+      break;
+  }
+
+  let _calYAxisData = {},
+    yAxisWidth = 0;
+  if (opts.type === 'line' || opts.type === 'column'|| opts.type === 'mount' || opts.type === 'area' || opts.type === 'mix' || opts.type === 'candle' || opts.type === 'scatter'  || opts.type === 'bubble' || opts.type === 'bar') {
+      _calYAxisData = calYAxisData(series, opts, config, context);
+      yAxisWidth = _calYAxisData.yAxisWidth;
+    //如果显示Y轴标题
+    if (opts.yAxis.showTitle) {
+      let maxTitleHeight = 0;
+      for (let i = 0; i < opts.yAxis.data.length; i++) {
+        maxTitleHeight = Math.max(maxTitleHeight, opts.yAxis.data[i].titleFontSize ? opts.yAxis.data[i].titleFontSize * opts.pix : config.fontSize)
+      }
+      opts.area[0] += maxTitleHeight;
+    }
+    let rightIndex = 0,
+      leftIndex = 0;
+    //计算主绘图区域左右位置
+    for (let i = 0; i < yAxisWidth.length; i++) {
+      if (yAxisWidth[i].position == 'left') {
+        if (leftIndex > 0) {
+          opts.area[3] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[3] += yAxisWidth[i].width;
+        }
+        leftIndex += 1;
+      } else if (yAxisWidth[i].position == 'right') {
+        if (rightIndex > 0) {
+          opts.area[1] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;
+        } else {
+          opts.area[1] += yAxisWidth[i].width;
+        }
+        rightIndex += 1;
+      }
+    }
+  } else {
+    config.yAxisWidth = yAxisWidth;
+  }
+  opts.chartData.yAxisData = _calYAxisData;
+
+  if (opts.categories && opts.categories.length && opts.type !== 'radar' && opts.type !== 'gauge' && opts.type !== 'bar') {
+    opts.chartData.xAxisData = getXAxisPoints(opts.categories, opts, config);
+    let _calCategoriesData = calCategoriesData(opts.categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),
+      xAxisHeight = _calCategoriesData.xAxisHeight,
+      angle = _calCategoriesData.angle;
+    config.xAxisHeight = xAxisHeight;
+    config._xAxisTextAngle_ = angle;
+    opts.area[2] += xAxisHeight;
+    opts.chartData.categoriesData = _calCategoriesData;
+  } else {
+    if (opts.type === 'line' || opts.type === 'area' || opts.type === 'scatter' || opts.type === 'bubble' || opts.type === 'bar') {
+      opts.chartData.xAxisData = calXAxisData(series, opts, config, context);
+      categories = opts.chartData.xAxisData.rangesFormat;
+      let _calCategoriesData = calCategoriesData(categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),
+        xAxisHeight = _calCategoriesData.xAxisHeight,
+        angle = _calCategoriesData.angle;
+      config.xAxisHeight = xAxisHeight;
+      config._xAxisTextAngle_ = angle;
+      opts.area[2] += xAxisHeight;
+      opts.chartData.categoriesData = _calCategoriesData;
+    } else {
+      opts.chartData.xAxisData = {
+        xAxisPoints: []
+      };
+    }
+  }
+
+  //计算右对齐偏移距离
+  if (opts.enableScroll && opts.xAxis.scrollAlign == 'right' && opts._scrollDistance_ === undefined) {
+    let offsetLeft = 0,
+      xAxisPoints = opts.chartData.xAxisData.xAxisPoints,
+      startX = opts.chartData.xAxisData.startX,
+      endX = opts.chartData.xAxisData.endX,
+      eachSpacing = opts.chartData.xAxisData.eachSpacing;
+    let totalWidth = eachSpacing * (xAxisPoints.length - 1);
+    let screenWidth = endX - startX;
+    offsetLeft = screenWidth - totalWidth;
+    _this.scrollOption.currentOffset = offsetLeft;
+    _this.scrollOption.startTouchX = offsetLeft;
+    _this.scrollOption.distance = 0;
+    _this.scrollOption.lastMoveTime = 0;
+    opts._scrollDistance_ = offsetLeft;
+  }
+
+  if (type === 'pie' || type === 'ring' || type === 'rose') {
+    config._pieTextMaxLength_ = opts.dataLabel === false ? 0 : getPieTextMaxLength(seriesMA, config, context, opts);
+  }
+  
+  switch (type) {
+    case 'word':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawWordCloudDataPoints(series, opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'map':
+      context.clearRect(0, 0, opts.width, opts.height);
+      drawMapDataPoints(series, opts, config, context);
+      setTimeout(()=>{
+        this.uevent.trigger('renderComplete');
+      },50)
+      break;
+    case 'funnel':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.funnelData = drawFunnelDataPoints(series, opts, config, context, chartProcess);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'line':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawLineDataPoints = drawLineDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawLineDataPoints.xAxisPoints,
+            calPoints = _drawLineDataPoints.calPoints,
+            eachSpacing = _drawLineDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'scatter':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawScatterDataPoints = drawScatterDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawScatterDataPoints.xAxisPoints,
+            calPoints = _drawScatterDataPoints.calPoints,
+            eachSpacing = _drawScatterDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bubble':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawBubbleDataPoints = drawBubbleDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawBubbleDataPoints.xAxisPoints,
+            calPoints = _drawBubbleDataPoints.calPoints,
+            eachSpacing = _drawBubbleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mix':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawMixDataPoints = drawMixDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawMixDataPoints.xAxisPoints,
+            calPoints = _drawMixDataPoints.calPoints,
+            eachSpacing = _drawMixDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'column':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawColumnDataPoints = drawColumnDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawColumnDataPoints.xAxisPoints,
+            calPoints = _drawColumnDataPoints.calPoints,
+            eachSpacing = _drawColumnDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'mount':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawMountDataPoints = drawMountDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawMountDataPoints.xAxisPoints,
+            calPoints = _drawMountDataPoints.calPoints,
+            eachSpacing = _drawMountDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'bar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawXAxis(categories, opts, config, context);
+          let _drawBarDataPoints = drawBarDataPoints(series, opts, config, context, chartProcess),
+            yAxisPoints = _drawBarDataPoints.yAxisPoints,
+            calPoints = _drawBarDataPoints.calPoints,
+            eachSpacing = _drawBarDataPoints.eachSpacing;
+          opts.chartData.yAxisPoints = yAxisPoints;
+          opts.chartData.xAxisPoints = opts.chartData.xAxisData.xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, yAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'area':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawAreaDataPoints = drawAreaDataPoints(series, opts, config, context, chartProcess),
+            xAxisPoints = _drawAreaDataPoints.xAxisPoints,
+            calPoints = _drawAreaDataPoints.calPoints,
+            eachSpacing = _drawAreaDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'ring':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, chartProcess);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'pie':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, chartProcess);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'rose':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.pieData = drawRoseDataPoints(series, opts, config, context, chartProcess);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'radar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.radarData = drawRadarDataPoints(series, opts, config, context, chartProcess);
+          drawLegend(opts.series, opts, config, context, opts.chartData);
+          drawToolTipBridge(opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'arcbar':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.arcbarData = drawArcbarDataPoints(series, opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'gauge':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          opts.chartData.gaugeData = drawGaugeDataPoints(categories, series, opts, config, context, chartProcess);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+    case 'candle':
+      this.animationInstance = new Animation({
+        timing: opts.timing,
+        duration: duration,
+        onProcess: function onProcess(process) {
+          context.clearRect(0, 0, opts.width, opts.height);
+          if (opts.rotate) {
+            contextRotate(context, opts);
+          }
+          drawYAxisGrid(categories, opts, config, context);
+          drawXAxis(categories, opts, config, context);
+          let _drawCandleDataPoints = drawCandleDataPoints(series, seriesMA, opts, config, context, chartProcess),
+            xAxisPoints = _drawCandleDataPoints.xAxisPoints,
+            calPoints = _drawCandleDataPoints.calPoints,
+            eachSpacing = _drawCandleDataPoints.eachSpacing;
+          opts.chartData.xAxisPoints = xAxisPoints;
+          opts.chartData.calPoints = calPoints;
+          opts.chartData.eachSpacing = eachSpacing;
+          drawYAxis(series, opts, config, context);
+          if (opts.enableMarkLine !== false && process === 1) {
+            drawMarkLine(opts, config, context);
+          }
+          if (seriesMA) {
+            drawLegend(seriesMA, opts, config, context, opts.chartData);
+          } else {
+            drawLegend(opts.series, opts, config, context, opts.chartData);
+          }
+          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);
+          drawCanvas(opts, context);
+        },
+        onAnimationFinish: function onAnimationFinish() {
+          _this.uevent.trigger('renderComplete');
+        }
+      });
+      break;
+  }
+}
+
+export class uChartsEvent {
+  constructor() { this.events = {}; }
+
+  addEventListener(type, listener) {
+  this.events[type] = this.events[type] || [];
+  this.events[type].push(listener);
+}
+
+  delEventListener(type) {
+  this.events[type] = [];
+}
+
+  trigger() {
+  for (let _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+    args[_key] = arguments[_key];
+  }
+  let type = args[0];
+  let params = args.slice(1);
+  if (!!this.events[type]) {
+    this.events[type].forEach(function(listener) {
+      try {
+        listener.apply(null, params);
+      } catch (e) {
+          //console.log('[uCharts] '+e);
+      }
+    });
+  }
+}
+
+}
+
+export class uCharts {
+  constructor(opts) {
+  opts.pix = opts.pixelRatio ? opts.pixelRatio : 1;
+  opts.fontSize = opts.fontSize ? opts.fontSize : 13;
+  opts.fontColor = opts.fontColor ? opts.fontColor : config.fontColor;
+  if (opts.background == "" || opts.background == "none") {
+    opts.background = "#FFFFFF"
+  }
+  opts.title = assign({}, opts.title);
+  opts.subtitle = assign({}, opts.subtitle);
+  opts.duration = opts.duration ? opts.duration : 1000;
+  opts.yAxis = assign({}, {
+    data: [],
+    showTitle: false,
+    disabled: false,
+    disableGrid: false,
+    gridSet: 'number',
+    splitNumber: 5,
+    gridType: 'solid',
+    dashLength: 4 * opts.pix,
+    gridColor: '#cccccc',
+    padding: 10,
+    fontColor: '#666666'
+  }, opts.yAxis);
+  opts.xAxis = assign({}, {
+    rotateLabel: false,
+    rotateAngle:45,
+    disabled: false,
+    disableGrid: false,
+    splitNumber: 5,
+    calibration:false,
+    fontColor: '#666666',
+    fontSize: 13,
+    lineHeight: 20,
+    marginTop: 0,
+    gridType: 'solid',
+    dashLength: 4,
+    scrollAlign: 'left',
+    boundaryGap: 'center',
+    axisLine: true,
+    axisLineColor: '#cccccc',
+    titleFontSize: 13,
+    titleOffsetY: 0,
+    titleOffsetX: 0,
+    titleFontColor: '#666666'
+  }, opts.xAxis);
+  opts.xAxis.scrollPosition = opts.xAxis.scrollAlign;
+  opts.legend = assign({}, {
+    show: true,
+    position: 'bottom',
+    float: 'center',
+    backgroundColor: 'rgba(0,0,0,0)',
+    borderColor: 'rgba(0,0,0,0)',
+    borderWidth: 0,
+    padding: 5,
+    margin: 5,
+    itemGap: 10,
+    fontSize: opts.fontSize,
+    lineHeight: opts.fontSize,
+    fontColor: opts.fontColor,
+    formatter: {},
+    hiddenColor: '#CECECE'
+  }, opts.legend);
+  opts.extra = assign({
+    tooltip:{
+      legendShape: 'auto'
+    }
+  }, opts.extra);
+  opts.rotate = opts.rotate ? true : false;
+  opts.animation = opts.animation ? true : false;
+  opts.rotate = opts.rotate ? true : false;
+  opts.canvas2d = opts.canvas2d ? true : false;
+  
+  let config = assign({}, config);
+  config.color = opts.color ? opts.color : config.color;
+  if (opts.type == 'pie') {
+    config.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.pie.labelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+  }
+  if (opts.type == 'ring') {
+    config.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.ring.labelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+  }
+  if (opts.type == 'rose') {
+    config.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.rose.labelWidth * opts.pix || config.pieChartLinePadding * opts.pix;
+  }
+  config.pieChartTextPadding = opts.dataLabel === false ? 0 : config.pieChartTextPadding * opts.pix;
+
+  //屏幕旋转
+  config.rotate = opts.rotate;
+  if (opts.rotate) {
+    let tempWidth = opts.width;
+    let tempHeight = opts.height;
+    opts.width = tempHeight;
+    opts.height = tempWidth;
+  }
+
+  //适配高分屏
+  opts.padding = opts.padding ? opts.padding : config.padding;
+  config.yAxisWidth = config.yAxisWidth * opts.pix;
+  config.fontSize = opts.fontSize * opts.pix;
+  config.titleFontSize = config.titleFontSize * opts.pix;
+  config.subtitleFontSize = config.subtitleFontSize * opts.pix;
+  if(!opts.context){
+    throw new Error('[uCharts] 未获取到context!注意:v2.0版本后,需要自行获取canvas的绘图上下文并传入opts.context!');
+  }
+  this.context = opts.context;
+  if (!this.context.setTextAlign) {
+    this.context.setStrokeStyle = function(e) {
+      return this.strokeStyle = e;
+    }
+    this.context.setLineWidth = function(e) {
+      return this.lineWidth = e;
+    }
+    this.context.setLineCap = function(e) {
+      return this.lineCap = e;
+    }
+    this.context.setFontSize = function(e) {
+      return this.font = e + "px sans-serif";
+    }
+    this.context.setFillStyle = function(e) {
+      return this.fillStyle = e;
+    }
+    this.context.setTextAlign = function(e) {
+      return this.textAlign = e;
+    }
+    this.context.setTextBaseline = function(e) {
+      return this.textBaseline = e;
+    }
+    this.context.setShadow = function(offsetX,offsetY,blur,color) {
+      this.shadowColor = color;
+      this.shadowOffsetX = offsetX;
+      this.shadowOffsetY = offsetY;
+      this.shadowBlur = blur;
+    }
+    this.context.draw = function() {}
+  }
+  //兼容NVUEsetLineDash
+  if(!this.context.setLineDash){
+    this.context.setLineDash = function(e) {}
+  }
+  opts.chartData = {};
+  this.uevent = new uChartsEvent();
+  this.scrollOption = {
+    currentOffset: 0,
+    startTouchX: 0,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  this.opts = opts;
+  this.config = config;
+  drawCharts.call(this, opts.type, opts, config, this.context);
+};
+
+  updateData() {
+  let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+  this.opts = assign({}, this.opts, data);
+  this.opts.updateData = true;
+  let scrollPosition = data.scrollPosition || 'current';
+  switch (scrollPosition) {
+    case 'current':
+      this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+      break;
+    case 'left':
+      this.opts._scrollDistance_ = 0;
+      this.scrollOption = {
+        currentOffset: 0,
+        startTouchX: 0,
+        distance: 0,
+        lastMoveTime: 0
+      };
+      break;
+    case 'right':
+      let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context), yAxisWidth = _calYAxisData.yAxisWidth;
+      this.config.yAxisWidth = yAxisWidth;
+      let offsetLeft = 0;
+      let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config), xAxisPoints = _getXAxisPoints0.xAxisPoints,
+        startX = _getXAxisPoints0.startX,
+        endX = _getXAxisPoints0.endX,
+        eachSpacing = _getXAxisPoints0.eachSpacing;
+      let totalWidth = eachSpacing * (xAxisPoints.length - 1);
+      let screenWidth = endX - startX;
+      offsetLeft = screenWidth - totalWidth;
+      this.scrollOption = {
+        currentOffset: offsetLeft,
+        startTouchX: offsetLeft,
+        distance: 0,
+        lastMoveTime: 0
+      };
+      this.opts._scrollDistance_ = offsetLeft;
+      break;
+  }
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+};
+
+  zoom() {
+  let val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.opts.xAxis.itemCount;
+  if (this.opts.enableScroll !== true) {
+    console.log('[uCharts] 请启用滚动条后使用')
+    return;
+  }
+  //当前屏幕中间点
+  let centerPoint = Math.round(Math.abs(this.scrollOption.currentOffset) / this.opts.chartData.eachSpacing) + Math.round(this.opts.xAxis.itemCount / 2);
+  this.opts.animation = false;
+  this.opts.xAxis.itemCount = val.itemCount;
+  //重新计算x轴偏移距离
+  let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context),
+    yAxisWidth = _calYAxisData.yAxisWidth;
+  this.config.yAxisWidth = yAxisWidth;
+  let offsetLeft = 0;
+  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),
+    xAxisPoints = _getXAxisPoints0.xAxisPoints,
+    startX = _getXAxisPoints0.startX,
+    endX = _getXAxisPoints0.endX,
+    eachSpacing = _getXAxisPoints0.eachSpacing;
+  let centerLeft = eachSpacing * centerPoint;
+  let screenWidth = endX - startX;
+  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+  offsetLeft = screenWidth / 2 - centerLeft;
+  if (offsetLeft > 0) {
+    offsetLeft = 0;
+  }
+  if (offsetLeft < MaxLeft) {
+    offsetLeft = MaxLeft;
+  }
+  this.scrollOption = {
+    currentOffset: offsetLeft,
+    startTouchX: 0,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);
+  this.opts._scrollDistance_ = offsetLeft;
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+};
+
+  dobuleZoom(e) {
+  if (this.opts.enableScroll !== true) {
+    console.log('[uCharts] 请启用滚动条后使用')
+    return;
+  }
+  const tcs = e.changedTouches;
+  if (tcs.length < 2) {
+    return;
+  }
+  for (let i = 0; i < tcs.length; i++) {
+    tcs[i].x = tcs[i].x ? tcs[i].x : tcs[i].clientX;
+    tcs[i].y = tcs[i].y ? tcs[i].y : tcs[i].clientY;
+  }
+  const ntcs = [getTouches(tcs[0], this.opts, e),getTouches(tcs[1], this.opts, e)]; 
+  const xlength = Math.abs(ntcs[0].x - ntcs[1].x);
+  // 记录初始的两指之间的数据
+  if(!this.scrollOption.moveCount){
+    let cts0 = {changedTouches:[{x:tcs[0].x,y:this.opts.area[0] / this.opts.pix + 2}]};
+    let cts1 = {changedTouches:[{x:tcs[1].x,y:this.opts.area[0] / this.opts.pix + 2}]};
+    if(this.opts.rotate){
+      cts0 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[0].y}]};
+      cts1 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[1].y}]};
+    }
+    const moveCurrent1 = this.getCurrentDataIndex(cts0).index;
+    const moveCurrent2 = this.getCurrentDataIndex(cts1).index;
+    const moveCount = Math.abs(moveCurrent1 - moveCurrent2);
+    this.scrollOption.moveCount = moveCount;
+    this.scrollOption.moveCurrent1 = Math.min(moveCurrent1, moveCurrent2);
+    this.scrollOption.moveCurrent2 = Math.max(moveCurrent1, moveCurrent2);
+    return;
+  }
+  
+  let currentEachSpacing = xlength / this.scrollOption.moveCount;
+  let itemCount = (this.opts.width - this.opts.area[1] - this.opts.area[3]) / currentEachSpacing;
+  itemCount = itemCount <= 2 ? 2 : itemCount;
+  itemCount = itemCount >= this.opts.categories.length ? this.opts.categories.length : itemCount;
+  this.opts.animation = false;
+  this.opts.xAxis.itemCount = itemCount;
+  // 重新计算滚动条偏移距离
+  let offsetLeft = 0;
+  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),
+    xAxisPoints = _getXAxisPoints0.xAxisPoints,
+    startX = _getXAxisPoints0.startX,
+    endX = _getXAxisPoints0.endX,
+    eachSpacing = _getXAxisPoints0.eachSpacing;
+  let currentLeft = eachSpacing * this.scrollOption.moveCurrent1;
+  let screenWidth = endX - startX;
+  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);
+  offsetLeft = -currentLeft+Math.min(ntcs[0].x,ntcs[1].x)-this.opts.area[3]-eachSpacing;
+  if (offsetLeft > 0) {
+    offsetLeft = 0;
+  }
+  if (offsetLeft < MaxLeft) {
+    offsetLeft = MaxLeft;
+  }
+  this.scrollOption.currentOffset= offsetLeft;
+  this.scrollOption.startTouchX= 0;
+  this.scrollOption.distance=0;
+  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);
+  this.opts._scrollDistance_ = offsetLeft;
+  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+}
+
+  stopAnimation() {
+  this.animationInstance && this.animationInstance.stop();
+};
+
+  addEventListener(type, listener) {
+  this.uevent.addEventListener(type, listener);
+};
+
+  delEventListener(type) {
+  this.uevent.delEventListener(type);
+};
+
+  getCurrentDataIndex(e) {
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    if (this.opts.type === 'pie' || this.opts.type === 'ring') {
+      return findPieChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.pieData, this.opts);
+    } else if (this.opts.type === 'rose') {
+      return findRoseChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.pieData, this.opts);
+    } else if (this.opts.type === 'radar') {
+      return findRadarChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.radarData, this.opts.categories.length);
+    } else if (this.opts.type === 'funnel') {
+      return findFunnelChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.funnelData);
+    } else if (this.opts.type === 'map') {
+      return findMapChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts);
+    } else if (this.opts.type === 'word') {
+      return findWordChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.wordCloudData);
+    } else if (this.opts.type === 'bar') {
+      return findBarChartCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));
+    } else {
+      return findCurrentIndex({
+        x: _touches$.x,
+        y: _touches$.y
+      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));
+    }
+  }
+  return -1;
+};
+
+  getLegendDataIndex(e) {
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    return findLegendIndex({
+      x: _touches$.x,
+      y: _touches$.y
+    }, this.opts.chartData.legendData);
+  }
+  return -1;
+};
+
+  touchLegend(e) {
+  let option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    let index = this.getLegendDataIndex(e);
+    if (index >= 0) {
+      if (this.opts.type == 'candle') {
+        this.opts.seriesMA[index].show = !this.opts.seriesMA[index].show;
+      } else {
+        this.opts.series[index].show = !this.opts.series[index].show;
+      }
+      this.opts.animation = option.animation ? true : false;
+      this.opts._scrollDistance_ = this.scrollOption.currentOffset;
+      drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);
+    }
+  }
+
+};
+
+  showToolTip(e) {
+  let option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (!touches) {
+    console.log("[uCharts] 未获取到event坐标信息");
+  }
+  let _touches$ = getTouches(touches, this.opts, e);
+  let currentOffset = this.scrollOption.currentOffset;
+  let opts = assign({}, this.opts, {
+    _scrollDistance_: currentOffset,
+    animation: false
+  });
+  if (this.opts.type === 'line' || this.opts.type === 'area' || this.opts.type === 'column' || this.opts.type === 'scatter' || this.opts.type === 'bubble') {
+    let current = this.getCurrentDataIndex(e);
+    let index = option.index == undefined ? current.index : option.index;
+    if (index > -1 || index.length>0) {
+      let seriesData = getSeriesDataItem(this.opts.series, index, current.group);
+      if (seriesData.length !== 0) {
+        let _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList !== undefined ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index,
+          group: current.group
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'mount') {
+    let index = option.index == undefined ? this.getCurrentDataIndex(e).index : option.index;
+    if (index > -1) {
+      let opts = assign({}, this.opts, {animation: false});
+      let seriesData = assign({}, opts._series_[index]);
+      let textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      let offset = {
+        x: opts.chartData.calPoints[index].x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'bar') {
+    let current = this.getCurrentDataIndex(e);
+    let index = option.index == undefined ? current.index : option.index;
+    if (index > -1 || index.length>0) {
+      let seriesData = getSeriesDataItem(this.opts.series, index, current.group);
+      if (seriesData.length !== 0) {
+        let _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.x = _touches$.x;
+        opts.tooltip = {
+          textList: option.textList !== undefined ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'mix') {
+    let current = this.getCurrentDataIndex(e);
+    let index = option.index == undefined ? current.index : option.index;
+    if (index > -1) {
+      let currentOffset = this.scrollOption.currentOffset;
+      let opts = assign({}, this.opts, {
+        _scrollDistance_: currentOffset,
+        animation: false
+      });
+      let seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        let _getMixToolTipData = getMixToolTipData(seriesData, this.opts, index, this.opts.categories, option),
+          textList = _getMixToolTipData.textList,
+          offset = _getMixToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'candle') {
+    let current = this.getCurrentDataIndex(e);
+    let index = option.index == undefined ? current.index : option.index;
+    if (index > -1) {
+      let currentOffset = this.scrollOption.currentOffset;
+      let opts = assign({}, this.opts, {
+        _scrollDistance_: currentOffset,
+        animation: false
+      });
+      let seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        let _getToolTipData = getCandleToolTipData(this.opts.series[0].data, seriesData, this.opts, index, this.opts.categories, this.opts.extra.candle, option),
+          textList = _getToolTipData.textList,
+          offset = _getToolTipData.offset;
+        offset.y = _touches$.y;
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'pie' || this.opts.type === 'ring' || this.opts.type === 'rose' || this.opts.type === 'funnel') {
+    let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      let opts = assign({}, this.opts, {animation: false});
+      let seriesData = assign({}, opts._series_[index]);
+      let textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      let offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'map') {
+    let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      let opts = assign({}, this.opts, {animation: false});
+      let seriesData = assign({}, this.opts.series[index]);
+      seriesData.name = seriesData.properties.name
+      let textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      let offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    opts.updateData = false;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'word') {
+    let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      let opts = assign({}, this.opts, {animation: false});
+      let seriesData = assign({}, this.opts.series[index]);
+      let textList = [{
+        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,
+        color: seriesData.color,
+        legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? seriesData.legendShape : this.opts.extra.tooltip.legendShape
+      }];
+      let offset = {
+        x: _touches$.x,
+        y: _touches$.y
+      };
+      opts.tooltip = {
+        textList: option.textList ? option.textList : textList,
+        offset: option.offset !== undefined ? option.offset : offset,
+        option: option,
+        index: index
+      };
+    }
+    opts.updateData = false;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+  if (this.opts.type === 'radar') {
+    let index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;
+    if (index > -1) {
+      let opts = assign({}, this.opts, {animation: false});
+      let seriesData = getSeriesDataItem(this.opts.series, index);
+      if (seriesData.length !== 0) {
+        let textList = seriesData.map((item) => {
+          return {
+            text: option.formatter ? option.formatter(item, this.opts.categories[index], index, this.opts) : item.name + ': ' + item.data,
+            color: item.color,
+            legendShape: this.opts.extra.tooltip.legendShape == 'auto' ? item.legendShape : this.opts.extra.tooltip.legendShape
+          };
+        });
+        let offset = {
+          x: _touches$.x,
+          y: _touches$.y
+        };
+        opts.tooltip = {
+          textList: option.textList ? option.textList : textList,
+          offset: option.offset !== undefined ? option.offset : offset,
+          option: option,
+          index: index
+        };
+      }
+    }
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+  }
+};
+
+  translate(distance) {
+  this.scrollOption = {
+    currentOffset: distance,
+    startTouchX: distance,
+    distance: 0,
+    lastMoveTime: 0
+  };
+  let opts = assign({}, this.opts, {
+    _scrollDistance_: distance,
+    animation: false
+  });
+  drawCharts.call(this, this.opts.type, opts, this.config, this.context);
+};
+
+  scrollStart(e) {
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  let _touches$ = getTouches(touches, this.opts, e);
+  if (touches && this.opts.enableScroll === true) {
+    this.scrollOption.startTouchX = _touches$.x;
+  }
+};
+
+  scroll(e) {
+  if (this.scrollOption.lastMoveTime === 0) {
+    this.scrollOption.lastMoveTime = Date.now();
+  }
+  let Limit = this.opts.touchMoveLimit || 60;
+  let currMoveTime = Date.now();
+  let duration = currMoveTime - this.scrollOption.lastMoveTime;
+  if (duration < Math.floor(1000 / Limit)) return;
+  if (this.scrollOption.startTouchX == 0) return;
+  this.scrollOption.lastMoveTime = currMoveTime;
+  let touches = null;
+  if (e.changedTouches) {
+    touches = e.changedTouches[0];
+  } else {
+    touches = e.mp.changedTouches[0];
+  }
+  if (touches && this.opts.enableScroll === true) {
+    let _touches$ = getTouches(touches, this.opts, e);
+    let _distance;
+    _distance = _touches$.x - this.scrollOption.startTouchX;
+    let currentOffset = this.scrollOption.currentOffset;
+    let validDistance = calValidDistance(this, currentOffset + _distance, this.opts.chartData, this.config, this.opts);
+    this.scrollOption.distance = _distance = validDistance - currentOffset;
+    let opts = assign({}, this.opts, {
+      _scrollDistance_: currentOffset + _distance,
+      animation: false
+    });
+		this.opts = opts;
+    drawCharts.call(this, opts.type, opts, this.config, this.context);
+    return currentOffset + _distance;
+  }
+};
+
+  scrollEnd(e) {
+  if (this.opts.enableScroll === true) {
+    let _scrollOption = this.scrollOption,
+      currentOffset = _scrollOption.currentOffset,
+      distance = _scrollOption.distance;
+    this.scrollOption.currentOffset = currentOffset + distance;
+    this.scrollOption.distance = 0;
+    this.scrollOption.moveCount = 0;
+  }
+}
+}
+
+export default uCharts;

+ 88 - 0
mini-ui-packages/mini-charts/src/lib/utils/collision.ts

@@ -0,0 +1,88 @@
+// 碰撞检测工具函数
+// 来源: u-charts.ts
+
+/**
+ * 坐标点接口
+ */
+export interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * 碰撞对象接口
+ */
+export interface CollisionObject {
+  start: Point;
+  end?: Point;
+  width: number;
+  height: number;
+  center?: Point;
+  area?: {
+    start: Point;
+    end: Point;
+    width: number;
+    height: number;
+  };
+}
+
+/**
+ * 工具函数集合接口
+ */
+export interface Util {
+  isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean;
+}
+
+/**
+ * 避免碰撞
+ * 通过调整对象位置来避免与目标对象碰撞
+ * @param obj - 待调整的碰撞对象
+ * @param target - 目标碰撞对象数组
+ * @returns 调整后的碰撞对象
+ */
+export function avoidCollision(
+  obj: CollisionObject,
+  target?: CollisionObject[]
+): CollisionObject {
+  if (target) {
+    // is collision test
+    while (util.isCollision(obj, target[0] as CollisionObject)) {
+      if (obj.start.x > 0) {
+        obj.start.y--;
+      } else if (obj.start.x < 0) {
+        obj.start.y++;
+      } else {
+        if (obj.start.y > 0) {
+          obj.start.y++;
+        } else {
+          obj.start.y--;
+        }
+      }
+    }
+  }
+  return obj;
+}
+
+/**
+ * 检测两个对象是否碰撞
+ * @param obj1 - 第一个碰撞对象
+ * @param obj2 - 第二个碰撞对象
+ * @returns 是否碰撞
+ */
+export function isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean {
+  obj1.end = {} as Point;
+  obj1.end.x = obj1.start.x + obj1.width;
+  obj1.end.y = obj1.start.y - obj1.height;
+  obj2.end = {} as Point;
+  obj2.end.x = obj2.start.x + obj2.width;
+  obj2.end.y = obj2.start.y - obj2.height;
+  let flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y;
+  return !flag;
+}
+
+/**
+ * 工具函数对象(保持与原 u-charts 兼容)
+ */
+export const util: Util = {
+  isCollision: isCollision
+};

+ 20 - 0
mini-ui-packages/mini-charts/src/lib/utils/color.ts

@@ -0,0 +1,20 @@
+// 颜色工具函数
+// 来源: u-charts.ts
+
+/**
+ * 将十六进制颜色转换为 RGBA 格式
+ * @param hexValue - 十六进制颜色值 (如 #FF0000 或 #F00)
+ * @param opc - 透明度 (0-1)
+ * @returns RGBA 颜色字符串 (如 rgba(255,0,0,0.5))
+ */
+export function hexToRgb(hexValue: string, opc: number): string {
+  let rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+  let hex = hexValue.replace(rgx, function(m, r, g, b) {
+    return r + r + g + g + b + b;
+  });
+  let rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+  let r = parseInt(rgb![1], 16);
+  let g = parseInt(rgb![2], 16);
+  let b = parseInt(rgb![3], 16);
+  return 'rgba(' + r + ',' + g + ',' + b + ',' + opc + ')';
+}

+ 155 - 0
mini-ui-packages/mini-charts/src/lib/utils/coordinate.ts

@@ -0,0 +1,155 @@
+// 坐标转换工具函数
+// 来源: u-charts.ts
+
+/**
+ * 坐标点接口
+ */
+export interface Point {
+  x: number;
+  y: number;
+}
+
+/**
+ * 中心点接口
+ */
+export interface Center {
+  x: number;
+  y: number;
+}
+
+/**
+ * 坐标原点转换
+ * @param x - 原 x 坐标
+ * @param y - 原 y 坐标
+ * @param center - 中心点坐标
+ * @returns 转换后的坐标点
+ */
+export function convertCoordinateOrigin(x: number, y: number, center: Center): Point {
+  return {
+    x: center.x + x,
+    y: center.y - y
+  };
+}
+
+/**
+ * 判断角度是否在指定范围内
+ * @param angle - 待判断的角度(弧度)
+ * @param startAngle - 起始角度(弧度)
+ * @param endAngle - 结束角度(弧度)
+ * @returns 是否在范围内
+ */
+export function isInAngleRange(
+  angle: number,
+  startAngle: number,
+  endAngle: number
+): boolean {
+  function adjust(angle: number): number {
+    while (angle < 0) {
+      angle += 2 * Math.PI;
+    }
+    while (angle > 2 * Math.PI) {
+      angle -= 2 * Math.PI;
+    }
+    return angle;
+  }
+  angle = adjust(angle);
+  startAngle = adjust(startAngle);
+  endAngle = adjust(endAngle);
+  if (startAngle > endAngle) {
+    endAngle += 2 * Math.PI;
+    if (angle < startAngle) {
+      angle += 2 * Math.PI;
+    }
+  }
+  return angle >= startAngle && angle <= endAngle;
+}
+
+/**
+ * 图表配置接口(用于 calValidDistance)
+ */
+export interface ChartConfig {
+  type: string;
+  width: number;
+  height: number;
+  area: number[];
+  extra?: {
+    mount?: {
+      widthRatio?: number;
+    };
+  };
+  xAxis?: {
+    scrollPosition?: string | number;
+  };
+  chartData?: {
+    xAxisData?: {
+      xAxisPoints: Point[];
+    };
+    eachSpacing?: number;
+  };
+  rotate?: boolean;
+  pix?: number;
+}
+
+/**
+ * 图表数据接口
+ */
+export interface ChartData {
+  eachSpacing: number;
+}
+
+/**
+ * 滚动选项接口
+ */
+export interface ScrollOption {
+  position: string | number;
+}
+
+/**
+ * 图表实例接口(用于 calValidDistance)
+ */
+export interface UChartInstance {
+  uevent: {
+    trigger: (event: string) => void;
+  };
+  scrollOption: ScrollOption;
+}
+
+/**
+ * 计算有效的滚动距离
+ * @param self - 图表实例
+ * @param distance - 滚动距离
+ * @param chartData - 图表数据
+ * @param config - 图表配置
+ * @param opts - 图表选项
+ * @returns 有效的滚动距离
+ */
+export function calValidDistance(
+  self: UChartInstance,
+  distance: number,
+  chartData: ChartData,
+  config: ChartConfig,
+  opts: ChartConfig
+): number {
+  let dataChartAreaWidth = opts.width - opts.area[1] - opts.area[3];
+  let dataChartWidth = chartData.eachSpacing * (opts.chartData!.xAxisData!.xAxisPoints.length - 1);
+  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){
+    if(opts.extra.mount.widthRatio > 2) opts.extra.mount.widthRatio = 2
+    dataChartWidth += (opts.extra.mount.widthRatio - 1) * chartData.eachSpacing;
+  }
+  let validDistance = distance;
+  if (distance >= 0) {
+    validDistance = 0;
+    self.uevent.trigger('scrollLeft');
+    self.scrollOption.position = 'left'
+    opts.xAxis!.scrollPosition = 'left';
+  } else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) {
+    validDistance = dataChartAreaWidth - dataChartWidth;
+    self.uevent.trigger('scrollRight');
+    self.scrollOption.position = 'right'
+    opts.xAxis!.scrollPosition = 'right';
+  } else {
+    self.scrollOption.position = distance
+    opts.xAxis!.scrollPosition = distance;
+  }
+  return validDistance;
+}

+ 33 - 0
mini-ui-packages/mini-charts/src/lib/utils/index.ts

@@ -0,0 +1,33 @@
+// 工具函数统一导出
+// 来源: u-charts.ts 模块化重构
+
+// 颜色工具函数
+export * from './color';
+
+// 数学工具函数
+export * from './math';
+
+// 坐标转换工具函数(选择性导出以避免 Point 类型冲突)
+export {
+  convertCoordinateOrigin,
+  isInAngleRange,
+  calValidDistance,
+  type ChartConfig,
+  type ChartData,
+  type ScrollOption,
+  type UChartInstance
+} from './coordinate';
+
+// 文本测量工具函数
+export * from './text';
+
+// 碰撞检测工具函数(选择性导出以避免 Point 类型冲突)
+export {
+  avoidCollision,
+  isCollision,
+  util,
+  type CollisionObject
+} from './collision';
+
+// 其他工具函数
+export * from './misc';

+ 92 - 0
mini-ui-packages/mini-charts/src/lib/utils/math.ts

@@ -0,0 +1,92 @@
+// 数学工具函数
+// 来源: u-charts.ts
+
+/**
+ * 计算数值范围(向上或向下取整到指定倍数)
+ * @param num - 输入数值
+ * @param type - 取整类型: 'upper' 向上取整, 'lower' 向下取整
+ * @param limit - 倍数限制,默认 10
+ * @returns 取整后的数值
+ * @throws Error 如果 num 不是数字
+ */
+export function findRange(num: number, type?: 'upper' | 'lower', limit?: number): number {
+  if (isNaN(num)) {
+    throw new Error('[uCharts] series数据需为Number格式');
+  }
+  limit = limit || 10;
+  type = type ? type : 'upper';
+  let multiple = 1;
+  while (limit < 1) {
+    limit *= 10;
+    multiple *= 10;
+  }
+  if (type === 'upper') {
+    num = Math.ceil(num * multiple);
+  } else {
+    num = Math.floor(num * multiple);
+  }
+  while (num % limit !== 0) {
+    if (type === 'upper') {
+      if (num == num + 1) { //修复数据值过大num++无效的bug by 向日葵 @xrk_jy
+        break;
+      }
+      num++;
+    } else {
+      num--;
+    }
+  }
+  return num / multiple;
+}
+
+/**
+ * K线图数据点接口
+ */
+export interface CandleDataPoint {
+  [key: string]: number;
+}
+
+/**
+ * 移动平均线系列项接口
+ */
+export interface MASeriesItem {
+  data: (number | null)[];
+  name: string;
+  color: string;
+}
+
+/**
+ * 计算 K 线图的移动平均线
+ * @param dayArr - MA 天数数组 (如 [5, 10, 20, 30])
+ * @param nameArr - MA 名称数组 (如 ['MA5', 'MA10', 'MA20', 'MA30'])
+ * @param colorArr - MA 颜色数组
+ * @param kdata - K 线数据数组,每个元素包含 OHLC 数据
+ * @returns MA 系列数据数组
+ */
+export function calCandleMA(
+  dayArr: number[],
+  nameArr: string[],
+  colorArr: string[],
+  kdata: CandleDataPoint[]
+): MASeriesItem[] {
+  let seriesTemp: MASeriesItem[] = [];
+  for (let k = 0; k < dayArr.length; k++) {
+    let seriesItem: MASeriesItem = {
+      data: [],
+      name: nameArr[k],
+      color: colorArr[k]
+    };
+    for (let i = 0, len = kdata.length; i < len; i++) {
+      if (i < dayArr[k]) {
+        seriesItem.data.push(null);
+        continue;
+      }
+      let sum = 0;
+      for (let j = 0; j < dayArr[k]; j++) {
+        sum += kdata[i - j][1];
+      }
+      seriesItem.data.push(+(sum / dayArr[k]).toFixed(3));
+    }
+    seriesTemp.push(seriesItem);
+  }
+  return seriesTemp;
+}

+ 233 - 0
mini-ui-packages/mini-charts/src/lib/utils/misc.ts

@@ -0,0 +1,233 @@
+// 其他工具函数
+// 来源: u-charts.ts
+
+/**
+ * H5 事件接口
+ */
+export interface H5Event {
+  offsetX: number;
+  offsetY: number;
+  mp: {
+    changedTouches: Array<{ x: number; y: number }>;
+  };
+}
+
+/**
+ * 触摸点接口
+ */
+export interface TouchPoint {
+  x?: number;
+  y?: number;
+  clientX?: number;
+  clientY?: number;
+  pageX?: number;
+  pageY?: number;
+}
+
+/**
+ * 图表选项接口(用于 getTouches)
+ */
+export interface ChartOptions {
+  rotate?: boolean;
+  height: number;
+  pix: number;
+  width?: number;
+}
+
+/**
+ * DOM 事件接口
+ */
+export interface DOMEvent {
+  currentTarget?: {
+    offsetTop?: number;
+  };
+}
+
+/**
+ * 兼容 H5 点击事件
+ * 将 H5 的 offsetX/offsetY 转换为小程序格式的 changedTouches
+ * @param e - H5 事件对象
+ * @returns 转换后的事件对象
+ */
+export function getH5Offset(e: { offsetX: number; offsetY: number }): H5Event {
+  (e as H5Event).mp = {
+    changedTouches: []
+  };
+  (e as H5Event).mp.changedTouches.push({
+    x: e.offsetX,
+    y: e.offsetY
+  });
+  return e as H5Event;
+}
+
+/**
+ * 曲线控制点接口
+ */
+export interface CurveControlPoints {
+  ctrA: {
+    x: number | null;
+    y: number | null;
+  };
+  ctrB: {
+    x: number | null;
+    y: number | null;
+  };
+}
+
+/**
+ * 点数组接口(用于 createCurveControlPoints)
+ */
+export interface PointArray extends Array<{ x: number; y: number }> {}
+
+/**
+ * 创建曲线控制点
+ * 用于绘制平滑曲线(贝塞尔曲线)
+ * @param points - 点数组
+ * @param i - 当前点索引
+ * @returns 曲线控制点
+ */
+export function createCurveControlPoints(points: PointArray, i: number): CurveControlPoints {
+  function isNotMiddlePoint(pts: PointArray, idx: number): boolean {
+    if (pts[idx - 1] && pts[idx + 1]) {
+      return pts[idx].y >= Math.max(pts[idx - 1].y, pts[idx + 1].y) || pts[idx].y <= Math.min(pts[idx - 1].y,
+        pts[idx + 1].y);
+    } else {
+      return false;
+    }
+  }
+  function isNotMiddlePointX(pts: PointArray, idx: number): boolean {
+    if (pts[idx - 1] && pts[idx + 1]) {
+      return pts[idx].x >= Math.max(pts[idx - 1].x, pts[idx + 1].x) || pts[idx].x <= Math.min(pts[idx - 1].x,
+        pts[idx + 1].x);
+    } else {
+      return false;
+    }
+  }
+  let a = 0.2;
+  let b = 0.2;
+  let pAx: number | null = null;
+  let pAy: number | null = null;
+  let pBx: number | null = null;
+  let pBy: number | null = null;
+  if (i < 1) {
+    pAx = points[0].x + (points[1].x - points[0].x) * a;
+    pAy = points[0].y + (points[1].y - points[0].y) * a;
+  } else {
+    pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a;
+    pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a;
+  }
+
+  if (i > points.length - 3) {
+    let last = points.length - 1;
+    pBx = points[last].x - (points[last].x - points[last - 1].x) * b;
+    pBy = points[last].y - (points[last].y - points[last - 1].y) * b;
+  } else {
+    pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b;
+    pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b;
+  }
+  if (isNotMiddlePoint(points, i + 1)) {
+    pBy = points[i + 1].y;
+  }
+  if (isNotMiddlePoint(points, i)) {
+    pAy = points[i].y;
+  }
+  if (isNotMiddlePointX(points, i + 1)) {
+    pBx = points[i + 1].x;
+  }
+  if (isNotMiddlePointX(points, i)) {
+    pAx = points[i].x;
+  }
+  if (pAy >= Math.max(points[i].y, points[i + 1].y) || pAy <= Math.min(points[i].y, points[i + 1].y)) {
+    pAy = points[i].y;
+  }
+  if (pBy >= Math.max(points[i].y, points[i + 1].y) || pBy <= Math.min(points[i].y, points[i + 1].y)) {
+    pBy = points[i + 1].y;
+  }
+  if (pAx >= Math.max(points[i].x, points[i + 1].x) || pAx <= Math.min(points[i].x, points[i + 1].x)) {
+    pAx = points[i].x;
+  }
+  if (pBx >= Math.max(points[i].x, points[i + 1].x) || pBx <= Math.min(points[i].x, points[i + 1].x)) {
+    pBx = points[i + 1].x;
+  }
+  return {
+    ctrA: {
+      x: pAx,
+      y: pAy
+    },
+    ctrB: {
+      x: pBx,
+      y: pBy
+    }
+  };
+}
+
+/**
+ * 分割数据点(处理 null 值)
+ * 用于折线图等图表的断点处理
+ * @param points - 数据点数组
+ * @param eachSeries - 系列配置(包含 connectNulls 属性)
+ * @returns 分割后的数据点数组
+ */
+export function splitPoints<T>(points: (T | null)[], eachSeries: { connectNulls?: boolean }): T[][] {
+  const newPoints: T[][] = [];
+  const items: T[] = [];
+  points.forEach(function(item, index) {
+    if (eachSeries.connectNulls) {
+      if (item !== null) {
+        items.push(item);
+      }
+    } else {
+      if (item !== null) {
+        items.push(item);
+      } else {
+        if (items.length) {
+          newPoints.push(items);
+        }
+        items.length = 0;
+      }
+    }
+  });
+  if (items.length) {
+    newPoints.push(items);
+  }
+  return newPoints;
+}
+
+/**
+ * 获取触摸点坐标
+ * 支持小程序和 H5 两种格式
+ * @param touches - 触摸点数组或单个触摸点
+ * @param opts - 图表选项
+ * @param e - DOM 事件对象
+ * @returns 触摸点坐标
+ */
+export function getTouches(
+  touches: TouchPoint | TouchPoint[],
+  opts: ChartOptions,
+  e: DOMEvent
+): TouchPoint {
+  let x: number, y: number;
+  const touch = Array.isArray(touches) ? touches[0] : touches;
+
+  if (touch.clientX) {
+    if (opts.rotate) {
+      y = opts.height - touch.clientX! * opts.pix;
+      x = (touch.pageY! - (e.currentTarget?.offsetTop || 0) - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    } else {
+      x = touch.clientX! * opts.pix;
+      y = (touch.pageY! - (e.currentTarget?.offsetTop || 0) - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;
+    }
+  } else {
+    if (opts.rotate) {
+      y = opts.height - touch.x! * opts.pix;
+      x = touch.y! * opts.pix;
+    } else {
+      x = touch.x! * opts.pix;
+      y = touch.y! * opts.pix;
+    }
+  }
+  return {
+    x: x,
+    y: y
+  };
+}

+ 59 - 0
mini-ui-packages/mini-charts/src/lib/utils/text.ts

@@ -0,0 +1,59 @@
+// 文本测量工具函数
+// 来源: u-charts.ts
+
+/**
+ * 文本度量结果接口
+ */
+export interface TextMeasure {
+  width: number;
+}
+
+/**
+ * 测量文本宽度
+ * @param text - 待测量的文本
+ * @param fontSize - 字体大小
+ * @param context - Canvas 渲染上下文(可选,如果不提供则使用估算方式)
+ * @returns 文本宽度
+ */
+export function measureText(
+  text: string | number,
+  fontSize: number,
+  context?: any
+): number {
+  let width = 0;
+  text = String(text);
+  // #ifdef MP-ALIPAY || MP-BAIDU || APP-NVUE
+  context = false;
+  // #endif
+  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {
+    context.setFontSize(fontSize);
+    return context.measureText(text).width;
+  } else {
+    let textChars = text.split('');
+    for (let i = 0; i < textChars.length; i++) {
+      let item = textChars[i];
+      if (/[a-zA-Z]/.test(item)) {
+        width += 7;
+      } else if (/[0-9]/.test(item)) {
+        width += 5.5;
+      } else if (/\./.test(item)) {
+        width += 2.7;
+      } else if (/-/.test(item)) {
+        width += 3.25;
+      } else if (/:/.test(item)) {
+        width += 2.5;
+      } else if (/[\u4e00-\u9fa5]/.test(item)) {
+        width += 10;
+      } else if (/\(|\)/.test(item)) {
+        width += 3.73;
+      } else if (/\s/.test(item)) {
+        width += 2.5;
+      } else if (/%/.test(item)) {
+        width += 8;
+      } else {
+        width += 10;
+      }
+    }
+    return width * fontSize / 10;
+  }
+}

+ 18 - 0
mini-ui-packages/mini-charts/src/types.ts

@@ -0,0 +1,18 @@
+/**
+ * mini-charts 包的共享类型定义
+ */
+
+import type { CanvasContext as TaroCanvasContext } from '@tarojs/taro';
+
+/**
+ * 扩展 Taro CanvasContext,添加 uCharts 需要的属性
+ * 用于统一 BaseChart 组件和 uCharts 库的 Canvas 上下文类型
+ */
+export interface ExtendedCanvasContext extends Omit<TaroCanvasContext, 'lineCap'> {
+  width: number;
+  height: number;
+  // uCharts 需要的额外属性(Taro 只有对应的 set 方法)
+  textAlign?: CanvasTextAlign | string; // 使用 string 类型以兼容 uCharts 的用法
+  textBaseline?: CanvasTextBaseline | string; // 使用 string 类型以兼容 uCharts 的用法
+  lineCap?: CanvasLineCap | string; // 使用 string 类型以兼容 uCharts 的用法
+}

+ 71 - 0
mini-ui-packages/mini-charts/src/types/u-charts-original.d.ts

@@ -0,0 +1,71 @@
+/**
+ * u-charts-original.js 类型定义
+ *
+ * 从原始 u-charts.js 文件中提取的核心类型定义
+ * 用于 BaseChartOriginal2D 和 ColumnChartOriginal2D 组件
+ */
+
+/**
+ * 图表配置接口 - 与原始 u-charts.js 兼容
+ */
+export interface ChartsConfig {
+  /** 图表类型 */
+  type: 'pie' | 'ring' | 'line' | 'column' | 'bar' | 'area' | 'radar' | 'candle';
+  /** Canvas 上下文 */
+  context: any;
+  /** 图表宽度(像素) */
+  width: number;
+  /** 图表高度(像素) */
+  height: number;
+  /** 设备像素比 */
+  pixelRatio?: number;
+  /** X 轴分类数据 */
+  categories?: string[];
+  /** 系列数据 */
+  series?: any[];
+  /** 图表动画 */
+  animation?: boolean;
+  /** 背景颜色 */
+  background?: string;
+  /** 颜色数组 */
+  color?: string[];
+  /** 内边距 */
+  padding?: number[];
+  /** 是否启用滚动 */
+  enableScroll?: boolean;
+  /** 是否显示数据标签 */
+  dataLabel?: boolean;
+  /** 图例配置 */
+  legend?: any;
+  /** X 轴配置 */
+  xAxis?: any;
+  /** Y 轴配置 */
+  yAxis?: any;
+  /** 额外配置 */
+  extra?: any;
+  /** 数据更新回调 */
+  update?: boolean;
+}
+
+/**
+ * 触摸事件接口
+ */
+export interface TouchEvent {
+  touchEvent: {
+    x: number;
+    y: number;
+  };
+  [key: string]: any;
+}
+
+declare module '../lib/u-charts-original.js' {
+  import { ChartsConfig } from '../types/u-charts-original';
+
+  const uCharts: new (config: ChartsConfig) => {
+    touchLegend(e: any): void;
+    showToolTip(e: any): void;
+    scroll(e: any): void;
+  };
+
+  export default uCharts;
+}

+ 1 - 0
mini-ui-packages/mini-charts/tests/__mocks__/fileMock.js

@@ -0,0 +1 @@
+module.exports = 'test-file-stub';

+ 1 - 0
mini-ui-packages/mini-charts/tests/__mocks__/styleMock.js

@@ -0,0 +1 @@
+module.exports = {};

+ 106 - 0
mini-ui-packages/mini-charts/tests/components/BaseChart.test.tsx

@@ -0,0 +1,106 @@
+/**
+ * BaseChart 组件基础测试
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { BaseChart } from '../../src/components/BaseChart';
+
+declare const describe: any;
+declare const it: any;
+declare const expect: any;
+declare const beforeEach: any;
+declare const afterEach: any;
+declare const jest: any;
+
+// Mock @tarojs/components
+jest.mock('@tarojs/components', () => ({
+  Canvas: ({ children, ...props }: any) => React.createElement('canvas', props, children)
+}));
+
+// Mock uCharts
+jest.mock('../../src/lib/charts/index.ts', () => ({
+  uCharts: jest.fn().mockImplementation(() => ({
+    showToolTip: jest.fn(),
+    scrollStart: jest.fn(),
+    scroll: jest.fn(),
+    scrollEnd: jest.fn()
+  }))
+}));
+
+describe('BaseChart 组件', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
+  it('应该渲染 Canvas 元素', () => {
+    const { container } = render(
+      <BaseChart
+        canvasId="test-chart"
+        type="column"
+        categories={['A', 'B', 'C']}
+        series={[]}
+      />
+    );
+
+    const canvas = container.querySelector('canvas');
+    expect(canvas).toBeInTheDocument();
+  });
+
+  it('应该设置正确的 canvas-id 和 id 属性', () => {
+    const { container } = render(
+      <BaseChart
+        canvasId="test-chart"
+        type="column"
+        categories={[]}
+        series={[]}
+      />
+    );
+
+    const canvas = container.querySelector('canvas') as HTMLCanvasElement;
+    expect(canvas).toHaveAttribute('canvas-id', 'test-chart');
+    expect(canvas).toHaveAttribute('id', 'test-chart');
+  });
+
+  it('应该使用指定的宽高', () => {
+    const { container } = render(
+      <BaseChart
+        canvasId="test-chart"
+        type="column"
+        width={600}
+        height={400}
+        categories={[]}
+        series={[]}
+      />
+    );
+
+    const canvas = container.querySelector('canvas') as HTMLCanvasElement;
+    expect(canvas?.style.width).toBe('600px');
+    expect(canvas?.style.height).toBe('400px');
+  });
+
+  it('应该支持触摸事件处理', () => {
+    const onTouchStart = jest.fn();
+    const onTouchMove = jest.fn();
+    const onTouchEnd = jest.fn();
+
+    const { container } = render(
+      <BaseChart
+        canvasId="test-chart"
+        type="column"
+        categories={[]}
+        series={[]}
+        onTouchStart={onTouchStart}
+        onTouchMove={onTouchMove}
+        onTouchEnd={onTouchEnd}
+      />
+    );
+
+    const canvas = container.querySelector('canvas');
+    expect(canvas).toBeInTheDocument();
+  });
+});

+ 163 - 0
mini-ui-packages/mini-charts/tests/components/ColumnChart.test.tsx

@@ -0,0 +1,163 @@
+/**
+ * ColumnChart 组件基础测试
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { ColumnChart } from '../../src/components/ColumnChart';
+
+declare const jest: any;
+declare const describe: any;
+declare const it: any;
+declare const expect: any;
+declare const beforeEach: any;
+declare const afterEach: any;
+
+// Mock @tarojs/components
+jest.mock('@tarojs/components', () => ({
+  Canvas: ({ children, ...props }: any) => React.createElement('canvas', props, children)
+}));
+
+// Mock uCharts
+jest.mock('../../src/lib/charts/index.ts', () => ({
+  uCharts: jest.fn().mockImplementation(() => ({
+    showToolTip: jest.fn(),
+    scrollStart: jest.fn(),
+    scroll: jest.fn(),
+    scrollEnd: jest.fn()
+  }))
+}));
+
+describe('ColumnChart 组件', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
+  it('应该渲染 Canvas 元素', () => {
+    const { container } = render(
+      <ColumnChart
+        canvasId="test-column"
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+      />
+    );
+
+    const canvas = container.querySelector('canvas');
+    expect(canvas).toBeInTheDocument();
+  });
+
+  it('应该使用柱状图类型', () => {
+    const { uCharts } = require('../../src/lib/charts/index.ts');
+
+    render(
+      <ColumnChart
+        canvasId="test-column"
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+      />
+    );
+
+    expect(uCharts).toHaveBeenCalledWith(
+      expect.objectContaining({
+        type: 'column'
+      })
+    );
+  });
+
+  it('应该支持分组模式', () => {
+    const { uCharts } = require('../../src/lib/charts/index.ts');
+
+    render(
+      <ColumnChart
+        canvasId="test-column"
+        columnType="group"
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+      />
+    );
+
+    expect(uCharts).toHaveBeenCalledWith(
+      expect.objectContaining({
+        extra: expect.objectContaining({
+          column: expect.objectContaining({
+            type: 'group'
+          })
+        })
+      })
+    );
+  });
+
+  it('应该支持堆叠模式', () => {
+    const { uCharts } = require('../../src/lib/charts/index.ts');
+
+    render(
+      <ColumnChart
+        canvasId="test-column"
+        columnType="stack"
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+      />
+    );
+
+    expect(uCharts).toHaveBeenCalledWith(
+      expect.objectContaining({
+        extra: expect.objectContaining({
+          column: expect.objectContaining({
+            type: 'stack'
+          })
+        })
+      })
+    );
+  });
+
+  it('应该支持自定义 tooltip 格式化', () => {
+    const tooltipFormatter = jest.fn((item: any, category: any) => `${category}: ${item.data}`);
+
+    const { container } = render(
+      <ColumnChart
+        canvasId="test-column"
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+        tooltipFormatter={tooltipFormatter}
+      />
+    );
+
+    const canvas = container.querySelector('canvas');
+    expect(canvas).toBeInTheDocument();
+  });
+
+  it('应该支持数据标签显示/隐藏', () => {
+    const { uCharts } = require('../../src/lib/charts/index.ts');
+
+    render(
+      <ColumnChart
+        canvasId="test-column"
+        dataLabel={false}
+        categories={['A', 'B', 'C']}
+        series={[
+          { name: 'Series 1', data: [10, 20, 30] }
+        ]}
+      />
+    );
+
+    expect(uCharts).toHaveBeenCalledWith(
+      expect.objectContaining({
+        dataLabel: false
+      })
+    );
+  });
+});

+ 2 - 0
mini-ui-packages/mini-charts/tests/setup.ts

@@ -0,0 +1,2 @@
+// Jest setup file for mini-charts package
+import '@testing-library/jest-dom';

+ 29 - 0
mini-ui-packages/mini-charts/tsconfig.json

@@ -0,0 +1,29 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM"],
+    "moduleResolution": "bundler",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "declaration": true,
+    "declarationMap": true,
+    "sourceMap": true,
+    "outDir": "./dist",
+    "rootDir": ".",
+    "jsx": "react-jsx",
+    "resolveJsonModule": true,
+    "allowSyntheticDefaultImports": true,
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+    "types": ["jest", "react", "node"],
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    }
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist", "tests"]
+}

+ 40 - 0
mini-ui-packages/mini-shared-ui-components/jest.config.cjs

@@ -0,0 +1,40 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'jsdom',
+  setupFilesAfterEnv: ['@d8d/mini-testing-utils/setup'],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^~/(.*)$': '<rootDir>/tests/$1',
+    '^@tarojs/taro$': '@d8d/mini-testing-utils/testing/taro-api-mock.ts',
+    '\\.(css|less|scss|sass)$': '@d8d/mini-testing-utils/testing/style-mock.js',
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+      '@d8d/mini-testing-utils/testing/file-mock.js'
+  },
+  testMatch: [
+    '<rootDir>/tests/**/*.spec.{ts,tsx}',
+    '<rootDir>/tests/**/*.test.{ts,tsx}'
+  ],
+  collectCoverageFrom: [
+    'src/**/*.{ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/**/index.{ts,tsx}',
+    '!src/**/*.stories.{ts,tsx}'
+  ],
+  coverageDirectory: 'coverage',
+  coverageReporters: ['text', 'lcov', 'html'],
+  testPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/coverage/'
+  ],
+  transform: {
+    '^.+\\.(ts|tsx)$': ['ts-jest', {
+      tsconfig: 'tests/__config__/tsconfig.test.json'
+    }],
+    '^.+\\.(js|jsx)$': 'babel-jest'
+  },
+  transformIgnorePatterns: [
+    '/node_modules/(?!(swiper|@tarojs)/)'
+  ],
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
+}

+ 139 - 0
mini-ui-packages/mini-shared-ui-components/package.json

@@ -0,0 +1,139 @@
+{
+  "name": "@d8d/mini-shared-ui-components",
+  "version": "1.0.0",
+  "type": "module",
+  "description": "Taro共享UI组件包",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./dist/src/index.d.ts",
+      "import": "./dist/src/index.js",
+      "require": "./dist/src/index.js"
+    },
+    "./components/button": {
+      "types": "./dist/src/components/button.d.ts",
+      "import": "./dist/src/components/button.js",
+      "require": "./dist/src/components/button.js"
+    },
+    "./components/avatar-upload": {
+      "types": "./dist/src/components/avatar-upload.d.ts",
+      "import": "./dist/src/components/avatar-upload.js",
+      "require": "./dist/src/components/avatar-upload.js"
+    },
+    "./components/card": {
+      "types": "./dist/src/components/card.d.ts",
+      "import": "./dist/src/components/card.js",
+      "require": "./dist/src/components/card.js"
+    },
+    "./components/dialog": {
+      "types": "./dist/src/components/dialog.d.ts",
+      "import": "./dist/src/components/dialog.js",
+      "require": "./dist/src/components/dialog.js"
+    },
+    "./components/form": {
+      "types": "./dist/src/components/form.d.ts",
+      "import": "./dist/src/components/form.js",
+      "require": "./dist/src/components/form.js"
+    },
+    "./components/image": {
+      "types": "./dist/src/components/image.d.ts",
+      "import": "./dist/src/components/image.js",
+      "require": "./dist/src/components/image.js"
+    },
+    "./components/input": {
+      "types": "./dist/src/components/input.d.ts",
+      "import": "./dist/src/components/input.js",
+      "require": "./dist/src/components/input.js"
+    },
+    "./components/label": {
+      "types": "./dist/src/components/label.d.ts",
+      "import": "./dist/src/components/label.js",
+      "require": "./dist/src/components/label.js"
+    },
+    "./components/navbar": {
+      "types": "./dist/src/components/navbar.d.ts",
+      "import": "./dist/src/components/navbar.js",
+      "require": "./dist/src/components/navbar.js"
+    },
+    "./components/page-container": {
+      "types": "./dist/src/components/page-container.d.ts",
+      "import": "./dist/src/components/page-container.js",
+      "require": "./dist/src/components/page-container.js"
+    },
+    "./components/tab-bar": {
+      "types": "./dist/src/components/tab-bar.d.ts",
+      "import": "./dist/src/components/tab-bar.js",
+      "require": "./dist/src/components/tab-bar.js"
+    },
+    "./components/user-status-bar": {
+      "types": "./dist/src/components/user-status-bar.d.ts",
+      "import": "./dist/src/components/user-status-bar.js",
+      "require": "./dist/src/components/user-status-bar.js"
+    },
+    "./utils/cn": {
+      "types": "./dist/src/utils/cn.d.ts",
+      "import": "./dist/src/utils/cn.js",
+      "require": "./dist/src/utils/cn.js"
+    },
+    "./utils/platform": {
+      "types": "./dist/src/utils/platform.d.ts",
+      "import": "./dist/src/utils/platform.js",
+      "require": "./dist/src/utils/platform.js"
+    },
+    "./utils/rpc/rpc-client": {
+      "types": "./dist/src/utils/rpc/rpc-client.d.ts",
+      "import": "./dist/src/utils/rpc/rpc-client.js",
+      "require": "./dist/src/utils/rpc/rpc-client.js"
+    },
+    "./testing": {
+      "types": "./dist/testing/index.d.ts",
+      "import": "./dist/testing/index.js",
+      "require": "./dist/testing/index.js"
+    }
+  },
+  "scripts": {
+    "clean": "rimraf dist",
+    "build": "tsc",
+    "prebuild": "pnpm run clean",
+    "dev": "tsc --watch",
+    "typecheck": "tsc --noEmit",
+    "test": "jest",
+    "test:watch": "jest --watch",
+    "test:coverage": "jest --coverage",
+    "test:components": "jest tests/components"
+  },
+  "dependencies": {
+    "@radix-ui/react-slot": "^1.2.3",
+    "@tarojs/components": "4.1.4",
+    "@tarojs/plugin-platform-weapp": "4.1.4",
+    "@tarojs/react": "4.1.4",
+    "@tarojs/taro": "4.1.4",
+    "@weapp-tailwindcss/merge": "^1.2.3",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "hono": "4.8.5",
+    "react": "^18.0.0",
+    "react-dom": "^18.0.0",
+    "react-hook-form": "^7.62.0"
+  },
+  "devDependencies": {
+    "@d8d/mini-testing-utils": "workspace:*",
+    "@testing-library/jest-dom": "^6.8.0",
+    "@testing-library/react": "^16.3.0",
+    "@testing-library/user-event": "^14.6.1",
+    "@types/jest": "^29.5.14",
+    "@types/node": "^18",
+    "@types/react": "^18.0.0",
+    "@types/react-dom": "^18.0.0",
+    "jest": "^30.2.0",
+    "jest-environment-jsdom": "^29.7.0",
+    "rimraf": "^6.1.2",
+    "ts-jest": "^29.4.5",
+    "typescript": "^5.4.5"
+  },
+  "files": [
+    "src",
+    "testing"
+  ]
+}

+ 93 - 0
mini-ui-packages/mini-shared-ui-components/src/components/avatar-upload.tsx

@@ -0,0 +1,93 @@
+import { useState } from 'react'
+import { View, Image } from '@tarojs/components'
+import Taro from '@tarojs/taro'
+import { cn } from '../utils/cn'
+
+interface AvatarUploadProps {
+  currentAvatar?: string
+  onUploadSuccess?: (result: any) => void
+  onUploadError?: (error: Error) => void
+  size?: number
+  editable?: boolean
+}
+
+export function AvatarUpload({
+  currentAvatar,
+  onUploadSuccess,
+  onUploadError,
+  size = 96,
+  editable = true
+}: AvatarUploadProps) {
+  const [uploading, setUploading] = useState(false)
+  const [progress, setProgress] = useState(0)
+
+  const handleChooseImage = async () => {
+    if (!editable || uploading) return
+
+    // 简化版本:只显示提示,不实际处理上传
+    Taro.showToast({
+      title: '头像上传功能需要在项目中实现',
+      icon: 'none'
+    })
+
+    // 调用错误回调,提示需要实现
+    onUploadError?.(new Error('头像上传功能需要在项目中实现,请参考原mini项目中的minio工具'))
+  }
+
+  const avatarSize = size
+
+  return (
+    <View
+      className="relative inline-block"
+      onClick={handleChooseImage}
+    >
+      <View
+        className={cn(
+          "relative overflow-hidden rounded-full",
+          "border-4 border-white shadow-lg",
+          editable && "cursor-pointer active:scale-95 transition-transform duration-150",
+          uploading && "opacity-75"
+        )}
+        style={{ width: avatarSize, height: avatarSize }}
+      >
+        <Image
+          src={currentAvatar || 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=160&h=160&fit=crop&crop=face'}
+          mode="aspectFill"
+          className="w-full h-full"
+        />
+
+        {uploading && (
+          <View className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
+            <View className="text-white text-xs">{progress}%</View>
+          </View>
+        )}
+      </View>
+
+      {editable && !uploading && (
+        <View
+          className={cn(
+            "absolute -bottom-1 -right-1",
+            "w-8 h-8 bg-blue-500 rounded-full",
+            "flex items-center justify-center shadow-md",
+            "border-2 border-white"
+          )}
+        >
+          <View className="i-heroicons-camera-20-solid w-4 h-4 text-white" />
+        </View>
+      )}
+
+      {uploading && (
+        <View
+          className={cn(
+            "absolute -bottom-1 -right-1",
+            "w-8 h-8 bg-gray-500 rounded-full",
+            "flex items-center justify-center shadow-md",
+            "border-2 border-white"
+          )}
+        >
+          <View className="i-heroicons-arrow-path-20-solid w-4 h-4 text-white animate-spin" />
+        </View>
+      )}
+    </View>
+  )
+}

+ 46 - 0
mini-ui-packages/mini-shared-ui-components/src/components/button.tsx

@@ -0,0 +1,46 @@
+import { Button as TaroButton, ButtonProps as TaroButtonProps } from '@tarojs/components'
+import { cn } from '../utils/cn'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+const buttonVariants = cva(
+  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
+  {
+    variants: {
+      variant: {
+        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+        outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
+        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+        ghost: 'hover:bg-accent hover:text-accent-foreground',
+        link: 'underline-offset-4 hover:underline text-primary',
+      },
+      size: {
+        default: 'h-10 py-2 px-4',
+        sm: 'h-9 px-3 rounded-md text-xs',
+        lg: 'h-11 px-8 rounded-md',
+        icon: 'h-10 w-10',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+interface ButtonProps extends Omit<TaroButtonProps, 'size'>, VariantProps<typeof buttonVariants> {
+  className?: string
+  children?: React.ReactNode
+}
+
+export function Button({ className, variant, size, ...props }: ButtonProps) {
+  return (
+    <TaroButton
+      className={cn(buttonVariants({ variant, size, className }))}
+      {...props}
+    />
+  )
+}
+
+// 预定义的按钮样式导出
+export { buttonVariants }

+ 54 - 0
mini-ui-packages/mini-shared-ui-components/src/components/card.tsx

@@ -0,0 +1,54 @@
+import { View } from '@tarojs/components'
+import { cn } from '../utils/cn'
+
+interface CardProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function Card({ className, children }: CardProps) {
+  return (
+    <View className={cn("bg-white rounded-xl shadow-sm", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface CardHeaderProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function CardHeader({ className, children }: CardHeaderProps) {
+  return (
+    <View className={cn("p-4 border-b border-gray-100", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface CardContentProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function CardContent({ className, children }: CardContentProps) {
+  return (
+    <View className={cn("p-4", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface CardFooterProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function CardFooter({ className, children }: CardFooterProps) {
+  return (
+    <View className={cn("p-4 border-t border-gray-100", className)}>
+      {children}
+    </View>
+  )
+}

+ 95 - 0
mini-ui-packages/mini-shared-ui-components/src/components/dialog.tsx

@@ -0,0 +1,95 @@
+import { useEffect } from 'react'
+import { View, Text } from '@tarojs/components'
+import { cn } from '../utils/cn'
+
+interface DialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  children: React.ReactNode
+}
+
+export function Dialog({ open, onOpenChange, children }: DialogProps) {
+  useEffect(() => {
+    if (open) {
+      // 在 Taro 中,我们可以使用模态框或者自定义弹窗
+      // 这里使用自定义实现
+    }
+  }, [open])
+
+  const handleBackdropClick = () => {
+    onOpenChange(false)
+  }
+
+  const handleContentClick = (e: any) => {
+    // 阻止事件冒泡,避免点击内容区域时关闭弹窗
+    e.stopPropagation()
+  }
+
+  if (!open) return null
+
+  return (
+    <View
+      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
+      onClick={handleBackdropClick}
+    >
+      <View
+        className="relative bg-white rounded-lg shadow-lg max-w-md w-full mx-4"
+        onClick={handleContentClick}
+      >
+        {children}
+      </View>
+    </View>
+  )
+}
+
+interface DialogContentProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogContent({ className, children }: DialogContentProps) {
+  return (
+    <View className={cn("p-6", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface DialogHeaderProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogHeader({ className, children }: DialogHeaderProps) {
+  return (
+    <View className={cn("mb-4", className)}>
+      {children}
+    </View>
+  )
+}
+
+interface DialogTitleProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogTitle({ className, children }: DialogTitleProps) {
+  return (
+    <Text className={cn("text-lg font-semibold text-gray-900", className)}>
+      {children}
+    </Text>
+  )
+}
+
+interface DialogFooterProps {
+  className?: string
+  children: React.ReactNode
+}
+
+export function DialogFooter({ className, children }: DialogFooterProps) {
+  return (
+    <View className={cn("flex justify-end space-x-2", className)}>
+      {children}
+    </View>
+  )
+}

+ 168 - 0
mini-ui-packages/mini-shared-ui-components/src/components/form.tsx

@@ -0,0 +1,168 @@
+import * as React from "react"
+import { View, Text } from "@tarojs/components"
+import { Slot } from "@radix-ui/react-slot"
+import {
+  Controller,
+  FormProvider,
+  useFormContext,
+  useFormState,
+  type ControllerProps,
+  type FieldPath,
+  type FieldValues,
+} from "react-hook-form"
+
+import { cn } from '../utils/cn'
+import { Label } from './label'
+
+const Form: typeof FormProvider = FormProvider
+
+type FormFieldContextValue<
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+> = {
+  name: TName
+}
+
+const FormFieldContext = React.createContext<FormFieldContextValue>(
+  {} as FormFieldContextValue
+)
+
+const FormField = <
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+>({
+  ...props
+}: ControllerProps<TFieldValues, TName, TFieldValues>) => {
+  const ControllerWrapper = (props: any) => (
+    // @ts-ignore
+    <Controller {...props} />
+  )
+  return (
+    <FormFieldContext.Provider value={{ name: props.name }}>
+      <ControllerWrapper {...props} />
+    </FormFieldContext.Provider>
+  )
+}
+
+const useFormField = () => {
+  const fieldContext = React.useContext(FormFieldContext)
+  const itemContext = React.useContext(FormItemContext)
+  const { getFieldState } = useFormContext()
+  const formState = useFormState({ name: fieldContext.name })
+  const fieldState = getFieldState(fieldContext.name, formState)
+
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+
+  const { id } = itemContext
+
+  return {
+    id,
+    name: fieldContext.name,
+    formItemId: `${id}-form-item`,
+    formDescriptionId: `${id}-form-item-description`,
+    formMessageId: `${id}-form-item-message`,
+    ...fieldState,
+  }
+}
+
+type FormItemContextValue = {
+  id: string
+}
+
+const FormItemContext = React.createContext<FormItemContextValue>(
+  {} as FormItemContextValue
+)
+
+function FormItem({ className, ...props }: React.ComponentProps<typeof View>) {
+  const id = React.useId()
+
+  return (
+    <FormItemContext.Provider value={{ id }}>
+      <View
+        className={cn("grid gap-2", className)}
+        {...props}
+      />
+    </FormItemContext.Provider>
+  )
+}
+
+function FormLabel({
+  className,
+  ...props
+}: React.ComponentProps<typeof Label>) {
+  const { error, formItemId } = useFormField()
+
+  return (
+    <Label
+      data-slot="form-label"
+      data-error={!!error}
+      className={cn("data-[error=true]:text-destructive", className)}
+      htmlFor={formItemId}
+      {...props}
+    />
+  )
+}
+
+function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
+  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+  return (
+    <Slot
+      data-slot="form-control"
+      id={formItemId}
+      aria-describedby={
+        !error
+          ? `${formDescriptionId}`
+          : `${formDescriptionId} ${formMessageId}`
+      }
+      aria-invalid={!!error}
+      {...props}
+    />
+  )
+}
+
+function FormDescription({ className, ...props }: React.ComponentProps<typeof Text>) {
+  const { formDescriptionId } = useFormField()
+
+  return (
+    <Text
+      data-slot="form-description"
+      id={formDescriptionId}
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  )
+}
+
+function FormMessage({ className, ...props }: React.ComponentProps<typeof Text>) {
+  const { error, formMessageId } = useFormField()
+  const body = error ? String(error?.message ?? "") : props.children
+
+  if (!body) {
+    return null
+  }
+
+  return (
+    <Text
+      data-slot="form-message"
+      id={formMessageId}
+      className={cn("text-destructive text-sm", className)}
+      {...props}
+    >
+      {body}
+    </Text>
+  )
+}
+
+export {
+  useFormField,
+  Form,
+  FormItem,
+  FormLabel,
+  FormControl,
+  FormDescription,
+  FormMessage,
+  FormField,
+}

+ 127 - 0
mini-ui-packages/mini-shared-ui-components/src/components/image.tsx

@@ -0,0 +1,127 @@
+import { View, Image as TaroImage, ImageProps as TaroImageProps } from '@tarojs/components'
+import { cn } from '../utils/cn'
+import { useState } from 'react'
+
+export interface ImageProps extends Omit<TaroImageProps, 'onError'> {
+  /**
+   * 图片地址
+   */
+  src: string
+  /**
+   * 替代文本
+   */
+  alt?: string
+  /**
+   * 图片模式
+   * @default "aspectFill"
+   */
+  mode?: TaroImageProps['mode']
+  /**
+   * 是否懒加载
+   * @default true
+   */
+  lazyLoad?: boolean
+  /**
+   * 是否显示加载占位
+   * @default true
+   */
+  showLoading?: boolean
+  /**
+   * 是否显示错误占位
+   * @default true
+   */
+  showError?: boolean
+  /**
+   * 圆角大小
+   */
+  rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'
+  /**
+   * 自定义样式类
+   */
+  className?: string
+  /**
+   * 图片加载失败时的回调
+   */
+  onError?: () => void
+  /**
+   * 图片加载成功的回调
+   */
+  onLoad?: () => void
+}
+
+const roundedMap = {
+  none: '',
+  sm: 'rounded-sm',
+  md: 'rounded-md',
+  lg: 'rounded-lg',
+  xl: 'rounded-xl',
+  full: 'rounded-full'
+}
+
+export function Image({
+  src,
+  alt = '图片',
+  mode = 'aspectFill',
+  lazyLoad = true,
+  showLoading = true,
+  showError = true,
+  rounded = 'none',
+  className,
+  onError,
+  onLoad,
+  ...props
+}: ImageProps) {
+  const [loading, setLoading] = useState(true)
+  const [error, setError] = useState(false)
+
+  const handleLoad = () => {
+    setLoading(false)
+    setError(false)
+    onLoad?.()
+  }
+
+  const handleError = () => {
+    setLoading(false)
+    setError(true)
+    onError?.()
+  }
+
+  const renderPlaceholder = () => {
+    if (loading && showLoading) {
+      return (
+        <View className="absolute inset-0 flex items-center justify-center bg-gray-100">
+          <View className="i-heroicons-photo-20-solid w-8 h-8 text-gray-400 animate-pulse" />
+        </View>
+      )
+    }
+
+    if (error && showError) {
+      return (
+        <View className="absolute inset-0 flex items-center justify-center bg-gray-100">
+          <View className="i-heroicons-exclamation-triangle-20-solid w-8 h-8 text-gray-400" />
+        </View>
+      )
+    }
+
+    return null
+  }
+
+  return (
+    <View className={cn('relative overflow-hidden', roundedMap[rounded], className)}>
+      <TaroImage
+        src={src}
+        mode={mode}
+        lazyLoad={lazyLoad}
+        onLoad={handleLoad}
+        onError={handleError}
+        className={cn(
+          'w-full h-full',
+          loading && 'opacity-0',
+          !loading && !error && 'opacity-100 transition-opacity duration-300'
+        )}
+        {...props}
+      />
+      {renderPlaceholder()}
+    </View>
+  )
+}

+ 102 - 0
mini-ui-packages/mini-shared-ui-components/src/components/input.tsx

@@ -0,0 +1,102 @@
+import { Input as TaroInput, InputProps as TaroInputProps, View, Text } from '@tarojs/components'
+import { cn } from '../utils/cn'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { forwardRef } from 'react'
+
+const inputVariants = cva(
+  'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
+  {
+    variants: {
+      variant: {
+        default: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
+        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
+        filled: 'border-none bg-gray-50 hover:bg-gray-100',
+      },
+      size: {
+        default: 'h-10 px-3 py-2',
+        sm: 'h-9 px-2 text-sm',
+        lg: 'h-11 px-4 text-lg',
+        icon: 'h-10 w-10',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+export interface InputProps extends Omit<TaroInputProps, 'className' | 'onChange'>, VariantProps<typeof inputVariants> {
+  className?: string
+  leftIcon?: string
+  rightIcon?: string
+  error?: boolean
+  errorMessage?: string
+  onLeftIconClick?: () => void
+  onRightIconClick?: () => void
+  onChange?: (value: string, event: any) => void
+}
+
+const Input = forwardRef<any, InputProps>(
+  ({ className, variant, size, leftIcon, rightIcon, error, errorMessage, onLeftIconClick, onRightIconClick, onChange, ...props }, ref) => {
+    const handleInput = (event: any) => {
+      const value = event.detail.value
+      onChange?.(value, event)
+
+      // 同时调用原始的onInput(如果提供了)
+      if (props.onInput) {
+        props.onInput(event)
+      }
+    }
+
+    return (
+      <View className="w-full">
+        <View className="relative">
+          {leftIcon && (
+            <View
+              className={cn(
+                "absolute left-3 top-1/2 -translate-y-1/2",
+                onLeftIconClick ? "cursor-pointer" : "pointer-events-none"
+              )}
+              onClick={onLeftIconClick}
+            >
+              <View className={cn('w-5 h-5 text-gray-400', leftIcon)} />
+            </View>
+          )}
+
+          <TaroInput
+            ref={ref}
+            className={cn(
+              inputVariants({ variant, size, className }),
+              error && 'border-red-500 focus:border-red-500 focus:ring-red-500',
+              leftIcon && 'pl-10',
+              rightIcon && 'pr-10',
+            )}
+            onInput={handleInput}
+            {...props}
+          />
+
+          {rightIcon && (
+            <View
+              className={cn(
+                "absolute right-3 top-1/2 -translate-y-1/2",
+                onRightIconClick ? "cursor-pointer" : "pointer-events-none"
+              )}
+              onClick={onRightIconClick}
+            >
+              <View className={cn('w-5 h-5 text-gray-400', rightIcon)} />
+            </View>
+          )}
+        </View>
+
+        {error && errorMessage && (
+          <Text className="mt-1 text-sm text-red-600">{errorMessage}</Text>
+        )}
+      </View>
+    )
+  }
+)
+
+Input.displayName = 'Input'
+
+export { Input, inputVariants }

+ 55 - 0
mini-ui-packages/mini-shared-ui-components/src/components/label.tsx

@@ -0,0 +1,55 @@
+import { View, Text } from '@tarojs/components'
+import { cn } from '../utils/cn'
+import { cva, type VariantProps } from 'class-variance-authority'
+import { forwardRef } from 'react'
+
+const labelVariants = cva(
+  'text-sm font-medium',
+  {
+    variants: {
+      variant: {
+        default: 'text-gray-900',
+        secondary: 'text-gray-600',
+        destructive: 'text-red-600',
+      },
+      size: {
+        default: 'text-sm',
+        sm: 'text-xs',
+        lg: 'text-base',
+      },
+    },
+    defaultVariants: {
+      variant: 'default',
+      size: 'default',
+    },
+  }
+)
+
+export interface LabelProps {
+  className?: string
+  variant?: VariantProps<typeof labelVariants>['variant']
+  size?: VariantProps<typeof labelVariants>['size']
+  children: React.ReactNode
+  required?: boolean
+  htmlFor?: string
+}
+
+const Label = forwardRef<HTMLLabelElement, LabelProps>(
+  ({ className, variant, size, children, required, htmlFor, ...props }, _ref) => {
+    return (
+      <View className="mb-2">
+        <Text
+          className={cn(labelVariants({ variant, size, className }))}
+          {...props}
+        >
+          {children}
+          {required && <Text className="text-red-500 ml-1">*</Text>}
+        </Text>
+      </View>
+    )
+  }
+)
+
+Label.displayName = 'Label'
+
+export { Label, labelVariants }

+ 230 - 0
mini-ui-packages/mini-shared-ui-components/src/components/navbar.tsx

@@ -0,0 +1,230 @@
+import React from 'react'
+import { View, Text } from '@tarojs/components'
+import { cn } from '../utils/cn'
+import Taro from '@tarojs/taro'
+import { isWeapp } from '../utils/platform'
+
+export interface NavbarProps {
+  title?: string
+  leftText?: string
+  leftIcon?: string
+  rightText?: string
+  rightIcon?: string
+  backgroundColor?: string
+  textColor?: string
+  border?: boolean
+  fixed?: boolean
+  placeholder?: boolean
+  onClickLeft?: () => void
+  onClickRight?: () => void
+  children?: React.ReactNode
+  className?: string
+  /** 是否在小程序环境下隐藏右侧按钮(默认false,会自动避让) */
+  hideRightInWeapp?: boolean
+}
+
+const systemInfo = Taro.getSystemInfoSync()
+const menuButtonInfo = isWeapp() ? Taro.getMenuButtonBoundingClientRect() : undefined
+
+// 计算导航栏高度
+const NAVBAR_HEIGHT = 44
+const STATUS_BAR_HEIGHT = systemInfo.statusBarHeight || 0
+const TOTAL_HEIGHT = STATUS_BAR_HEIGHT + NAVBAR_HEIGHT
+
+export const Navbar: React.FC<NavbarProps> = ({
+  title,
+  leftText,
+  leftIcon = 'i-heroicons-chevron-left-20-solid',
+  rightText,
+  rightIcon,
+  backgroundColor = 'bg-white',
+  textColor = 'text-gray-900',
+  border = true,
+  fixed = true,
+  placeholder = true,
+  onClickLeft,
+  onClickRight,
+  children,
+  className,
+  hideRightInWeapp,
+}) => {
+  // 处理左侧点击
+  const handleLeftClick = () => {
+    if (onClickLeft) {
+      onClickLeft()
+    } else {
+      // 默认返回上一页
+      Taro.navigateBack()
+    }
+  }
+
+  // 渲染左侧内容
+  const renderLeft = () => {
+    if (children) return null
+
+    return (
+      <View
+        className="absolute left-3 top-0 bottom-0 flex items-center z-10"
+        style={{ height: NAVBAR_HEIGHT }}
+        onClick={handleLeftClick}
+      >
+        <View className="flex items-center">
+          {leftIcon && (
+            <View className={cn(leftIcon, 'w-5 h-5', textColor)} />
+          )}
+          {leftText && (
+            <Text className={cn('ml-1 text-sm', textColor)}>{leftText}</Text>
+          )}
+        </View>
+      </View>
+    )
+  }
+
+  // 渲染右侧内容
+  const renderRight = () => {
+    if (!rightText && !rightIcon || (hideRightInWeapp && isWeapp())) return null
+
+    if (isWeapp() && menuButtonInfo) {
+      // 小程序环境下,调整右侧按钮位置
+      return (
+        <View
+          className="absolute top-0 bottom-0 flex items-center z-10"
+          style={{
+            height: NAVBAR_HEIGHT,
+            right: `${systemInfo.screenWidth - menuButtonInfo.left + 10}px`,
+          }}
+          onClick={onClickRight}
+        >
+          <View className="flex items-center">
+            {rightText && (
+              <Text className={cn('mr-1 text-sm', textColor)}>{rightText}</Text>
+            )}
+            {rightIcon && (
+              <View className={cn(rightIcon, 'w-5 h-5', textColor)} />
+            )}
+          </View>
+        </View>
+      )
+    }
+
+    // H5或其他平台,保持原有样式
+    return (
+      <View
+        className="absolute right-3 top-0 bottom-0 flex items-center z-10"
+        style={{ height: NAVBAR_HEIGHT }}
+        onClick={onClickRight}
+      >
+        <View className="flex items-center">
+          {rightText && (
+            <Text className={cn('mr-1 text-sm', textColor)}>{rightText}</Text>
+          )}
+          {rightIcon && (
+            <View className={cn(rightIcon, 'w-5 h-5', textColor)} />
+          )}
+        </View>
+      </View>
+    )
+  }
+
+  // 渲染标题
+  const renderTitle = () => {
+    if (children) return children
+
+    if (isWeapp() && menuButtonInfo) {
+      // 小程序环境下,调整标题位置
+      return (
+        <View className="flex-1 flex items-center justify-center">
+          <Text
+            className={cn('text-base font-semibold truncate', textColor)}
+            style={{
+              maxWidth: `calc(100% - ${systemInfo.screenWidth - menuButtonInfo.right + 10}px - 60px - 60px)`
+            }}
+          >
+            {title}
+          </Text>
+        </View>
+      )
+    }
+
+    // H5或其他平台,保持原有样式
+    return (
+      <Text className={cn('text-base font-semibold', textColor)}>
+        {title}
+      </Text>
+    )
+  }
+
+  // 导航栏样式
+  const navbarStyle = {
+    height: TOTAL_HEIGHT,
+    paddingTop: STATUS_BAR_HEIGHT,
+  }
+
+  return (
+    <>
+      <View
+        className={cn(
+          'relative w-full',
+          backgroundColor,
+          border && 'border-b border-gray-200',
+          fixed && 'fixed top-0 left-0 right-0 z-50',
+          className
+        )}
+        style={navbarStyle}
+      >
+        {/* 导航栏内容 */}
+        <View
+          className="relative flex items-center justify-center"
+          style={{ height: NAVBAR_HEIGHT }}
+        >
+          {renderLeft()}
+          {renderTitle()}
+          {renderRight()}
+        </View>
+      </View>
+
+      {/* 占位元素 */}
+      {fixed && placeholder && (
+        <View style={{ height: TOTAL_HEIGHT }} />
+      )}
+    </>
+  )
+}
+
+// 预设样式
+export const NavbarPresets = {
+  // 默认白色导航栏
+  default: {
+    backgroundColor: 'bg-white',
+    textColor: 'text-gray-900',
+    border: true,
+  },
+
+  // 深色导航栏
+  dark: {
+    backgroundColor: 'bg-gray-900',
+    textColor: 'text-white',
+    border: true,
+  },
+
+  // 透明导航栏
+  transparent: {
+    backgroundColor: 'bg-transparent',
+    textColor: 'text-white',
+    border: false,
+  },
+
+  // 主色调导航栏
+  primary: {
+    backgroundColor: 'bg-blue-500',
+    textColor: 'text-white',
+    border: false,
+  },
+}
+
+// 快捷创建函数
+export const createNavbar = (preset: keyof typeof NavbarPresets) => {
+  return NavbarPresets[preset]
+}
+
+export default Navbar

+ 37 - 0
mini-ui-packages/mini-shared-ui-components/src/components/page-container.tsx

@@ -0,0 +1,37 @@
+import React, { ReactNode } from 'react'
+import { View } from '@tarojs/components'
+import { cn } from '../utils/cn'
+
+export interface PageContainerProps {
+  children: ReactNode
+  className?: string
+  padding?: boolean
+  background?: string
+  safeArea?: boolean
+}
+
+export const PageContainer: React.FC<PageContainerProps> = ({
+  children,
+  className,
+  padding = true,
+  background = 'bg-gray-50',
+  safeArea = true,
+}) => {
+  return (
+    <View className={cn(
+      'min-h-screen w-full',
+      background,
+      safeArea && 'pb-safe',
+      className
+    )}>
+      <View className={cn(
+        padding && 'px-4 py-4',
+        'max-w-screen-md mx-auto'
+      )}>
+        {children}
+      </View>
+    </View>
+  )
+}
+
+export default PageContainer

+ 155 - 0
mini-ui-packages/mini-shared-ui-components/src/components/tab-bar.tsx

@@ -0,0 +1,155 @@
+import React from 'react'
+import { View, Text } from '@tarojs/components'
+import { cn } from '../utils/cn'
+
+export interface TabBarItem {
+  key: string
+  title: string
+  icon?: string
+  selectedIcon?: string
+  iconClass?: string
+  selectedIconClass?: string
+  badge?: number | string
+  dot?: boolean
+}
+
+export interface TabBarProps {
+  items: TabBarItem[]
+  activeKey?: string
+  onChange?: (key: string) => void
+  className?: string
+  style?: React.CSSProperties
+  fixed?: boolean
+  safeArea?: boolean
+  color?: string
+  selectedColor?: string
+  backgroundColor?: string
+}
+
+const TabBar = React.forwardRef<HTMLDivElement, TabBarProps>(({
+  items,
+  activeKey,
+  onChange,
+  className,
+  style,
+  fixed = true,
+  safeArea = true,
+  color = '#7f7f7f',
+  selectedColor = '#1890ff',
+  backgroundColor = '#ffffff',
+}, ref) => {
+
+  const currentActiveKey = activeKey || items[0]?.key
+
+  const handleTabChange = (key: string) => {
+    if (key !== currentActiveKey) {
+      onChange?.(key)
+    }
+  }
+
+  return (
+    <View
+      ref={ref}
+      className={cn(
+        'tab-bar',
+        fixed && 'fixed bottom-0 left-0 right-0',
+        safeArea && 'pb-safe',
+        'z-50',
+        className
+      )}
+      style={{
+        backgroundColor,
+        ...style,
+      }}
+    >
+      <View className="flex h-16 border-t border-gray-200">
+        {items.map((item) => {
+          const isActive = item.key === currentActiveKey
+
+          return (
+            <View
+              key={item.key}
+              className={cn(
+                'flex-1 flex flex-col items-center justify-center',
+                'px-2 py-1',
+                'cursor-pointer',
+                'transition-colors duration-200',
+                'hover:opacity-80'
+              )}
+              onClick={() => handleTabChange(item.key)}
+            >
+              <View className="relative">
+                {(item.iconClass || item.icon) && (
+                  <View
+                    className={cn(
+                      'mb-1',
+                      'flex items-center justify-center',
+                      item.iconClass ? 'w-6 h-6' : 'text-2xl',
+                      isActive ? 'text-blue-500' : 'text-gray-500'
+                    )}
+                    style={{
+                      color: isActive ? selectedColor : color,
+                    }}
+                  >
+                    {item.iconClass ? (
+                      <View
+                        className={cn(
+                          isActive && item.selectedIconClass
+                            ? item.selectedIconClass
+                            : item.iconClass,
+                          'w-full h-full'
+                        )}
+                      />
+                    ) : (
+                      isActive && item.selectedIcon ? item.selectedIcon : item.icon
+                    )}
+                  </View>
+                )}
+
+                {item.badge && (
+                  <View
+                    className={cn(
+                      'absolute -top-1 -right-2',
+                      'bg-red-500 text-white text-xs',
+                      'rounded-full px-1.5 py-0.5',
+                      'min-w-4 h-4 flex items-center justify-center'
+                    )}
+                  >
+                    {typeof item.badge === 'number' && item.badge > 99 ? '99+' : item.badge}
+                  </View>
+                )}
+
+                {item.dot && (
+                  <View
+                    className={cn(
+                      'absolute -top-1 -right-1',
+                      'w-2 h-2 bg-red-500 rounded-full'
+                    )}
+                  />
+                )}
+              </View>
+
+              <Text
+                className={cn(
+                  'text-xs',
+                  'leading-tight',
+                  isActive ? 'font-medium' : 'font-normal'
+                )}
+                style={{
+                  color: isActive ? selectedColor : color,
+                }}
+                numberOfLines={1}
+              >
+                {item.title}
+              </Text>
+            </View>
+          )
+        })}
+      </View>
+    </View>
+  )
+})
+
+TabBar.displayName = 'TabBar'
+
+export { TabBar }

+ 58 - 0
mini-ui-packages/mini-shared-ui-components/src/components/user-status-bar.tsx

@@ -0,0 +1,58 @@
+import React from 'react'
+import { View, Text, Image } from '@tarojs/components'
+import { cn } from '../utils/cn'
+
+export interface UserStatusBarProps {
+  userName?: string
+  avatarUrl?: string
+  companyName?: string
+  notificationCount?: number
+  className?: string
+}
+
+export const UserStatusBar: React.FC<UserStatusBarProps> = ({
+  userName = '企业用户',
+  avatarUrl,
+  companyName = '企业名称',
+  notificationCount = 0,
+  className,
+}) => {
+  return (
+    <View className={cn(
+      'flex items-center justify-between px-4 py-3 bg-white border-b border-gray-200',
+      className
+    )}>
+      <View className="flex items-center">
+        {avatarUrl ? (
+          <Image
+            src={avatarUrl}
+            className="w-10 h-10 rounded-full mr-3"
+            mode="aspectFill"
+          />
+        ) : (
+          <View className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center mr-3">
+            <Text className="text-white font-bold text-lg">
+              {userName.charAt(0).toUpperCase()}
+            </Text>
+          </View>
+        )}
+        <View>
+          <Text className="font-semibold text-gray-900">{userName}</Text>
+          <Text className="text-sm text-gray-600">{companyName}</Text>
+        </View>
+      </View>
+      <View className="relative">
+        <View className="i-heroicons-bell-20-solid w-6 h-6 text-gray-600" />
+        {notificationCount > 0 && (
+          <View className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full flex items-center justify-center">
+            <Text className="text-white text-xs font-bold">
+              {notificationCount > 99 ? '99+' : notificationCount}
+            </Text>
+          </View>
+        )}
+      </View>
+    </View>
+  )
+}
+
+export default UserStatusBar

+ 15 - 0
mini-ui-packages/mini-shared-ui-components/src/index.ts

@@ -0,0 +1,15 @@
+// mini-shared-ui-components采用细粒度路径导出方式
+// 不再导出任何内容,所有工具和组件通过具体路径导入
+//
+// 组件导入示例:
+// import { Button } from '@d8d/mini-shared-ui-components/button'
+// import { Card } from '@d8d/mini-shared-ui-components/card'
+//
+// 工具函数导入示例:
+// import { cn } from '@d8d/mini-shared-ui-components/utils/cn'
+// import { getPlatform } from '@d8d/mini-shared-ui-components/utils/platform'
+//
+// RPC工具导入示例:
+// import { createRpcClient } from '@d8d/mini-shared-ui-components/utils/rpc/rpc-client'
+//
+// ResponsePolyfill和HeadersPolyfill仅内部使用,不对外导出

+ 15 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/cn.ts

@@ -0,0 +1,15 @@
+import { type ClassValue } from 'clsx'
+import { create } from '@weapp-tailwindcss/merge';
+import Taro from '@tarojs/taro';
+
+// 根据当前环境判断是否需要转义
+const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP;
+
+const { twMerge } = create({
+  // 仅在小程序环境下启用转义,H5环境禁用
+  disableEscape: !isWeapp
+});
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(inputs)
+}

+ 16 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/platform.ts

@@ -0,0 +1,16 @@
+import Taro from '@tarojs/taro'
+
+// 获取当前平台
+export const getPlatform = () => {
+  return Taro.getEnv()
+}
+
+// 是否为小程序
+export const isWeapp = (): boolean => {
+  return getPlatform() === Taro.ENV_TYPE.WEAPP
+}
+
+// 是否为H5
+export const isH5 = (): boolean => {
+  return getPlatform() === Taro.ENV_TYPE.WEB
+}

+ 16 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/rpc/headers-polyfill.d.ts

@@ -0,0 +1,16 @@
+declare class Headers {
+  constructor(init?: HeadersInit)
+
+  append(name: string, value: string): void
+  set(name: string, value: string): void
+  get(name: string): string | null
+  has(name: string): boolean
+  delete(name: string): void
+  forEach(callback: (value: string, name: string, headers: Headers) => void): void
+  raw(): Record<string, string>
+
+  _normalizeName(name: string): string
+}
+
+export declare function registerGlobalHeadersPolyfill(): void
+export default Headers

+ 87 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/rpc/headers-polyfill.js

@@ -0,0 +1,87 @@
+class Headers {
+  constructor(init = {}) {
+    this._headers = {};
+
+    if (init instanceof Headers) {
+      // 如果传入的是另一个 Headers 实例,复制其内容
+      init.forEach((value, name) => {
+        this.append(name, value);
+      });
+    } else if (init) {
+      // 处理普通对象或数组
+      Object.entries(init).forEach(([name, value]) => {
+        if (Array.isArray(value)) {
+          // 处理数组值(如 ['value1', 'value2'])
+          value.forEach(v => this.append(name, v));
+        } else {
+          this.set(name, value);
+        }
+      });
+    }
+  }
+
+  // 添加头(可重复添加同名头)
+  append(name, value) {
+    const normalizedName = this._normalizeName(name);
+    if (this._headers[normalizedName]) {
+      this._headers[normalizedName] += `, ${value}`;
+    } else {
+      this._headers[normalizedName] = String(value);
+    }
+  }
+
+  // 设置头(覆盖同名头)
+  set(name, value) {
+    this._headers[this._normalizeName(name)] = String(value);
+  }
+
+  // 获取头
+  get(name) {
+    return this._headers[this._normalizeName(name)] || null;
+  }
+
+  // 检查是否存在头
+  has(name) {
+    return this._normalizeName(name) in this._headers;
+  }
+
+  // 删除头
+  delete(name) {
+    delete this._headers[this._normalizeName(name)];
+  }
+
+  // 遍历头
+  forEach(callback) {
+    Object.entries(this._headers).forEach(([name, value]) => {
+      callback(value, name, this);
+    });
+  }
+
+  // 获取所有头(原始对象)
+  raw() {
+    return { ...this._headers };
+  }
+
+  // 规范化头名称(转为小写)
+  _normalizeName(name) {
+    if (typeof name !== 'string') {
+      throw new TypeError('Header name must be a string');
+    }
+    return name.toLowerCase();
+  }
+}
+
+// 可选的全局注册函数
+export function registerGlobalHeadersPolyfill() {
+  if (typeof globalThis.Headers === 'undefined') {
+    globalThis.Headers = Headers;
+  }
+}
+
+// 默认导出Headers类
+export default Headers;
+
+// 自动注册(如果环境需要)
+if (typeof globalThis.Headers === 'undefined') {
+  globalThis.Headers = Headers;
+}

+ 93 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/rpc/response-polyfill.ts

@@ -0,0 +1,93 @@
+// 在全局注册Headers(如果不存在)
+if (typeof globalThis.Headers === 'undefined') {
+  // 这里会由headers-polyfill.js注册
+}
+
+export class ResponsePolyfill {
+  constructor(
+    public body: string | ArrayBuffer | null,
+    public init: {
+      status?: number
+      statusText?: string
+      headers?: Record<string, string>
+    } = {}
+  ) {}
+
+  get ok(): boolean {
+    return this.status >= 200 && this.status < 300
+  }
+
+  get status(): number {
+    return this.init.status || 200
+  }
+
+  get statusText(): string {
+    return this.init.statusText || 'OK'
+  }
+
+  get headers(): Headers {
+    return new Headers(this.init.headers || {})
+  }
+
+  get bodyUsed(): boolean {
+    return false // 小程序环境简单实现
+  }
+
+  async arrayBuffer(): Promise<ArrayBuffer> {
+    if (this.body instanceof ArrayBuffer) {
+      return this.body
+    }
+    throw new Error('Not implemented')
+  }
+
+  async text(): Promise<string> {
+    if (typeof this.body === 'string') {
+      return this.body
+    }
+    throw new Error('Not implemented')
+  }
+
+  async json<T = any>(): Promise<T> {
+    if (typeof this.body === 'string') {
+      try {
+        return JSON.parse(this.body)
+      } catch (e) {
+        throw new Error('Invalid JSON')
+      }
+    }
+    throw new Error('Not implemented')
+  }
+
+  clone(): ResponsePolyfill {
+    return new ResponsePolyfill(this.body, { ...this.init })
+  }
+
+  static json(data: any, init?: ResponseInit): ResponsePolyfill {
+    const headers = new Headers(init && 'headers' in init ? init.headers : undefined)
+    if (!headers.has('Content-Type')) {
+      headers.set('Content-Type', 'application/json')
+    }
+    return new ResponsePolyfill(JSON.stringify(data), {
+      ...init,
+      headers: (headers as any).raw ? (headers as any).raw() : {}
+    })
+  }
+
+  static error(): ResponsePolyfill {
+    return new ResponsePolyfill(null, { status: 0, statusText: 'Network Error' })
+  }
+
+  static redirect(url: string, status: number): ResponsePolyfill {
+    return new ResponsePolyfill(null, {
+      status,
+      headers: { Location: url }
+    })
+  }
+}
+
+// 可选的全局注册函数
+export function registerGlobalResponsePolyfill(): void {
+  if (typeof globalThis.Response === 'undefined') {
+    globalThis.Response = ResponsePolyfill as any
+  }
+}

+ 189 - 0
mini-ui-packages/mini-shared-ui-components/src/utils/rpc/rpc-client.ts

@@ -0,0 +1,189 @@
+import Taro from '@tarojs/taro'
+import { hc } from 'hono/client'
+import { ResponsePolyfill } from './response-polyfill'
+
+// 刷新token的函数
+let isRefreshing = false
+let refreshSubscribers: ((token: string) => void)[] = []
+
+// 执行token刷新
+const refreshToken = async (): Promise<string | null> => {
+  if (isRefreshing) {
+    // 如果已经在刷新,等待结果
+    return new Promise((resolve) => {
+      refreshSubscribers.push((token) => {
+        resolve(token)
+      })
+    })
+  }
+
+  isRefreshing = true
+  try {
+    const refreshToken = Taro.getStorageSync('enterprise_refresh_token')
+    if (!refreshToken) {
+      throw new Error('未找到刷新token')
+    }
+
+    // 调用刷新token接口
+    const response = await Taro.request({
+      url: `${process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'}/api/v1/yongren/auth/refresh-token`,
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json',
+        'Authorization': `Bearer ${refreshToken}`
+      }
+    })
+
+    if (response.statusCode === 200) {
+      const { token, refresh_token: newRefreshToken } = response.data
+      Taro.setStorageSync('enterprise_token', token)
+      if (newRefreshToken) {
+        Taro.setStorageSync('enterprise_refresh_token', newRefreshToken)
+      }
+
+      // 通知所有等待的请求
+      refreshSubscribers.forEach(callback => callback(token))
+      refreshSubscribers = []
+      return token
+    } else {
+      throw new Error('刷新token失败')
+    }
+  } catch (error) {
+    console.error('刷新token失败:', error)
+    // 清除token,跳转到登录页
+    Taro.removeStorageSync('enterprise_token')
+    Taro.removeStorageSync('enterprise_refresh_token')
+    Taro.removeStorageSync('enterpriseUserInfo')
+
+    // 跳转到登录页
+    Taro.showToast({
+      title: '登录已过期,请重新登录',
+      icon: 'none'
+    })
+    setTimeout(() => {
+      Taro.redirectTo({
+        url: '/pages/login/index'
+      })
+    }, 1500)
+
+    return null
+  } finally {
+    isRefreshing = false
+  }
+}
+
+// API配置
+const API_BASE_URL = process.env.TARO_APP_API_BASE_URL || 'http://localhost:3000'
+
+// 完整的API地址
+// const BASE_URL = `${API_BASE_URL}/api/${API_VERSION}`
+
+// 创建自定义fetch函数,适配Taro.request,支持token自动刷新
+const taroFetch: any = async (input, init) => {
+  const url = typeof input === 'string' ? input : input.url
+  const method = init.method || 'GET'
+
+  const requestHeaders: Record<string, string> = init.headers;
+
+  const keyOfContentType = Object.keys(requestHeaders).find(item => item.toLowerCase() === 'content-type')
+  if (!keyOfContentType) {
+    requestHeaders['content-type'] = 'application/json'
+  }
+
+  // 构建Taro请求选项
+  const options: Taro.request.Option = {
+    url,
+    method: method as any,
+    data: init.body,
+    header: requestHeaders
+  }
+
+  // 添加token - 根据API路径自动选择正确的token
+  let token: string | null = null
+
+  // 根据API路径判断是用人方还是人才小程序的请求
+  if (url.includes('/api/v1/yongren/')) {
+    // 用人方API,使用enterprise_token
+    token = Taro.getStorageSync('enterprise_token')
+  } else if (url.includes('/api/v1/rencai/')) {
+    // 人才API,使用talent_token
+    token = Taro.getStorageSync('talent_token')
+  }
+
+  if (token) {
+    options.header = {
+      ...options.header,
+      'Authorization': `Bearer ${token}`
+    }
+  }
+
+  // 发送请求
+  const sendRequest = async (): Promise<any> => {
+    try {
+      console.log('API请求:', options.url)
+      const response = await Taro.request(options)
+
+      const responseHeaders = response.header;
+
+      // 处理204 No Content响应,不设置body
+      const body = response.statusCode === 204
+        ? null
+        : responseHeaders['content-type']!.includes('application/json')
+          ? JSON.stringify(response.data)
+          : response.data;
+
+      return new ResponsePolyfill(
+        body,
+        {
+          status: response.statusCode,
+          statusText: response.errMsg || 'OK',
+          headers: responseHeaders
+        }
+      )
+    } catch (error) {
+      console.error('API Error:', error)
+      throw error
+    }
+  }
+
+  try {
+    let response = await sendRequest()
+
+    // 检查是否为401错误,尝试刷新token
+    if (response.status === 401 && token) {
+      console.log('检测到401错误,尝试刷新token...')
+      const newToken = await refreshToken()
+
+      if (newToken) {
+        // 更新请求header中的token
+        options.header = {
+          ...options.header,
+          'Authorization': `Bearer ${newToken}`
+        }
+
+        // 重试原始请求
+        response = await sendRequest()
+      } else {
+        // 刷新失败,返回原始401响应
+        return response
+      }
+    }
+
+    return response
+  } catch (error) {
+    console.error('API请求失败:', error)
+    Taro.showToast({
+      title: error.message || '网络错误',
+      icon: 'none'
+    })
+    throw error
+  }
+}
+
+// 创建Hono RPC客户端
+export const rpcClient = <T extends any>(apiBasePath?: string) => {
+  // @ts-ignore
+  return hc<T>(`${API_BASE_URL}${apiBasePath}`, {
+    fetch: taroFetch
+  })
+}

+ 10 - 0
mini-ui-packages/mini-shared-ui-components/testing/index.ts

@@ -0,0 +1,10 @@
+// 测试工具导出
+import { setupTaroMocks } from '../tests/__helpers__/taro-mocks'
+import { renderTaroComponent } from '../tests/__helpers__/test-utils'
+import { setupTestEnv } from '../tests/__helpers__/env-setup'
+
+export { setupTaroMocks, renderTaroComponent, setupTestEnv }
+
+// 配置导出
+export { default as jestPreset } from '../tests/__config__/jest-preset.js'
+export { default as tsconfigTest } from '../tests/__config__/tsconfig.test.json'

+ 17 - 0
mini-ui-packages/mini-shared-ui-components/tests/__config__/jest-preset.d.ts

@@ -0,0 +1,17 @@
+// TypeScript declarations for jest-preset.js
+declare const jestPreset: {
+  preset: string;
+  testEnvironment: string;
+  setupFilesAfterEnv: string[];
+  moduleNameMapper: Record<string, string>;
+  testMatch: string[];
+  collectCoverageFrom: string[];
+  coverageDirectory: string;
+  coverageReporters: string[];
+  testPathIgnorePatterns: string[];
+  transform: Record<string, any>;
+  transformIgnorePatterns: string[];
+  moduleFileExtensions: string[];
+};
+
+export default jestPreset;

+ 39 - 0
mini-ui-packages/mini-shared-ui-components/tests/__config__/jest-preset.js

@@ -0,0 +1,39 @@
+// Jest预设配置,供其他mini UI包复用
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'jsdom',
+  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1',
+    '^~/(.*)$': '<rootDir>/tests/$1',
+    '^@tarojs/taro$': '<rootDir>/tests/__mocks__/taroMock.ts',
+    '\\.(css|less|scss|sass)$': '<rootDir>/tests/__mocks__/styleMock.js',
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+      '<rootDir>/tests/__mocks__/fileMock.js'
+  },
+  testMatch: [
+    '<rootDir>/tests/**/*.spec.{ts,tsx}',
+    '<rootDir>/tests/**/*.test.{ts,tsx}'
+  ],
+  collectCoverageFrom: [
+    'src/**/*.{ts,tsx}',
+    '!src/**/*.d.ts',
+    '!src/**/index.{ts,tsx}',
+    '!src/**/*.stories.{ts,tsx}'
+  ],
+  coverageDirectory: 'coverage',
+  coverageReporters: ['text', 'lcov', 'html'],
+  testPathIgnorePatterns: [
+    '/node_modules/',
+    '/dist/',
+    '/coverage/'
+  ],
+  transform: {
+    '^.+\\.(ts|tsx)$': 'ts-jest',
+    '^.+\\.(js|jsx)$': 'babel-jest'
+  },
+  transformIgnorePatterns: [
+    '/node_modules/(?!(swiper|@tarojs)/)'
+  ],
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json']
+}

+ 9 - 0
mini-ui-packages/mini-shared-ui-components/tests/__config__/tsconfig.test.json

@@ -0,0 +1,9 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "jsx": "react-jsx",
+    "types": ["jest", "@testing-library/jest-dom", "node"]
+  },
+  "include": ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "setup.ts"],
+  "exclude": ["node_modules", "dist"]
+}

+ 10 - 0
mini-ui-packages/mini-shared-ui-components/tests/__helpers__/env-setup.ts

@@ -0,0 +1,10 @@
+// 测试环境设置
+export const setupTestEnv = () => {
+  // 设置环境变量
+  process.env.TARO_ENV = 'h5'
+  process.env.TARO_PLATFORM = 'web'
+  process.env.SUPPORT_TARO_POLYFILL = 'disabled'
+
+  // 定义 defineAppConfig 全局函数用于测试 Taro 配置文件
+  ;(global as any).defineAppConfig = (config: any) => config
+}

+ 8 - 0
mini-ui-packages/mini-shared-ui-components/tests/__helpers__/taro-mocks.ts

@@ -0,0 +1,8 @@
+// Taro组件mock配置
+// 从setup.ts中提取的mock逻辑可以放在这里
+// 目前直接使用setup.ts中的完整mock
+
+export const setupTaroMocks = () => {
+  // 如果需要额外的mock设置,可以在这里添加
+  // 目前setup.ts已经处理了所有mock
+}

+ 8 - 0
mini-ui-packages/mini-shared-ui-components/tests/__helpers__/test-utils.ts

@@ -0,0 +1,8 @@
+// 测试工具函数
+import { render, type RenderResult } from '@testing-library/react'
+
+export const renderTaroComponent = (component: React.ReactElement, options?: any): RenderResult => {
+  return render(component, options)
+}
+
+// 其他通用的测试工具函数可以在这里添加

+ 1 - 0
mini-ui-packages/mini-shared-ui-components/tests/__mocks__/fileMock.js

@@ -0,0 +1 @@
+module.exports = 'test-file-stub'

+ 1 - 0
mini-ui-packages/mini-shared-ui-components/tests/__mocks__/styleMock.js

@@ -0,0 +1 @@
+module.exports = {}

+ 100 - 0
mini-ui-packages/mini-shared-ui-components/tests/__mocks__/taroMock.ts

@@ -0,0 +1,100 @@
+/**
+ * Taro API Mock 文件
+ * 通过 jest.config.js 的 moduleNameMapper 重定向 @tarojs/taro 到这里
+ */
+
+// 创建所有 Taro API 的 mock 函数
+export const mockShowToast = jest.fn()
+export const mockShowLoading = jest.fn()
+export const mockHideLoading = jest.fn()
+export const mockNavigateTo = jest.fn()
+export const mockNavigateBack = jest.fn()
+export const mockSwitchTab = jest.fn()
+export const mockShowModal = jest.fn()
+export const mockReLaunch = jest.fn()
+export const mockOpenCustomerServiceChat = jest.fn()
+export const mockUseRouter = jest.fn()
+export const mockRequestPayment = jest.fn()
+export const mockGetEnv = jest.fn()
+export const mockUseLoad = jest.fn()
+export const mockUseShareAppMessage = jest.fn()
+export const mockUseShareTimeline = jest.fn()
+export const mockGetCurrentInstance = jest.fn()
+
+// 环境类型常量
+export const ENV_TYPE = {
+  WEAPP: 'WEAPP',
+  WEB: 'WEB',
+  RN: 'RN',
+  SWAN: 'SWAN',
+  ALIPAY: 'ALIPAY',
+  TT: 'TT',
+  QQ: 'QQ',
+  JD: 'JD',
+  HARMONY: 'HARMONY'
+}
+
+// 导出所有 mock 函数,便于在测试中访问
+export default {
+  // UI 相关
+  showToast: mockShowToast,
+  showLoading: mockShowLoading,
+  hideLoading: mockHideLoading,
+  showModal: mockShowModal,
+
+  // 导航相关
+  navigateTo: mockNavigateTo,
+  navigateBack: mockNavigateBack,
+  switchTab: mockSwitchTab,
+  reLaunch: mockReLaunch,
+  useRouter: () => mockUseRouter(),
+  useLoad: (callback: any) => mockUseLoad(callback),
+
+  // 微信相关
+  openCustomerServiceChat: mockOpenCustomerServiceChat,
+  requestPayment: mockRequestPayment,
+
+  // 系统信息
+  getSystemInfoSync: () => ({
+    statusBarHeight: 20
+  }),
+  getMenuButtonBoundingClientRect: () => ({
+    width: 87,
+    height: 32,
+    top: 48,
+    right: 314,
+    bottom: 80,
+    left: 227
+  }),
+  getEnv: mockGetEnv,
+
+  // 分享相关
+  useShareAppMessage: mockUseShareAppMessage,
+  useShareTimeline: mockUseShareTimeline,
+
+  // 实例相关
+  getCurrentInstance: mockGetCurrentInstance,
+
+  // 环境类型常量
+  ENV_TYPE
+}
+
+// 为命名导入导出所有函数
+export {
+  mockShowToast as showToast,
+  mockShowLoading as showLoading,
+  mockHideLoading as hideLoading,
+  mockShowModal as showModal,
+  mockNavigateTo as navigateTo,
+  mockNavigateBack as navigateBack,
+  mockSwitchTab as switchTab,
+  mockReLaunch as reLaunch,
+  mockUseRouter as useRouter,
+  mockUseLoad as useLoad,
+  mockOpenCustomerServiceChat as openCustomerServiceChat,
+  mockRequestPayment as requestPayment,
+  mockGetEnv as getEnv,
+  mockUseShareAppMessage as useShareAppMessage,
+  mockUseShareTimeline as useShareTimeline,
+  mockGetCurrentInstance as getCurrentInstance
+}

Some files were not shown because too many files changed in this diff