UNPKG

@antv/g-svg

Version:

A renderer implemented by SVG

1,268 lines (1,230 loc) 95.2 kB
/*! * @antv/g-svg * @description A renderer implemented by SVG * @version 2.1.1 * @date 12/24/2025, 11:57:09 AM * @author AntVis * @docs https://g.antv.antgroup.com/ */ 'use strict'; var _createClass = require('@babel/runtime/helpers/createClass'); var _classCallCheck = require('@babel/runtime/helpers/classCallCheck'); var _callSuper = require('@babel/runtime/helpers/callSuper'); var _inherits = require('@babel/runtime/helpers/inherits'); var gLite = require('@antv/g-lite'); var util = require('@antv/util'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var glMatrix = require('gl-matrix'); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _createForOfIteratorHelper = require('@babel/runtime/helpers/createForOfIteratorHelper'); var _regeneratorRuntime = require('@babel/runtime/helpers/regeneratorRuntime'); var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator'); var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties'); var ElementSVG = /*#__PURE__*/_createClass(function ElementSVG() { _classCallCheck(this, ElementSVG); }); ElementSVG.tag = 'c-svg-element'; function updateImageElementAttribute($el, parsedStyle) { var _parsedStyle$src = parsedStyle.src, src = _parsedStyle$src === void 0 ? '' : _parsedStyle$src, _parsedStyle$x = parsedStyle.x, x = _parsedStyle$x === void 0 ? 0 : _parsedStyle$x, _parsedStyle$y = parsedStyle.y, y = _parsedStyle$y === void 0 ? 0 : _parsedStyle$y, width = parsedStyle.width, height = parsedStyle.height; $el.setAttribute('x', "".concat(x)); $el.setAttribute('y', "".concat(y)); if (util.isString(src)) { $el.setAttribute('href', src); } else if (src instanceof Image) { if (!width) { $el.setAttribute('width', "".concat(src.width)); } if (!height) { $el.setAttribute('height', "".concat(src.height)); } $el.setAttribute('href', src.src); } else if ( // @ts-ignore src instanceof HTMLElement && util.isString(src.nodeName) && src.nodeName.toUpperCase() === 'CANVAS') { $el.setAttribute('href', src.toDataURL()); // @ts-ignore } else if (src instanceof ImageData) { var canvas = document.createElement('canvas'); // @ts-ignore canvas.setAttribute('width', "".concat(src.width)); // @ts-ignore canvas.setAttribute('height', "".concat(src.height)); var context = canvas.getContext('2d'); if (context) { context.putImageData(src, 0, 0); if (!width) { // @ts-ignore $el.setAttribute('width', "".concat(src.width)); } if (!height) { // @ts-ignore $el.setAttribute('height', "".concat(src.height)); } $el.setAttribute('href', canvas.toDataURL()); } } } function updateLineElementAttribute($el, parsedStyle) { var x1 = parsedStyle.x1, y1 = parsedStyle.y1, x2 = parsedStyle.x2, y2 = parsedStyle.y2, markerStart = parsedStyle.markerStart, markerEnd = parsedStyle.markerEnd, markerStartOffset = parsedStyle.markerStartOffset, markerEndOffset = parsedStyle.markerEndOffset; var startOffsetX = 0; var startOffsetY = 0; var endOffsetX = 0; var endOffsetY = 0; var rad = 0; var x; var y; if (markerStart && gLite.isDisplayObject(markerStart) && markerStartOffset) { x = x2 - x1; y = y2 - y1; rad = Math.atan2(y, x); startOffsetX = Math.cos(rad) * (markerStartOffset || 0); startOffsetY = Math.sin(rad) * (markerStartOffset || 0); } if (markerEnd && gLite.isDisplayObject(markerEnd) && markerEndOffset) { x = x1 - x2; y = y1 - y2; rad = Math.atan2(y, x); endOffsetX = Math.cos(rad) * (markerEndOffset || 0); endOffsetY = Math.sin(rad) * (markerEndOffset || 0); } // @see https://github.com/antvis/g/issues/1038 $el.setAttribute('x1', "".concat(x1 + startOffsetX)); $el.setAttribute('y1', "".concat(y1 + startOffsetY)); $el.setAttribute('x2', "".concat(x2 + endOffsetX)); $el.setAttribute('y2', "".concat(y2 + endOffsetY)); } function updatePathElementAttribute($el, parsedStyle) { var d = parsedStyle.d, markerStart = parsedStyle.markerStart, markerEnd = parsedStyle.markerEnd, markerStartOffset = parsedStyle.markerStartOffset, markerEndOffset = parsedStyle.markerEndOffset; var startOffsetX = 0; var startOffsetY = 0; var endOffsetX = 0; var endOffsetY = 0; var rad = 0; var x; var y; if (markerStart && gLite.isDisplayObject(markerStart) && markerStartOffset) { var _getStartTangent = markerStart.parentNode.getStartTangent(), _getStartTangent2 = _slicedToArray(_getStartTangent, 2), p1 = _getStartTangent2[0], p2 = _getStartTangent2[1]; x = p1[0] - p2[0]; y = p1[1] - p2[1]; rad = Math.atan2(y, x); startOffsetX = Math.cos(rad) * (markerStartOffset || 0); startOffsetY = Math.sin(rad) * (markerStartOffset || 0); } if (markerEnd && gLite.isDisplayObject(markerEnd) && markerEndOffset) { var _getEndTangent = markerEnd.parentNode.getEndTangent(), _getEndTangent2 = _slicedToArray(_getEndTangent, 2), _p = _getEndTangent2[0], _p2 = _getEndTangent2[1]; x = _p[0] - _p2[0]; y = _p[1] - _p2[1]; rad = Math.atan2(y, x); endOffsetX = Math.cos(rad) * (markerEndOffset || 0); endOffsetY = Math.sin(rad) * (markerEndOffset || 0); } $el.setAttribute('d', gLite.translatePathToString(d.absolutePath, startOffsetX, startOffsetY, endOffsetX, endOffsetY)); } function updatePolylineElementAttribute($el, parsedStyle) { var points = parsedStyle.points.points, markerStart = parsedStyle.markerStart, markerStartOffset = parsedStyle.markerStartOffset, markerEnd = parsedStyle.markerEnd, markerEndOffset = parsedStyle.markerEndOffset; var length = points.length; if (points && length >= 2) { var startOffsetX = 0; var startOffsetY = 0; var endOffsetX = 0; var endOffsetY = 0; var rad = 0; var x; var y; if (markerStart && gLite.isDisplayObject(markerStart) && markerStartOffset) { x = points[1][0] - points[0][0]; y = points[1][1] - points[0][1]; rad = Math.atan2(y, x); startOffsetX = Math.cos(rad) * (markerStartOffset || 0); startOffsetY = Math.sin(rad) * (markerStartOffset || 0); } if (markerEnd && gLite.isDisplayObject(markerEnd) && markerEndOffset) { x = points[length - 2][0] - points[length - 1][0]; y = points[length - 2][1] - points[length - 1][1]; rad = Math.atan2(y, x); endOffsetX = Math.cos(rad) * (markerEndOffset || 0); endOffsetY = Math.sin(rad) * (markerEndOffset || 0); } $el.setAttribute('points', points.map(function (point, i) { var offsetX = 0; var offsetY = 0; if (i === 0) { offsetX = startOffsetX; offsetY = startOffsetY; } else if (i === length - 1) { offsetX = endOffsetX; offsetY = endOffsetY; } return "".concat(point[0] + offsetX, ",").concat(point[1] + offsetY); }).join(' ')); } } function updateRectElementAttribute($el, parsedStyle) { var radius = parsedStyle.radius, _parsedStyle$x = parsedStyle.x, x = _parsedStyle$x === void 0 ? 0 : _parsedStyle$x, _parsedStyle$y = parsedStyle.y, y = _parsedStyle$y === void 0 ? 0 : _parsedStyle$y, width = parsedStyle.width, height = parsedStyle.height; // CSSKeyword: auto if (!isFinite(width) || !isFinite(height)) { return; } var hasRadius = radius && radius.some(function (r) { return r !== 0; }); var d = ''; if (!hasRadius) { d = "M ".concat(x, ",").concat(y, " l ").concat(width, ",0 l 0,").concat(height, " l").concat(-width, " 0 z"); } else { var _radius$map = radius.map(function (r) { return util.clamp(r, 0, Math.min(Math.abs(width) / 2, Math.abs(height) / 2)); }), _radius$map2 = _slicedToArray(_radius$map, 4), tlr = _radius$map2[0], trr = _radius$map2[1], brr = _radius$map2[2], blr = _radius$map2[3]; var signX = width > 0 ? 1 : -1; var signY = height > 0 ? 1 : -1; // sweep-flag @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths#arcs var sweepFlag = signX + signY !== 0 ? 1 : 0; d = [["M ".concat(signX * tlr + x, ",").concat(y)], ["l ".concat(width - signX * (tlr + trr), ",0")], ["a ".concat(trr, ",").concat(trr, ",0,0,").concat(sweepFlag, ",").concat(signX * trr, ",").concat(signY * trr)], ["l 0,".concat(height - signY * (trr + brr))], ["a ".concat(brr, ",").concat(brr, ",0,0,").concat(sweepFlag, ",").concat(-signX * brr, ",").concat(signY * brr)], ["l ".concat(signX * (brr + blr) - width, ",0")], ["a ".concat(blr, ",").concat(blr, ",0,0,").concat(sweepFlag, ",").concat(-signX * blr, ",").concat(-signY * blr)], ["l 0,".concat(signY * (blr + tlr) - height)], ["a ".concat(tlr, ",").concat(tlr, ",0,0,").concat(sweepFlag, ",").concat(signX * tlr, ",").concat(-signY * tlr)], ['z']].join(' '); } $el.setAttribute('d', d); } function createSVGElement(type, doc) { return (doc || document).createElementNS('http://www.w3.org/2000/svg', type); } var FILTER_PREFIX = 'g-filter-'; /** * use SVG filters, eg. blur, brightness, contrast... * @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/filter */ function createOrUpdateFilter(document, $def, object, $el, filters) { // eg. filter="url(#f1) url(#f2)" var filterName = FILTER_PREFIX + object.entity; var $existedFilters = $def.querySelectorAll("[name=".concat(filterName, "]")); if ($existedFilters.length) { $existedFilters.forEach(function ($filter) { $def.removeChild($filter); }); } if (filters.length === 0) { // 'none' $el === null || $el === void 0 || $el.removeAttribute('filter'); } else { var filterIds = filters.map(function (_ref, i) { var name = _ref.name, params = _ref.params; var $filter = createSVGElement('filter', document); // @see https://github.com/antvis/g/issues/1025 $filter.setAttribute('filterUnits', 'userSpaceOnUse'); if (name === 'blur') { createBlur(document, $filter, params); } else if (name === 'brightness') { createBrightness(document, $filter, params); } else if (name === 'drop-shadow') { createDropShadow(document, $filter, params); } else if (name === 'contrast') { createContrast(document, $filter, params); } else if (name === 'grayscale') { createGrayscale(document, $filter, params); } else if (name === 'sepia') { createSepia(document, $filter, params); } else if (name === 'saturate') { createSaturate(document, $filter, params); } else if (name === 'hue-rotate') { createHueRotate(document, $filter, params); } else if (name === 'invert') { createInvert(document, $filter, params); } $filter.id = "".concat(filterName, "-").concat(i); $filter.setAttribute('name', filterName); $def.appendChild($filter); return $filter.id; }); // @see https://github.com/antvis/G/issues/1114 setTimeout(function () { $el === null || $el === void 0 || $el.setAttribute('filter', filterIds.map(function (filterId) { return "url(#".concat(filterId, ")"); }).join(' ')); }); } } function convertToAbsoluteValue(param) { return param.unit === gLite.UnitType.kPercentage ? param.value / 100 : param.value; } /** * @see https://drafts.fxtf.org/filter-effects/#blurEquivalent */ function createBlur(document, $filter, params) { var $feGaussianBlur = createSVGElement('feGaussianBlur', document); $feGaussianBlur.setAttribute('in', 'SourceGraphic'); $feGaussianBlur.setAttribute('stdDeviation', "".concat(params[0].value)); $filter.appendChild($feGaussianBlur); } function createFeComponentTransfer(document, $filter, _ref2) { var type = _ref2.type, slope = _ref2.slope, intercept = _ref2.intercept, tableValues = _ref2.tableValues; var $feComponentTransfer = createSVGElement('feComponentTransfer', document); [createSVGElement('feFuncR', document), createSVGElement('feFuncG', document), createSVGElement('feFuncB', document)].forEach(function ($feFunc) { $feFunc.setAttribute('type', type); if (type === 'table') { $feFunc.setAttribute('tableValues', "".concat(tableValues)); } else { $feFunc.setAttribute('slope', "".concat(slope)); $feFunc.setAttribute('intercept', "".concat(intercept)); } $feComponentTransfer.appendChild($feFunc); }); $filter.appendChild($feComponentTransfer); } function createContrast(document, $filter, params) { var slope = convertToAbsoluteValue(params[0]); createFeComponentTransfer(document, $filter, { type: 'linear', slope: slope, intercept: -(0.5 * slope) + 0.5 }); } function createInvert(document, $filter, params) { var amount = convertToAbsoluteValue(params[0]); createFeComponentTransfer(document, $filter, { type: 'table', tableValues: "".concat(amount, " ").concat(1 - amount) }); } function createBrightness(document, $filter, params) { var slope = convertToAbsoluteValue(params[0]); createFeComponentTransfer(document, $filter, { type: 'linear', slope: slope, intercept: 0 }); } function createSaturate(document, $filter, params) { var amount = convertToAbsoluteValue(params[0]); var $feColorMatrix = createSVGElement('feColorMatrix', document); $feColorMatrix.setAttribute('type', 'saturate'); $feColorMatrix.setAttribute('values', "".concat(amount)); $filter.appendChild($feColorMatrix); } function createHueRotate(document, $filter, params) { var $feColorMatrix = createSVGElement('feColorMatrix', document); $feColorMatrix.setAttribute('type', 'hueRotate'); // $feColorMatrix.setAttribute('values', `${params[0].to(UnitType.kDegrees).value}`); // FIXME: convert to degrees $feColorMatrix.setAttribute('values', "".concat(params[0].value)); $filter.appendChild($feColorMatrix); } function createDropShadow(document, $filter, params) { var shadowOffsetX = params[0].value; var shadowOffsetY = params[1].value; var shadowBlur = params[2].value; // @ts-ignore var shadowColor = params[3].formatted; var $feGaussianBlur = createSVGElement('feGaussianBlur', document); $feGaussianBlur.setAttribute('in', 'SourceAlpha'); $feGaussianBlur.setAttribute('stdDeviation', "".concat(shadowBlur)); $filter.appendChild($feGaussianBlur); var $feOffset = createSVGElement('feOffset', document); $feOffset.setAttribute('dx', "".concat(shadowOffsetX)); $feOffset.setAttribute('dy', "".concat(shadowOffsetY)); $feOffset.setAttribute('result', 'offsetblur'); $filter.appendChild($feOffset); var $feFlood = createSVGElement('feFlood', document); $feFlood.setAttribute('flood-color', shadowColor); $filter.appendChild($feFlood); var $feComposite = createSVGElement('feComposite', document); $feComposite.setAttribute('in2', 'offsetblur'); $feComposite.setAttribute('operator', 'in'); $filter.appendChild($feComposite); var $feMerge = createSVGElement('feMerge', document); $filter.appendChild($feMerge); var $feMergeNode1 = createSVGElement('feMergeNode', document); var $feMergeNode2 = createSVGElement('feMergeNode', document); $feMergeNode2.setAttribute('in', 'SourceGraphic'); $feMerge.appendChild($feMergeNode1); $feMerge.appendChild($feMergeNode2); } function createFeColorMatrix(document, $filter, matrix) { var $feColorMatrix = createSVGElement('feColorMatrix', document); $feColorMatrix.setAttribute('type', 'matrix'); $feColorMatrix.setAttribute('values', matrix.join(' ')); $filter.appendChild($feColorMatrix); } /** * @see https://drafts.fxtf.org/filter-effects/#grayscaleEquivalent */ function createGrayscale(document, $filter, params) { var amount = convertToAbsoluteValue(params[0]); createFeColorMatrix(document, $filter, [0.2126 + 0.7874 * (1 - amount), 0.7152 - 0.7152 * (1 - amount), 0.0722 - 0.0722 * (1 - amount), 0, 0, 0.2126 - 0.2126 * (1 - amount), 0.7152 + 0.2848 * (1 - amount), 0.0722 - 0.0722 * (1 - amount), 0, 0, 0.2126 - 0.2126 * (1 - amount), 0.7152 - 0.7152 * (1 - amount), 0.0722 + 0.9278 * (1 - amount), 0, 0, 0, 0, 0, 1, 0]); } /** * @see https://drafts.fxtf.org/filter-effects/#sepiaEquivalent */ function createSepia(document, $filter, params) { var amount = convertToAbsoluteValue(params[0]); createFeColorMatrix(document, $filter, [0.393 + 0.607 * (1 - amount), 0.769 - 0.769 * (1 - amount), 0.189 - 0.189 * (1 - amount), 0, 0, 0.349 - 0.349 * (1 - amount), 0.686 + 0.314 * (1 - amount), 0.168 - 0.168 * (1 - amount), 0, 0, 0.272 - 0.272 * (1 - amount), 0.534 - 0.534 * (1 - amount), 0.131 + 0.869 * (1 - amount), 0, 0, 0, 0, 0, 1, 0]); } var PATTERN_PREFIX = 'g-pattern-'; var cacheKey2IDMap = {}; var counter = 0; function resetPatternCounter() { counter = 0; cacheKey2IDMap = {}; } function createOrUpdateGradientAndPattern(document, $def, object, $el, parsedColor, name, createImage, plugin) { // eg. clipPath don't have fill/stroke if (!parsedColor) { return ''; } if (gLite.isCSSRGB(parsedColor)) { // keep using currentColor @see https://github.com/d3/d3-axis/issues/49 if (object.style[name] === 'currentColor') { $el === null || $el === void 0 || $el.setAttribute(name, 'currentColor'); } else { // constant value, eg. '#fff' $el === null || $el === void 0 || $el.setAttribute(name, parsedColor.isNone ? 'none' : parsedColor.toString()); } } else if (gLite.isPattern(parsedColor)) { var patternId = createOrUpdatePattern(document, $def, object, parsedColor, createImage, plugin); // use style instead of attribute when applying <pattern> // @see https://stackoverflow.com/a/7723115 $el.style[name] = "url(#".concat(patternId, ")"); return patternId; } else { if (parsedColor.length === 1) { var gradientId = createOrUpdateGradient(document, object, $def, $el, parsedColor[0]); $el === null || $el === void 0 || $el.setAttribute(name, "url(#".concat(gradientId, ")")); return gradientId; } // @see https://stackoverflow.com/questions/20671502/can-i-blend-gradients-in-svg var filterId = createOrUpdateMultiGradient(document, object, $def, $el, parsedColor); $el === null || $el === void 0 || $el.setAttribute('filter', "url(#".concat(filterId, ")")); $el === null || $el === void 0 || $el.setAttribute('fill', 'black'); return filterId; } return ''; } function generateCacheKey(src) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var cacheKey = ''; if (gLite.isCSSGradientValue(src)) { var type = src.type, value = src.value; if (type === gLite.GradientType.LinearGradient || type === gLite.GradientType.RadialGradient) { // @ts-ignore var _value$options = _objectSpread(_objectSpread({}, value), options), _type = _value$options.type, x = _value$options.x, y = _value$options.y, width = _value$options.width, height = _value$options.height, steps = _value$options.steps, angle = _value$options.angle, cx = _value$options.cx, cy = _value$options.cy, size = _value$options.size; cacheKey = "gradient-".concat(_type, "-").concat((x === null || x === void 0 ? void 0 : x.toString()) || 0, "-").concat((y === null || y === void 0 ? void 0 : y.toString()) || 0, "-").concat((angle === null || angle === void 0 ? void 0 : angle.toString()) || 0, "-").concat((cx === null || cx === void 0 ? void 0 : cx.toString()) || 0, "-").concat((cy === null || cy === void 0 ? void 0 : cy.toString()) || 0, "-").concat((size === null || size === void 0 ? void 0 : size.toString()) || 0, "-").concat(width, "-").concat(height, "-").concat(steps.map(function (_ref) { var offset = _ref.offset, color = _ref.color; return "".concat(offset).concat(color); }).join('-')); } } else if (gLite.isPattern(src)) { if (util.isString(src.image)) { cacheKey = "pattern-".concat(src.image, "-").concat(src.repetition); } else if (src.image.nodeName === 'rect') { // use rect's entity as key cacheKey = "pattern-rect-".concat(src.image.entity); } else { cacheKey = "pattern-".concat(counter); } } if (cacheKey) { if (!cacheKey2IDMap[cacheKey]) { cacheKey2IDMap[cacheKey] = "".concat(PATTERN_PREFIX).concat(counter++); } } return cacheKey2IDMap[cacheKey]; } function formatTransform(transform) { // @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternTransform // should remove unit: rotate(20deg) -> rotate(20) return gLite.parseTransform(transform).map(function (parsed) { var t = parsed.t, d = parsed.d; if (t === 'translate') { return "translate(".concat(d[0].value, " ").concat(d[1].value, ")"); } if (t === 'translateX') { return "translate(".concat(d[0].value, " 0)"); } if (t === 'translateY') { return "translate(0 ".concat(d[0].value, ")"); } if (t === 'rotate') { return "rotate(".concat(d[0].value, ")"); } if (t === 'scale') { // scale(1) scale(1, 1) var newScale = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [1, 1]; return "scale(".concat(newScale[0], ", ").concat(newScale[1], ")"); } if (t === 'scaleX') { var _newScale = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [1]; return "scale(".concat(_newScale[0], ", 1)"); } if (t === 'scaleY') { var _newScale2 = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [1]; return "scale(1, ".concat(_newScale2[0], ")"); } if (t === 'skew') { var newSkew = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [0, 0]; return "skewX(".concat(newSkew[0], ") skewY(").concat(newSkew[1], ")"); } if (t === 'skewZ') { var _newSkew = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [0]; return "skewX(".concat(_newSkew[0], ")"); } if (t === 'skewY') { var _newSkew2 = (d === null || d === void 0 ? void 0 : d.map(function (s) { return s.value; })) || [0]; return "skewY(".concat(_newSkew2[0], ")"); } if (t === 'matrix') { var _d$map = d.map(function (s) { return s.value; }), _d$map2 = _slicedToArray(_d$map, 6), a = _d$map2[0], b = _d$map2[1], c = _d$map2[2], dd = _d$map2[3], tx = _d$map2[4], ty = _d$map2[5]; return "matrix(".concat(a, " ").concat(b, " ").concat(c, " ").concat(dd, " ").concat(tx, " ").concat(ty, ")"); } return null; }).filter(function (item) { return item !== null; }).join(' '); } function create$Pattern(document, $def, object, pattern, patternId, width, height) { var repetition = pattern.repetition, transform = pattern.transform; // @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/pattern var $pattern = createSVGElement('pattern', document); if (transform) { $pattern.setAttribute('patternTransform', formatTransform(transform)); } $pattern.setAttribute('patternUnits', 'userSpaceOnUse'); $pattern.id = patternId; $def.appendChild($pattern); var _object$getGeometryBo = object.getGeometryBounds(), halfExtents = _object$getGeometryBo.halfExtents, min = _object$getGeometryBo.min; $pattern.setAttribute('x', "".concat(min[0])); $pattern.setAttribute('y', "".concat(min[1])); // There is no equivalent to CSS no-repeat for SVG patterns // @see https://stackoverflow.com/a/33481956 var patternWidth = width; var patternHeight = height; if (repetition === 'repeat-x') { patternHeight = halfExtents[1] * 2; } else if (repetition === 'repeat-y') { patternWidth = halfExtents[0] * 2; } else if (repetition === 'no-repeat') { patternWidth = halfExtents[0] * 2; patternHeight = halfExtents[1] * 2; } $pattern.setAttribute('width', "".concat(patternWidth)); $pattern.setAttribute('height', "".concat(patternHeight)); return $pattern; } function createOrUpdatePattern(document, $def, object, pattern, createImage, plugin) { var patternId = generateCacheKey(pattern); var $existed = $def.querySelector("#".concat(patternId)); if (!$existed) { var image = pattern.image; var imageURL = ''; if (util.isString(image)) { imageURL = image; } else if (gLite.isBrowser) { if (image instanceof HTMLImageElement) { imageURL = image.src; } else if (image instanceof HTMLCanvasElement) { imageURL = image.toDataURL(); } else ; } if (imageURL) { var $image = createSVGElement('image', document); // use href instead of xlink:href // @see https://stackoverflow.com/a/13379007 $image.setAttribute('href', imageURL); var img = createImage(); if (!imageURL.match(/^data:/i)) { img.crossOrigin = 'Anonymous'; $image.setAttribute('crossorigin', 'anonymous'); } img.src = imageURL; var onload = function onload() { var $pattern = create$Pattern(document, $def, object, pattern, patternId, img.width, img.height); $def.appendChild($pattern); $pattern.appendChild($image); $image.setAttribute('x', '0'); $image.setAttribute('y', '0'); $image.setAttribute('width', "".concat(img.width)); $image.setAttribute('height', "".concat(img.height)); }; if (img.complete) { onload(); } else { img.onload = onload; // Fix onload() bug in IE9 // img.src = img.src; } } if (image.nodeName === 'rect') { var _parsedStyle = image.parsedStyle, width = _parsedStyle.width, height = _parsedStyle.height; var $pattern = create$Pattern(document, $def, image, pattern, patternId, width, height); // traverse subtree of pattern image.forEach(function (object) { plugin.createSVGDom(document, object, null); // @ts-ignore var svgElement = object.elementSVG; // apply local RTS transformation to <group> wrapper var localTransform = object.getLocalTransform(); plugin.applyTransform(svgElement.$groupEl, localTransform); }); // @ts-ignore var svgElement = image.elementSVG; $pattern.appendChild(svgElement.$groupEl); } } return patternId; } function createOrUpdateGradient(document, object, $def, $el, parsedColor) { var bounds = object.getGeometryBounds(); var width = bounds && bounds.halfExtents[0] * 2 || 0; var height = bounds && bounds.halfExtents[1] * 2 || 0; var min = bounds && bounds.min || [0, 0]; var gradientId = generateCacheKey(parsedColor, { x: min[0], y: min[1], width: width, height: height }); var $existed = $def.querySelector("#".concat(gradientId)); if (!$existed) { // <linearGradient> <radialGradient> // @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/linearGradient // @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/radialGradient $existed = createSVGElement(parsedColor.type === gLite.GradientType.LinearGradient ? 'linearGradient' : 'radialGradient', document); // @see https://github.com/antvis/g/issues/1025 $existed.setAttribute('gradientUnits', 'userSpaceOnUse'); // add stops var innerHTML = ''; parsedColor.value.steps // sort by offset @see https://github.com/antvis/G/issues/1171 .sort(function (a, b) { return a.offset.value - b.offset.value; }).forEach(function (_ref2) { var offset = _ref2.offset, color = _ref2.color; // TODO: support absolute unit like `px` innerHTML += "<stop offset=\"".concat(offset.value / 100, "\" stop-color=\"").concat(color, "\"></stop>"); }); $existed.innerHTML = innerHTML; $existed.id = gradientId; $def.appendChild($existed); } if (parsedColor.type === gLite.GradientType.LinearGradient) { var _ref3 = parsedColor.value, angle = _ref3.angle; var _computeLinearGradien = gLite.computeLinearGradient([min[0], min[1]], width, height, angle), x1 = _computeLinearGradien.x1, y1 = _computeLinearGradien.y1, x2 = _computeLinearGradien.x2, y2 = _computeLinearGradien.y2; $existed.setAttribute('x1', "".concat(x1)); $existed.setAttribute('y1', "".concat(y1)); $existed.setAttribute('x2', "".concat(x2)); $existed.setAttribute('y2', "".concat(y2)); // $existed.setAttribute('gradientTransform', `rotate(${angle})`); } else { var _ref4 = parsedColor.value, cx = _ref4.cx, cy = _ref4.cy, size = _ref4.size; var _computeRadialGradien = gLite.computeRadialGradient([min[0], min[1]], width, height, cx, cy, size), x = _computeRadialGradien.x, y = _computeRadialGradien.y, r = _computeRadialGradien.r; $existed.setAttribute('cx', "".concat(x)); $existed.setAttribute('cy', "".concat(y)); $existed.setAttribute('r', "".concat(r)); } return gradientId; } function createOrUpdateMultiGradient(document, object, $def, $el, gradients) { var filterId = "".concat(FILTER_PREFIX + object.entity, "-gradient"); var $existed = $def.querySelector("#".concat(filterId)); if (!$existed) { $existed = createSVGElement('filter', document); $existed.setAttribute('filterUnits', 'userSpaceOnUse'); // @see https://github.com/antvis/g/issues/1025 $existed.setAttribute('x', '0%'); $existed.setAttribute('y', '0%'); $existed.setAttribute('width', '100%'); $existed.setAttribute('height', '100%'); $existed.id = filterId; $def.appendChild($existed); } /** * <rect id="wave-rect" x="0" y="0" width="100%" height="100%" fill="url(#wave)"></rect> * <filter id="blend-it" x="0%" y="0%" width="100%" height="100%"> <feImage xlink:href="#wave-rect" result="myWave" x="100" y="100"/> <feImage xlink:href="#ry-rect" result="myRY" x="100" y="100"/> <feBlend in="myWave" in2="myRY" mode="multiply" result="blendedGrad"/> <feComposite in="blendedGrad" in2="SourceGraphic" operator="in"/> </filter> */ var blended = 0; gradients.forEach(function (gradient, i) { var gradientId = createOrUpdateGradient(document, object, $def, $el, gradient); var rectId = "".concat(gradientId, "_rect"); var $rect = createSVGElement('rect', document); $rect.setAttribute('x', '0'); $rect.setAttribute('y', '0'); $rect.setAttribute('width', '100%'); $rect.setAttribute('height', '100%'); $rect.setAttribute('fill', "url(#".concat(gradientId, ")")); $rect.id = rectId; $def.appendChild($rect); var $feImage = createSVGElement('feImage', document); $feImage.setAttribute('href', "#".concat(rectId)); $feImage.setAttribute('result', "".concat(filterId, "-").concat(i)); $existed.appendChild($feImage); if (i > 0) { var $feBlend = createSVGElement('feBlend', document); $feBlend.setAttribute('in', i === 1 ? "".concat(filterId, "-").concat(i - 1) : "".concat(filterId, "-blended-").concat(blended - 1)); $feBlend.setAttribute('in2', "".concat(filterId, "-").concat(i)); $feBlend.setAttribute('result', "".concat(filterId, "-blended-").concat(blended++)); // @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/blend-mode $feBlend.setAttribute('mode', 'multiply'); $existed.appendChild($feBlend); } }); var $feComposite = createSVGElement('feComposite', document); $feComposite.setAttribute('in', "".concat(filterId, "-blended-").concat(blended)); $feComposite.setAttribute('in2', 'SourceGraphic'); $feComposite.setAttribute('operator', 'in'); $existed.appendChild($feComposite); return filterId; } var FILTER_DROPSHADOW_PREFIX = 'g-filter-dropshadow-'; /** * use SVG filters * @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/filter */ function createOrUpdateShadow(document, $def, object, $el, name) { var _object$parsedStyle = object.parsedStyle, _object$parsedStyle$s = _object$parsedStyle.shadowType, shadowType = _object$parsedStyle$s === void 0 ? 'outer' : _object$parsedStyle$s, shadowBlur = _object$parsedStyle.shadowBlur, shadowColor = _object$parsedStyle.shadowColor, shadowOffsetX = _object$parsedStyle.shadowOffsetX, shadowOffsetY = _object$parsedStyle.shadowOffsetY; var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0; var shadowId = FILTER_DROPSHADOW_PREFIX + object.entity; var $existedFilter = $def.querySelector("#".concat(shadowId)); if ($existedFilter) { var existedShadowType = $existedFilter.getAttribute('data-type'); if (existedShadowType !== shadowType || !hasShadow) { // remove existed shadow $existedFilter.remove(); $existedFilter = null; } } // <Group> also has shadowType as its default value // only apply shadow when blur > 0 if (hasShadow) { // use filter <feDropShadow> // @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow $el === null || $el === void 0 || $el.setAttribute('filter', "url(#".concat(shadowId, ")")); } else { $el === null || $el === void 0 || $el.removeAttribute('filter'); return; } if (!$existedFilter) { $existedFilter = createSVGElement('filter', document); $existedFilter.setAttribute('data-type', shadowType); if (shadowType === 'outer') { var $feDropShadow = createSVGElement('feDropShadow', document); $feDropShadow.setAttribute('dx', "".concat((shadowOffsetX || 0) / 2)); $feDropShadow.setAttribute('dy', "".concat((shadowOffsetY || 0) / 2)); $feDropShadow.setAttribute('stdDeviation', "".concat((shadowBlur || 0) / 4)); $feDropShadow.setAttribute('flood-color', shadowColor.toString()); $existedFilter.appendChild($feDropShadow); } else if (shadowType === 'inner') { var $feComponentTransfer = createSVGElement('feComponentTransfer', document); $feComponentTransfer.setAttribute('in', 'SourceAlpha'); var $feFuncA = createSVGElement('feFuncA', document); $feFuncA.setAttribute('type', 'table'); $feFuncA.setAttribute('tableValues', '1 0'); $feComponentTransfer.appendChild($feFuncA); $existedFilter.appendChild($feComponentTransfer); var $feGaussianBlur = createSVGElement('feGaussianBlur', document); $feGaussianBlur.setAttribute('stdDeviation', "".concat((shadowBlur || 0) / 4)); $existedFilter.appendChild($feGaussianBlur); var $feOffset = createSVGElement('feOffset', document); $feOffset.setAttribute('dx', "".concat((shadowOffsetX || 0) / 2)); $feOffset.setAttribute('dy', "".concat((shadowOffsetY || 0) / 2)); $feOffset.setAttribute('result', 'offsetblur'); $existedFilter.appendChild($feOffset); var $feFlood = createSVGElement('feFlood', document); $feFlood.setAttribute('flood-color', shadowColor.toString()); $feFlood.setAttribute('result', 'color'); $existedFilter.appendChild($feFlood); var $feComposite = createSVGElement('feComposite', document); $feComposite.setAttribute('in2', 'offsetblur'); $feComposite.setAttribute('operator', 'in'); $existedFilter.appendChild($feComposite); var $feComposite2 = createSVGElement('feComposite', document); $feComposite2.setAttribute('in2', 'SourceAlpha'); $feComposite2.setAttribute('operator', 'in'); $existedFilter.appendChild($feComposite2); var $feMerge = createSVGElement('feMerge', document); $existedFilter.appendChild($feMerge); var $feMergeNode = createSVGElement('feMergeNode', document); $feMergeNode.setAttribute('in', 'SourceGraphic'); var $feMergeNode2 = createSVGElement('feMergeNode', document); $feMerge.appendChild($feMergeNode); $feMerge.appendChild($feMergeNode2); } $existedFilter.id = shadowId; // @see https://github.com/antvis/g/issues/1025 $existedFilter.setAttribute('filterUnits', 'userSpaceOnUse'); $def.appendChild($existedFilter); return; } if (shadowType === 'inner') { var _$feGaussianBlur = $existedFilter.children[1]; var _$feOffset = $existedFilter.children[2]; var _$feFlood = $existedFilter.children[3]; if (name === 'shadowColor') { _$feFlood.setAttribute('flood-color', shadowColor.toString()); } else if (name === 'shadowBlur') { // half the blur radius // @see https://drafts.csswg.org/css-backgrounds/#shadow-blur // @see https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/ _$feGaussianBlur.setAttribute('stdDeviation', "".concat((shadowBlur || 0) / 4)); } else if (name === 'shadowOffsetX') { _$feOffset.setAttribute('dx', "".concat((shadowOffsetX || 0) / 2)); } else if (name === 'shadowOffsetY') { _$feOffset.setAttribute('dy', "".concat((shadowOffsetY || 0) / 2)); } } else if (shadowType === 'outer') { var _$feDropShadow = $existedFilter.children[0]; if (name === 'shadowColor') { _$feDropShadow.setAttribute('flood-color', shadowColor.toString()); } else if (name === 'shadowBlur') { // half the blur radius // @see https://drafts.csswg.org/css-backgrounds/#shadow-blur // @see https://css-tricks.com/breaking-css-box-shadow-vs-drop-shadow/ _$feDropShadow.setAttribute('stdDeviation', "".concat((shadowBlur || 0) / 4)); } else if (name === 'shadowOffsetX') { _$feDropShadow.setAttribute('dx', "".concat((shadowOffsetX || 0) / 2)); } else if (name === 'shadowOffsetY') { _$feDropShadow.setAttribute('dy', "".concat((shadowOffsetY || 0) / 2)); } } } var urlRegexp = /url\("?#(.*)\)/; var DefElementManager = /*#__PURE__*/function () { function DefElementManager(context) { _classCallCheck(this, DefElementManager); this.gradientCache = {}; this.context = context; } /** * container for <gradient> <clipPath>... */ return _createClass(DefElementManager, [{ key: "getDefElement", value: function getDefElement() { return this.$def; } }, { key: "init", value: function init() { var document = this.context.config.document; var $svg = this.context.contextService.getContext(); this.$def = createSVGElement('defs', document); $svg.appendChild(this.$def); } }, { key: "clear", value: function clear(entity) { var _this = this; Object.keys(this.gradientCache).forEach(function (id) { _this.clearUnusedDefElement(_this.gradientCache, id, entity); }); } }, { key: "clearUnusedDefElement", value: function clearUnusedDefElement(cache, id, entity) { if (cache[id] && cache[id].size === 1 && cache[id].has(entity)) { var targetElement = this.$def.querySelector("#".concat(id)); if (targetElement) { this.$def.removeChild(targetElement); } } } }, { key: "createOrUpdateGradientAndPattern", value: function createOrUpdateGradientAndPattern$1(object, $el, parsedColor, name, plugin) { var _this$context$config = this.context.config, doc = _this$context$config.document, createImage = _this$context$config.createImage; if ($el) { var attributeValue = ''; if (gLite.isPattern(parsedColor)) { // `fill: url(#${patternId})` attributeValue = $el.style[name]; } else { // `url(#${gradientId})` attributeValue = $el.getAttribute(name) || ''; } var matches = attributeValue.match(urlRegexp); if (matches && matches.length > 1) { this.clearUnusedDefElement(this.gradientCache, matches[1].replace('"', ''), object.entity); } var newDefElementId = createOrUpdateGradientAndPattern(doc || document, this.$def, object, $el, parsedColor, name, createImage, plugin); if (newDefElementId) { if (!this.gradientCache[newDefElementId]) { this.gradientCache[newDefElementId] = new Set(); } this.gradientCache[newDefElementId].add(object.entity); } } } }, { key: "createOrUpdateShadow", value: function createOrUpdateShadow$1(object, $el, name) { var doc = this.context.config.document; createOrUpdateShadow(doc || document, this.$def, object, $el, name); } }, { key: "createOrUpdateFilter", value: function createOrUpdateFilter$1(object, $el, filters) { var doc = this.context.config.document; createOrUpdateFilter(doc || document, this.$def, object, $el, filters); } }]); }(); function numberToLongString(x) { return x.toFixed(6).replace('.000000', ''); } function convertHTML(str) { var regex = /[&|<|>|"|']/g; return str.replace(regex, function (match) { if (match === '&') { return '&amp;'; } if (match === '<') { return '&lt;'; } if (match === '>') { return '&gt;'; } if (match === '"') { return '&quot;'; } return '&apos;'; }); } var SVG_ATTR_MAP = { opacity: 'opacity', fillStyle: 'fill', fill: 'fill', fillRule: 'fill-rule', fillOpacity: 'fill-opacity', strokeStyle: 'stroke', strokeOpacity: 'stroke-opacity', stroke: 'stroke', clipPath: 'clip-path', textPath: 'text-path', r: 'r', cx: 'cx', cy: 'cy', rx: 'rx', ry: 'ry', x: 'x', y: 'y', width: 'width', height: 'height', lineCap: 'stroke-linecap', lineJoin: 'stroke-linejoin', lineWidth: 'stroke-width', lineDash: 'stroke-dasharray', lineDashOffset: 'stroke-dashoffset', miterLimit: 'stroke-miterlimit', font: 'font', fontSize: 'font-size', fontStyle: 'font-style', fontVariant: 'font-variant', fontWeight: 'font-weight', fontFamily: 'font-family', letterSpacing: 'letter-spacing', startArrow: 'marker-start', endArrow: 'marker-end', "class": 'class', id: 'id', // style: 'style', preserveAspectRatio: 'preserveAspectRatio', visibility: 'visibility', shadowColor: 'flood-color', shadowBlur: 'stdDeviation', shadowOffsetX: 'dx', shadowOffsetY: 'dy', filter: 'filter', innerHTML: 'innerHTML', textAlign: 'text-anchor', pointerEvents: 'pointer-events' }; var FORMAT_VALUE_MAP = { textAlign: { inherit: 'inherit', left: 'left', start: 'left', center: 'middle', right: 'end', end: 'end' } }; var DEFAULT_VALUE_MAP = { textAlign: 'inherit', // textBaseline: 'alphabetic', // @see https://www.w3.org/TR/SVG/painting.html#LineCaps lineCap: 'butt', // @see https://www.w3.org/TR/SVG/painting.html#LineJoin lineJoin: 'miter', // @see https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute/stroke-width lineWidth: '1px', opacity: '1', fillOpacity: '1', fillRule: 'nonzero', strokeOpacity: '1', strokeWidth: '0', strokeMiterLimit: '4', letterSpacing: '0', fontSize: 'inherit', fontFamily: 'inherit', pointerEvents: 'auto', transform: 'matrix(1,0,0,1,0,0)' }; /** * G_SVG_PREFIX + nodeName + entity * * eg. g_svg_circle_345 */ var G_SVG_PREFIX = 'g-svg'; var CLIP_PATH_PREFIX = 'clip-path-'; var TEXT_PATH_PREFIX = 'text-path-'; var SVGRendererPlugin = /*#__PURE__*/function () { function SVGRendererPlugin(pluginOptions, defElementManager, context) { _classCallCheck(this, SVGRendererPlugin); /** * Will be used in svg-picker for finding relative SVG element of current DisplayObject. */ this.svgElementMap = new WeakMap(); /** * render at the end of frame */ this.renderQueue = []; /** * dirty attributes at the end of frame */ this.dirtyAttributes = new WeakMap(); /** * reorder after mounted */ this.pendingReorderQueue = new Set(); /** * <use> elements in <clipPath>, which should be sync with clipPath * * @example * <clipPath transform="matrix(1,0,0,1,-100,-155)" id="clip-path-0-2"> * <use href="#g_svg_circle_0" transform="matrix(1.477115,0,0,1.477115,150,150)"> * </use> * </clipPath> */ this.clipPathUseMap = new WeakMap(); this.pluginOptions = pluginOptions; this.defElementManager = defElementManager; this.context = context; } return _createClass(SVGRendererPlugin, [{ key: "apply", value: function apply(context) { var _this = this; var renderingService = context.renderingService, renderingContext = context.renderingContext; this.context = context; // @ts-ignore this.context.svgElementMap = this.svgElementMap; var canvas = renderingContext.root.ownerDocument.defaultView; var document = this.context.config.document; var handleMounted = function handleMounted(e) { var object = e.target; // should remove clipPath already existed in <defs> var $useRefs = _this.clipPathUseMap.get(object); if ($useRefs) { var $def = _this.defElementManager.getDefElement(); var existed = $def.querySelector("#".concat(_this.getId(object))); if (existed) { existed.remove(); } } // create SVG DOM Node _this.createSVGDom(document, object, _this.$camera); }; var handleUnmounted = function handleUnmounted(e) { var object = e.target; _this.defElementManager.clear(object.entity); _this.clipPathUseMap["delete"](object); _this.removeSVGDom(object); }; var reorderChildren = function reorderChildren(object) { var _object$parentNode; var parent = object.parentNode; // @ts-ignore var $groupEl = (_object$parentNode = object.parentNode) === null || _object$parentNode === void 0 || (_object$parentNode = _object$parentNode.elementSVG) === null || _object$parentNode === void 0 ? void 0 : _object$parentNode.$groupEl; var children = ((parent === null || parent === void 0 ? void 0 : parent.children) || []).slice(); if ($groupEl) { _this.reorderChildren(document, $groupEl, children); } }; var handleReparent = function handleReparent(e) { var object = e.target; reorderChildren(object); }; var handleAttributeChanged = function handleAttributeChanged(e) { var object = e.target; // @see https://github.com/antvis/g/issues/994 // @ts-ignore if (!object.elementSVG) { return; } var attrName = e.attrName; var attribtues = _this.dirtyAttributes.get(object); if (!attribtues) { _this.dirtyAttributes.set(object, []); attribtues = _this.dirtyAttributes.get(object); } attribtues.push(attrName); }; var handleGeometryBoundsChanged = function handleGeometryBoundsChanged(e) { var records = e.detail; var _loop = function _loop() { var record = records[i]; var object = record.target; var nodes = object.nodeName === gLite.Shape.FRAGMENT ? object.childNodes : [object]; nodes.forEach(function (node) { var _object$elementSVG; // @ts-ignore var $el = (_object$elementSVG = object.elementSVG) === null || _object$elementSVG === void 0 ? void 0 : _object$elementSVG.$el; var _object$parsedStyle = object.parsedStyle, fill = _object$parsedStyle.fill, stroke = _object$parsedStyle.stroke, clipPath = _object$parsedStyle.clipPath; if (fill && !gLite.isCSSRGB(fill)) { _this.defElementManager.createOrUpdateGradientAndPattern(object, $el, fill, 'fill', _this); } if (stroke && !gLite.isCSSRGB(stroke)) { _this.defElementManager.createOrUpdateGradientAndPattern(object, $el, stroke, 'stroke', _this); } if (clipPath) { var parentInvert = glMatrix.mat4.invert(glMatrix.mat4.create(), object.getWorldTransform()); var clipPathId = "".concat(CLIP_PATH_PREFIX + clipPath.entity, "-").concat(object.entity); var $def = _this.defElementManager.getDefElement(); var $existed = $def.querySelector("#".concat(clipPathId)); if ($existed) { _this.applyTransform($existed, parentInvert); } } }); }; for (var i = 0; i < records.length; i++) { _loop(); } }; renderingService.hooks.init.tap(SVGRendererPlugin.tag, function () { var _this$context$config = _this.context.config, background = _this$context$config.background, document = _this$context$config.document; // <defs> _this.defElementManager.init(); var $svg = _this.context.contextService.getContext(); if (background) { $svg.style.background = background; } // @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/color-interpolation-filters $svg.setAttribute('color-interpolation-filters', 'sRGB'); _this.$camera = createSVGElement('g', document); _this.$camera.id = "".concat(G_SVG_PREFIX, "-camera"); _this.applyTransform(_this.$camera, _this.context.camera.getOrthoMatrix()); $svg.appendChild(_this.$camera); canvas.addEventListener(gLite.ElementEvent.MOUNTED, handleMounted); canvas.addEventListener(gLite.ElementEvent.UNMOUNTED, handleUnmounted); canvas.addEventListener(gLite.ElementEvent.REPARENT, handleReparent); canvas.addEventListener(gLite.ElementEvent.ATTR_MODIFIED, hand