|
|
@@ -0,0 +1,1124 @@
|
|
|
+/**
|
|
|
+ * 柱状图和条形图绘制函数
|
|
|
+ *
|
|
|
+ * 从 u-charts 核心库搬迁的柱状图和条形图绘制相关函数
|
|
|
+ */
|
|
|
+
|
|
|
+// @ts-nocheck - 由于从 u-charts 搬迁,类型系统不兼容,暂时禁用类型检查
|
|
|
+
|
|
|
+import type { ChartOptions, UChartsConfig, SeriesItem, ColumnOptions, MountOptions } from '../data-processing/series-calculator.js';
|
|
|
+import {
|
|
|
+ getColumnDataPoints,
|
|
|
+ getStackDataPoints,
|
|
|
+ getDataPoints,
|
|
|
+ getBarDataPoints,
|
|
|
+ getBarStackDataPoints,
|
|
|
+ getMountDataPoints,
|
|
|
+} from '../charts-data/basic-charts.js';
|
|
|
+import {
|
|
|
+ fixColumeData,
|
|
|
+ fixColumeStackData,
|
|
|
+ fixColumeMeterData,
|
|
|
+ fixBarData,
|
|
|
+ fillCustomColor,
|
|
|
+} from '../u-charts.js';
|
|
|
+import { drawToolTipSplitArea, drawBarToolTipSplitArea } from './common-renderer.js';
|
|
|
+import { assign } from '../config.js';
|
|
|
+import { hexToRgb } from '../utils/color.js';
|
|
|
+
|
|
|
+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;
|
|
|
+}
|
|
|
+
|
|
|
+// 声明全局变量
|
|
|
+declare const process: number;
|
|
|
+declare const chartProcess: any;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 绘制柱状图数据点
|
|
|
+ * @param series 系列数据
|
|
|
+ * @param opts 图表配置
|
|
|
+ * @param config UCharts配置
|
|
|
+ * @param context Canvas上下文
|
|
|
+ * @returns 计算结果
|
|
|
+ */
|
|
|
+export function drawColumnDataPoints(
|
|
|
+ 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 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,
|
|
|
+ chartProcess
|
|
|
+ );
|
|
|
+ const tooltipPoints = getStackDataPoints(
|
|
|
+ data as number[],
|
|
|
+ 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++) {
|
|
|
+ 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':
|
|
|
+ // 绘制堆叠数据图
|
|
|
+ let points = getStackDataPoints(
|
|
|
+ data as number[],
|
|
|
+ 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++) {
|
|
|
+ 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':
|
|
|
+ // 绘制温度计数据图
|
|
|
+ let 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();
|
|
|
+ const data = eachSeries.data;
|
|
|
+
|
|
|
+ switch (columnOption.type) {
|
|
|
+ case 'group':
|
|
|
+ let points = getColumnDataPoints(
|
|
|
+ data as number[],
|
|
|
+ minRange,
|
|
|
+ maxRange,
|
|
|
+ xAxisPoints,
|
|
|
+ eachSpacing,
|
|
|
+ opts,
|
|
|
+ config,
|
|
|
+ zeroPoints,
|
|
|
+ chartProcess
|
|
|
+ );
|
|
|
+ points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
|
|
|
+ drawColumePointText(points, eachSeries, config, context, opts);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'stack':
|
|
|
+ let points = getStackDataPoints(
|
|
|
+ data as number[],
|
|
|
+ minRange,
|
|
|
+ maxRange,
|
|
|
+ xAxisPoints,
|
|
|
+ eachSpacing,
|
|
|
+ opts,
|
|
|
+ config,
|
|
|
+ seriesIndex,
|
|
|
+ series,
|
|
|
+ chartProcess
|
|
|
+ );
|
|
|
+ drawColumePointText(points, eachSeries, config, context, opts);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'meter':
|
|
|
+ let 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();
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|