UNPKG

@alicloud/cloud-charts

Version:

![](https://img.shields.io/npm/v/@alicloud/cloud-charts?color=%23ff8200)

511 lines (498 loc) 23 kB
'use strict'; import _extends from "@babel/runtime/helpers/extends"; import React, { useState, useEffect, useRef } from 'react'; import { FullCrossName, PrefixName } from '../constants'; import WidgetsTooltip from '../common/Tooltip'; import { numberDecimal, unitConversion } from '../common/common'; import { GlobalResizeObserver } from '../common/globalResizeObserver'; import "./index.css"; var prefix = PrefixName + "-wgauge"; var Wgauge = function Wgauge(props) { var _config$angle3, _config$angle4; var data = props.data, config = props.config; var label = data.label; var _ref = config || {}, _ref$min = _ref.min, minValue = _ref$min === void 0 ? 0 : _ref$min, maxValue = _ref.max, _ref$colors = _ref.colors, colors = _ref$colors === void 0 ? [[60, 'error'], [80, 'warning'], [100, 'success']] : _ref$colors, _ref$fontColorFit = _ref.fontColorFit, fontColorFit = _ref$fontColorFit === void 0 ? false : _ref$fontColorFit, _ref$angle = _ref.angle, angle = _ref$angle === void 0 ? { start: -180, // 默认起始角度(x轴负方向) end: 0 // 默认结束角度(x轴方向) } : _ref$angle, renderText = _ref.renderText, _ref$className = _ref.className, className = _ref$className === void 0 ? '' : _ref$className, _ref$decorationStroke = _ref.decorationStrokeWidth, decorationStrokeWidth = _ref$decorationStroke === void 0 ? 4 : _ref$decorationStroke, _ref$outRing = _ref.outRing, outRing = _ref$outRing === void 0 ? true : _ref$outRing, _ref$gaugeScale = _ref.gaugeScale, gaugeScale = _ref$gaugeScale === void 0 ? false : _ref$gaugeScale, unit = _ref.unit, _ref$customStyles = _ref.customStyles, customStyles = _ref$customStyles === void 0 ? {} : _ref$customStyles, needUnitTransform = _ref.needUnitTransform, _ref$valueType = _ref.valueType, valueType = _ref$valueType === void 0 ? 'percent_100' : _ref$valueType, decimal = _ref.decimal, unitTransformTo = _ref.unitTransformTo, customCarryUnits = _ref.customCarryUnits, customCarryThreshold = _ref.customCarryThreshold, addonTextAfter = _ref.addonTextAfter; var _ref2 = Array.isArray(colors) ? transformColors(colors) : [[[100, colors]], [[100, colors]]], colors1 = _ref2[0], colors2 = _ref2[1]; var current = data.current || 0; if (valueType === 'percent_1') { current = current * 100; } var finalValue = current; var finalUnit = unit; var min = minValue; var max = maxValue === undefined ? 100 : valueType === 'percent_1' ? maxValue * 100 : maxValue; if (needUnitTransform && (unit || valueType)) { var _unitConversion = unitConversion(current, finalUnit, decimal, unitTransformTo, valueType, customCarryUnits, customCarryThreshold, addonTextAfter), value = _unitConversion.value, transformUnit = _unitConversion.unit; finalValue = value; finalUnit = transformUnit; } else { finalValue = numberDecimal(current, decimal); } var _customStyles$textSty = customStyles.textStyle, textStyle = _customStyles$textSty === void 0 ? {} : _customStyles$textSty, _customStyles$valueSt = customStyles.valueStyle, valueStyle = _customStyles$valueSt === void 0 ? {} : _customStyles$valueSt, _customStyles$unitSty = customStyles.unitStyle, unitStyle = _customStyles$unitSty === void 0 ? {} : _customStyles$unitSty, _customStyles$gaugeTe = customStyles.gaugeTextStyle, gaugeTextStyle = _customStyles$gaugeTe === void 0 ? {} : _customStyles$gaugeTe, _customStyles$gaugeLi = customStyles.gaugeLineStyle, gaugeLineStyle = _customStyles$gaugeLi === void 0 ? {} : _customStyles$gaugeLi, _customStyles$scaleLi = customStyles.scaleLineLength, scaleLineLength = _customStyles$scaleLi === void 0 ? gaugeScale !== false ? 10 : 0 : _customStyles$scaleLi; var labelRef = useRef(null); // 获取用户自定义刻度样式时的字体大小 var _useState = useState(false), gaugeScaleFlag = _useState[0], setGaugeScaleFlag = _useState[1]; var _useState2 = useState(scaleLineLength), realScaleLineLength = _useState2[0], setRealScaleLineLength = _useState2[1]; // 当半径小于50时,取消掉外圈刻度,否则取用户自定义字体大小,如果没有设置,则看用户是否启用刻度,启用的话默认12 var gaugeTextSize = gaugeScaleFlag ? 0 : getFontSizeNumber(gaugeTextStyle) === 0 ? gaugeScale ? 12 : 0 : getFontSizeNumber(gaugeTextStyle); var _ref3 = typeof gaugeScale === 'object' && gaugeScale !== null ? gaugeScale : { scaleNum: 0, scale: false }, _ref3$scaleNum = _ref3.scaleNum, scaleNum = _ref3$scaleNum === void 0 ? 5 : _ref3$scaleNum, _ref3$scale = _ref3.scale, scale = _ref3$scale === void 0 ? true : _ref3$scale; var containerRef = useRef(null); // 用于引用父容器的ref var _useState3 = useState(100), radius = _useState3[0], setRadius = _useState3[1]; // 默认半径为100,实际会基于父元素高度来调整 var strokeWidth = radius * 0.12 < 12 ? 12 : radius * 0.12; // const decorationGap = config?.angle && config.angle?.end - config.angle?.start > 180 ? 0 : outRing ? strokeWidth : 0; var decorationGap = outRing ? strokeWidth : 0; var _useState4 = useState(100 * 0.8 * 0.24), lineSize = _useState4[0], setLineSize = _useState4[1]; var _useState5 = useState(0), gap = _useState5[0], setGap = _useState5[1]; var textRef = useRef(null); var _useState6 = useState(false), flag = _useState6[0], setFlag = _useState6[1]; var strokeColor = Array.isArray(colors) ? "var(--" + prefix + "-" + getColorForCurrent(current, min, max, colors) + "-color)" : "var(--" + prefix + "-" + colors + "-color)"; useEffect(function () { // 确定父元素的高度,并据此更新半径 var timer; var handleResize = function handleResize() { clearTimeout(timer); timer = setTimeout(function () { var _config$angle, _config$angle2; if (!containerRef.current) return; var height = containerRef.current.clientHeight || 200; var width = containerRef.current.clientWidth; // 当用宽度的一半做半径时设置flag用于调整文字样式 if (width < height * 2) { setFlag(true); } else { setFlag(false); } var realradius = width < height * 2 ? width / 2 : height; var strokeWidth = realradius * 0.12 < 12 ? 12 : realradius * 0.12; var decorationGap = config !== null && config !== void 0 && config.angle && ((_config$angle = config.angle) === null || _config$angle === void 0 ? void 0 : _config$angle.end) - ((_config$angle2 = config.angle) === null || _config$angle2 === void 0 ? void 0 : _config$angle2.start) > 180 ? 0 : outRing ? strokeWidth : 0; var scaleHeight = gaugeScaleFlag && width < height * 2 ? decorationStrokeWidth * 2 : 0; // 圆的半径需要考虑strokeWidth的影响,以便圆完全显示在父元素内 // setGap(height - (realradius - strokeWidth - decorationGap - decorationStrokeWidth - scaleHeight)); setRadius(realradius - strokeWidth - decorationGap - decorationStrokeWidth - scaleHeight); setLineSize(realradius * 0.8 * 0.24); }, 0); }; handleResize(); var parent = containerRef.current && containerRef.current.parentElement; if (parent) { GlobalResizeObserver.observe(parent, handleResize); } return function () { clearTimeout(timer); var parent = containerRef.current && containerRef.current.parentElement; if (parent) { GlobalResizeObserver.unobserve(parent); } }; }, [containerRef]); useEffect(function () { if (radius < 40) { setGaugeScaleFlag(true); setRealScaleLineLength(0); } else { setGaugeScaleFlag(false); setRealScaleLineLength(scaleLineLength); } }, [radius]); // 计算弧度的起始角和结束角 var startAngle = angle.start; var endAngle = startAngle + Math.min(1, Math.max(0, (current - min) / (max - min))) * (angle.end - angle.start); var bottomEnd = angle.end; var middleAngle = angle.start + (angle.end - angle.start) / 2; // 计算起始点和结束点坐标 var _calculatePositionOnC = calculatePositionOnCircle(startAngle, radius), startX = _calculatePositionOnC.x, startY = _calculatePositionOnC.y; var _calculatePositionOnC2 = calculatePositionOnCircle(endAngle, radius), endX = _calculatePositionOnC2.x, endY = _calculatePositionOnC2.y; var _calculatePositionOnC3 = calculatePositionOnCircle(bottomEnd, radius), bottomEndX = _calculatePositionOnC3.x, bottomEndY = _calculatePositionOnC3.y; // 根据圆弧长短设置 largeArcFlag var largeArcFlag = angle.end - angle.start > 180 ? 1 : 0; var dataArcFlag = Math.min(1, Math.max(0, (current - min) / (max - min))) * (angle.end - angle.start) > 180 ? 1 : 0; var _calculatePositionOnC4 = calculatePositionOnCircle(startAngle, radius, decorationGap), decoratedStartX = _calculatePositionOnC4.x, decoratedStartY = _calculatePositionOnC4.y; var _calculatePositionOnC5 = calculatePositionOnCircle(middleAngle, radius, decorationGap), decoratedMiddleX = _calculatePositionOnC5.x, decoratedMiddleY = _calculatePositionOnC5.y; var _calculatePositionOnC6 = calculatePositionOnCircle(bottomEnd, radius, decorationGap), decoratedEndX = _calculatePositionOnC6.x, decoratedEndY = _calculatePositionOnC6.y; // 仪表盘装饰圆环左侧 var pathRingDataLeft = ["M " + decoratedStartX + " " + decoratedStartY, // 移动到装饰性弧线的起点 "A " + (radius + decorationGap) + " " + (radius + decorationGap) + " 0 0 1 " + decoratedMiddleX + " " + decoratedMiddleY].join(' '); // 仪表盘装饰圆环右侧 var pathRingDataRight = ["M " + decoratedMiddleX + " " + decoratedMiddleY, // 移动到装饰性弧线的中点 "A " + (radius + decorationGap) + " " + (radius + decorationGap) + " 0 0 1 " + decoratedEndX + " " + decoratedEndY].join(' '); var pathRingDataAll = ["M " + decoratedStartX + " " + decoratedStartY, // 移动到装饰性弧线的中点 "A " + (radius + decorationGap) + " " + (radius + decorationGap) + " 0 0 1 " + decoratedEndX + " " + decoratedEndY].join(' '); // 仪表盘圆环 var pathData = ["M " + startX + " " + startY, // 移动到起点 "A " + radius + " " + radius + " 0 " + dataArcFlag + " 1 " + endX + " " + endY].join(' '); // 底纹 var pathBottomData = ["M " + startX + " " + startY, // 移动到起点 "A " + radius + " " + radius + " 0 " + largeArcFlag + " 1 " + bottomEndX + " " + bottomEndY // `A ${radius} ${radius} 0 0 1 ${startX} ${startY}`, ].join(' '); var renderNum = function renderNum() { var lineHeight = valueStyle.lineHeight, fontSize = valueStyle.fontSize; return /*#__PURE__*/React.createElement("div", { className: prefix + "-value-wrapper" }, /*#__PURE__*/React.createElement("div", { className: prefix + "-num", style: { fontSize: fontSize ? fontSize : lineSize, lineHeight: lineHeight ? lineHeight : lineSize + "px" } }, renderText ? renderText : finalValue), /*#__PURE__*/React.createElement("div", { style: _extends({ fontSize: fontSize ? fontSize : lineSize * 0.7, lineHeight: lineHeight ? lineHeight : lineSize * 0.7 + "px" }, unitStyle), className: prefix + "-unit" }, !renderText ? finalUnit : undefined)); }; // strokeWidth:24,decorationGap:22,decorationStrokeWidth:4 var viewBoxDecoratedX = -strokeWidth / 2 - decorationGap - decorationStrokeWidth / 2; var viewBoxDecoratedY = -strokeWidth / 2 - decorationGap - decorationStrokeWidth / 2; var viewBoxWidthWithDecorations = 2 * radius + strokeWidth + 2 * (decorationGap + decorationStrokeWidth); var viewBoxHeightWithDecorations = radius + strokeWidth + decorationGap + decorationStrokeWidth > 50 ? config !== null && config !== void 0 && config.angle && ((_config$angle3 = config.angle) === null || _config$angle3 === void 0 ? void 0 : _config$angle3.end) - ((_config$angle4 = config.angle) === null || _config$angle4 === void 0 ? void 0 : _config$angle4.start) > 180 ? radius + strokeWidth + decorationGap + decorationStrokeWidth : radius + 2 * strokeWidth + decorationGap + decorationStrokeWidth : 50; var tickArr = new Array(scaleNum).fill(0).map(function (item, idx) { return numberDecimal(0 + idx * 100 / (scaleNum - 1)); }); var tickMarks = tickArr.map(function (value, index) { // 根据圆环划分段数,计算对应的角度 var angleValue = angle.start + (angle.end - angle.start) * value / 100; var textOffset = angle.end - angle.start > 180 ? strokeWidth : decorationGap + decorationStrokeWidth; var innerPos = calculatePositionOnCircle(angleValue, radius, textOffset); var outerPos = calculatePositionOnCircle(angleValue, radius, textOffset + realScaleLineLength); if (value === 0 || value === 100) { innerPos.y -= 1; outerPos.y -= 1; } var textPos = calculatePositionOnCircle(angleValue, radius, textOffset + realScaleLineLength * (scale ? 2 : 1) + gaugeTextSize / 3); var renderText = /*#__PURE__*/React.createElement("text", { x: textPos.x, y: textPos.y, className: angle.end - angle.start > 180 ? prefix + "-scale-num-big" : prefix + "-scale-num", style: gaugeTextStyle, textAnchor: "middle", alignmentBaseline: "middle" }, value); return /*#__PURE__*/React.createElement(React.Fragment, null, scale && /*#__PURE__*/React.createElement("line", { key: index, x1: innerPos.x, y1: innerPos.y, x2: outerPos.x, y2: outerPos.y, style: gaugeLineStyle, className: prefix + "-scale" }), scaleNum > 8 ? scaleNum % 2 !== 0 ? index % 2 === 0 && renderText : renderText : renderText); }); var textOffset = viewBoxHeightWithDecorations - startY + viewBoxDecoratedY - gaugeTextSize / 3; var viewBox = angle.end - angle.start > 180 ? viewBoxDecoratedX + " " + (viewBoxDecoratedY - strokeWidth - gaugeTextSize * 2 - realScaleLineLength) + " " + viewBoxWidthWithDecorations + " " + (viewBoxHeightWithDecorations + realScaleLineLength + gaugeTextSize + lineSize + Math.abs(Math.sin((angle.start - 180) * Math.PI / 180)) * (viewBoxHeightWithDecorations + realScaleLineLength + gaugeTextSize)) : viewBoxDecoratedX - realScaleLineLength * 2 - gaugeTextSize - 2 + " " + (viewBoxDecoratedY - strokeWidth / 3) + " " + (viewBoxWidthWithDecorations + realScaleLineLength * 4 + gaugeTextSize * 2) + " " + (viewBoxHeightWithDecorations - realScaleLineLength * 2 - gaugeTextSize); return /*#__PURE__*/React.createElement("div", { ref: containerRef, style: { alignItems: angle.end - angle.start > 180 ? 'center' : 'end' }, className: FullCrossName + " " + prefix + "-container" }, /*#__PURE__*/React.createElement("svg", { className: prefix + "-svg", width: viewBoxWidthWithDecorations, height: viewBoxHeightWithDecorations, viewBox: viewBox }, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", { id: "gradientLeft", x1: "0", y1: "0", x2: "0", y2: "1" }, Array.isArray(colors1) && colors1.map(function (_ref4) { var offset = _ref4[0], colorName = _ref4[1]; var numberOffset = offset === 'max' ? 100 : Math.min(1, Math.max(0, (offset - min) / (max - min))) * 100; var colorAngle = (angle.end - angle.start) * numberOffset / 100 - (angle.end - angle.start) / 2; var colorOffset = (1 - Math.cos(colorAngle * Math.PI / 180)) / (1 + Math.cos((180 - (angle.end - angle.start) / 2) * Math.PI / 180)); return /*#__PURE__*/React.createElement("stop", { key: colorName, offset: numberOffset === 0 ? 0 : colorOffset, stopColor: "var(--" + prefix + "-" + colorName + "-color)" }); })), /*#__PURE__*/React.createElement("linearGradient", { id: "gradientRight", x1: "0", y1: "0", x2: "0", y2: "1" }, Array.isArray(colors2) && colors2.map(function (_ref5) { var offset = _ref5[0], colorName = _ref5[1]; var numberOffset = offset === 'max' ? 100 : Math.min(1, Math.max(0, (offset - min) / (max - min))) * 100; var colorAngle = (angle.end - angle.start) * numberOffset / 100 - (angle.end - angle.start) / 2; var colorOffset = (1 - Math.cos(colorAngle * Math.PI / 180)) / (1 + Math.cos((180 - (angle.end - angle.start) / 2) * Math.PI / 180)); return /*#__PURE__*/React.createElement("stop", { key: colorName, offset: numberOffset === 0 ? 0 : colorOffset, stopColor: "var(--" + prefix + "-" + colorName + "-color)" }); })), /*#__PURE__*/React.createElement("linearGradient", { id: "gradient", x1: "0%", y1: "0%", x2: "100%", y2: "0%" }, Array.isArray(colors) ? colors.map(function (_ref6) { var offset = _ref6[0], colorName = _ref6[1]; var numberOffset = offset === 'max' ? 100 : Math.min(1, Math.max(0, (offset - min) / (max - min))) * 100; return /*#__PURE__*/React.createElement("stop", { key: colorName, offset: numberOffset + "%", stopColor: "var(--" + prefix + "-" + colorName + "-color)" }); }) : /*#__PURE__*/React.createElement("stop", { key: colors, offset: "100%", stopColor: "var(--" + prefix + "-" + colors + "-color)" }))), outRing && angle.end - angle.start > 180 && /*#__PURE__*/React.createElement("path", { d: pathRingDataLeft, fill: "none", stroke: "url(#gradientLeft)", strokeWidth: decorationStrokeWidth }), outRing && angle.end - angle.start > 180 && /*#__PURE__*/React.createElement("path", { d: pathRingDataRight, fill: "none", stroke: "url(#gradientRight)", strokeWidth: decorationStrokeWidth }), outRing && angle.end - angle.start <= 180 && /*#__PURE__*/React.createElement("path", { d: pathRingDataAll, fill: "none", stroke: "url(#gradient)", strokeWidth: decorationStrokeWidth }), /*#__PURE__*/React.createElement("path", { d: pathBottomData, fill: "none", className: prefix + "-path", strokeWidth: strokeWidth }), /*#__PURE__*/React.createElement("path", { d: pathData, fill: "none", stroke: strokeColor, strokeWidth: strokeWidth }), !gaugeScaleFlag ? tickMarks : /*#__PURE__*/React.createElement(React.Fragment, null)), /*#__PURE__*/React.createElement("div", { ref: textRef, className: prefix + "-text " + (gaugeScale || flag ? prefix + '-scale' : '') + " " + (flag && angle.end - angle.start <= 180 ? prefix + '-width-scale' : ''), style: { transform: angle.end - angle.start > 180 ? "translateY(50%)" : "translateY(-" + textOffset + "px)", color: fontColorFit && strokeColor } }, renderNum(), /*#__PURE__*/React.createElement("div", { className: prefix + "-label", ref: labelRef, style: _extends({ maxWidth: radius + "px", color: fontColorFit && strokeColor }, textStyle), title: label }, label), /*#__PURE__*/React.createElement(WidgetsTooltip, { ref: labelRef, content: label || '' }))); }; function calculatePositionOnCircle(angle, radius, offset) { if (offset === void 0) { offset = 0; } var radians = angle * Math.PI / 180; var x = radius + (radius + offset) * Math.cos(radians); var y = radius + (radius + offset) * Math.sin(radians); return { x: x, y: y }; } function getColorForCurrent(current, min, max, colors) { for (var i = 0; i < colors.length; i++) { var _colors$i = colors[i], threshold = _colors$i[0], colorName = _colors$i[1]; if (threshold === 'max' || current <= threshold) { return colorName; } } return colors[colors.length - 1][1]; } function transformColors(data, opts) { var _opts$split, _opts$offsetFrom; if (opts === void 0) { opts = {}; } var split = (_opts$split = opts.split) !== null && _opts$split !== void 0 ? _opts$split : 50; var offsetFrom = (_opts$offsetFrom = opts.offsetFrom) !== null && _opts$offsetFrom !== void 0 ? _opts$offsetFrom : 'edge'; // 假设整体范围以 data 最后一个值为最大值、0 为最小值 var arr = data.slice().sort(function (a, b) { return a[0] - b[0]; }); var domainMin = 0; var domainMax = arr[arr.length - 1][0]; // 构造区间:prev..value 区间用当前颜色 var intervals = []; var prev = domainMin; for (var i = 0; i < arr.length; i++) { var _arr$i = arr[i], value = _arr$i[0], color = _arr$i[1]; if (value > prev) intervals.push({ start: prev, end: value, color: color }); prev = value; } // 切分到左右两半 var leftPieces = []; var rightPieces = []; for (var _i = 0, _intervals = intervals; _i < _intervals.length; _i++) { var seg = _intervals[_i]; var start = seg.start, end = seg.end, _color = seg.color; if (end <= split) { leftPieces.push({ start: start, end: end, color: _color }); } else if (start >= split) { rightPieces.push({ start: start, end: end, color: _color }); } else { leftPieces.push({ start: start, end: split, color: _color }); rightPieces.push({ start: split, end: end, color: _color }); } } // 排序:从中间往外 leftPieces.sort(function (a, b) { return b.end - a.end; }); // 左半:end 越大越靠近中间 rightPieces.sort(function (a, b) { return a.start - b.start; }); // 右半:start 越小越靠近中间 function buildStops(pieces, side) { if (pieces.length === 0) return []; var stops = []; // 起点颜色(靠近 50 的那段) stops.push([0, pieces[0].color]); if (offsetFrom === 'center') { // 从中间向外的累计距离 var cum = 0; for (var _i2 = 0; _i2 < pieces.length - 1; _i2++) { var len = pieces[_i2].end - pieces[_i2].start; cum += len; stops.push([cum, pieces[_i2 + 1].color]); } } else { // 从两端算起的距离 for (var _i3 = 0; _i3 < pieces.length - 1; _i3++) { // 左半的边界取 pieces[i].start;右半的边界取 pieces[i].end var boundaryVal = side === 'left' ? pieces[_i3].start : pieces[_i3].end; var offset = side === 'left' ? boundaryVal - domainMin : domainMax - boundaryVal; stops.push([offset, pieces[_i3 + 1].color]); } } return stops; } var colors1 = buildStops(leftPieces, 'left'); // 左半(从中间到左侧) var colors2 = buildStops(rightPieces, 'right'); // 右半(从中间到右侧) return [colors1, colors2]; } function getFontSizeNumber(style) { if (style && style.hasOwnProperty('fontSize')) { return parseInt(style.fontSize, 10) <= 12 ? 0 : parseInt(style.fontSize, 10); } return 0; } export default Wgauge;