- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在执行许多 D3.JS 操作,要求我使用 SVG 路径而不是基元/形状(折线、记录等)。
这个问题很普遍,但我想知道是否可以使用 D3 或其他脚本/库将任何 SVG 原语转换为路径。
作为引用,这里有一个针对折线执行此操作的链接:https://gist.github.com/andytlr/9283541
我想对每个原语都这样做。有任何想法吗?这可能吗?
最佳答案
您还可以使用 Jarek Foksa's path-data polyfill 转换所有基元:
它的主要目的是解析路径的d
属性到命令数组。
getPathData()
还可以从任何 SVGGeometryElement
检索pathData包括像 <rect>
这样的原语, <circle>
, <ellipse>
, <polygon>
.
const svgWrp = document.querySelector('.svgWrp');
const svg = document.querySelector('svg');
const primitives = svg.querySelectorAll('path, line, polyline, polygon, circle, rect');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = new XMLSerializer().serializeToString(svg);
function convertPrimitives(svg, primitives) {
primitives.forEach(function(primitive, i) {
/**
* get normalized path data:
* all coordinates are absolute;
* reduced set of commands: M, L, C, Z
*/
let pathData = primitive.getPathData();
//get all attributes
let attributes = [...primitive.attributes];
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
setAttributes(path, attributes, exclude);
// set d attribute from rounded pathData
path.setPathData(roundPathData(pathData, 1));
primitive.replaceWith(path);
})
// optional: output new svg markup
let newSvgMarkup = new XMLSerializer().serializeToString(svg);
svgMarkup.value = newSvgMarkup;
}
function roundPathData(pathData, decimals = 3) {
pathData.forEach(function(com, c) {
let values = com['values'];
values.forEach(function(val, v) {
pathData[c]['values'][v] = +val.toFixed(decimals);
})
})
return pathData;
}
function setAttributes(el, attributes, exclude = []) {
attributes.forEach(function(att, a) {
if (exclude.indexOf(att.nodeName) === -1) {
el.setAttribute(att.nodeName, att.nodeValue);
}
})
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<p><button type="button" onclick="convertPrimitives(svg, primitives)">Convert Primitives</button></p>
<div class="svgWrp">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 30">
<polygon id="polygon" fill="#ccc" stroke="green" points="9,22.4 4.1,14 9,5.5 18.8,5.5 23.7,14 18.8,22.4 " />
<polyline id="polyline" fill="none" stroke="red" points="43,22.4 33.3,22.4 28.4,14 33.3,5.5 43,5.5 47.9,14 " />
<rect id="rect" x="57.3" y="5.5" rx="2" ry="2" fill="none" stroke="orange" width="16.9" height="16.9" />
<line id="line" fill="none" stroke="purple" x1="52.6" y1="22.4" x2="52.6" y2="5.5" />
<circle class="circle" data-att="circle" id="circle" fill="none" stroke="magenta" cx="87.4" cy="14" r="8.5" />
<path transform="scale(0.9) translate(110,5)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z" fill="red" class="segment segment-1 segment-class" id="segment-01" />
</svg>
</div>
<h3>Svg markup</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>
getPathData()
还提供标准化方法,将任何元素的几何形状转换为一组精简的绝对命令 - 仅使用:
M
, L
, C
, Z
.
二次Q
或T
命令也被转换
弧线 A
和简写如 V
或H
)
element.getPathData({normalize: true});
const svgWrp = document.querySelector('.svgWrp');
const svg = document.querySelector('svg');
const primitives = svg.querySelectorAll('path, line, polyline, polygon, circle, rect');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = new XMLSerializer().serializeToString(svg);
function convertPrimitives(svg, primitives) {
primitives.forEach(function(primitive, i) {
/**
* get path data:
*/
let pathData = primitive.getPathData({normalize:true});
//get all attributes
let attributes = [...primitive.attributes];
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
setAttributes(path, attributes, exclude);
// set d attribute from rounded pathData
path.setPathData(roundPathData(pathData, 1));
primitive.replaceWith(path);
})
// optional: output new svg markup
let newSvgMarkup = new XMLSerializer().serializeToString(svg);
svgMarkup.value = newSvgMarkup;
}
function roundPathData(pathData, decimals = 3) {
pathData.forEach(function(com, c) {
let values = com['values'];
values.forEach(function(val, v) {
pathData[c]['values'][v] = +val.toFixed(decimals);
})
})
return pathData;
}
function setAttributes(el, attributes, exclude = []) {
attributes.forEach(function(att, a) {
if (exclude.indexOf(att.nodeName) === -1) {
el.setAttribute(att.nodeName, att.nodeValue);
}
})
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<p><button type="button" onclick="convertPrimitives(svg, primitives)">Convert Primitives</button></p>
<div class="svgWrp">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 30">
<polygon id="polygon" fill="#ccc" stroke="green" points="9,22.4 4.1,14 9,5.5 18.8,5.5 23.7,14 18.8,22.4 " />
<polyline id="polyline" fill="none" stroke="red" points="43,22.4 33.3,22.4 28.4,14 33.3,5.5 43,5.5 47.9,14 " />
<rect id="rect" x="57.3" y="5.5" rx="2" ry="2" fill="none" stroke="orange" width="16.9" height="16.9" />
<line id="line" fill="none" stroke="purple" x1="52.6" y1="22.4" x2="52.6" y2="5.5" />
<circle class="circle" data-att="circle" id="circle" fill="none" stroke="magenta" cx="87.4" cy="14" r="8.5" />
<path transform="scale(0.9) translate(110,5)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z" fill="red" class="segment segment-1 segment-class" id="segment-01" />
</svg>
</div>
<h3>Svg markup</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>
上面的示例脚本还将保留所有属性,例如 class
, id
, fill
等等
但它会删除像 r
这样的属性, cx
, rx
特定于基元。
不幸的是,getPathData()
和setPathData()
方法仍然是svg 2 drafts/proposals – 旨在替换已弃用的 pathSegList()
方法。
希望我们能在不久的将来获得 native 浏览器支持。
由于与更高级的 svg 库(如(snap.svg、d3 等))相比,这个 polyfill 仍然相当轻量(未压缩时约 12.5 KB),因此不会显着增加加载时间。
这实际上是一个概念证明 - 您可以基于非常基本的值计算来转换 svg 原语 - 无需高级框架/库 - 受到这篇文章的启发:Convert all shapes/primitives into path elements of SVG .
但是当我摆弄自己笨重的转换脚本时,我很快意识到存在一些挑战(Jarek Foksa 的规范化实现完美地解决了),例如:
相对,即基于百分比的单位
<circle cx="50%" cy="50%" r="25%" />
好的...我想我们需要根据 viewBox
定义的父 svg 边界来计算这些相对于绝对坐标的相对值。属性...也许根本没有可用的 viewBox ...或宽度/高度值。
或者像rx
这样的东西, ry
属性将圆角边框应用于 <rect>
元素 – 为了进行适当的转换,我们需要添加一些曲线命令,例如 a
, c
或s
.
路径与基元
确实是 <path>
元素可以通过 cubic or quadratic spline commands 绘制基元可以提供的任何形状– 由于它的连接能力(组合多个形状)以及它的相对或速记命令,甚至以更有效的方式。
但它不支持相对单位 - 但是您需要转换的形状可能在很大程度上取决于相对尺寸(例如圆形仪表饼图等)
结论
编写自定义转换脚本并不太难,但要注意一些棘手的细节。
const svg = document.querySelector('svg');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = new XMLSerializer().serializeToString(svg);
/**
* example script
**/
function getConvertedMarkup(svg, markupEl, decimals = 1) {
convertPrimitivesNative(svg, decimals);
markupEl.value = new XMLSerializer().serializeToString(svg);
}
/**
* parse svg attributes and convert relative units
**/
function parseSvgAttributes(svg, atts) {
let calcW = 0;
let calcH = 0;
let calcR = 0;
//1. check viewBox
let viewBoxAtt = svg.getAttribute('viewBox');
let viewBox = viewBoxAtt ? viewBoxAtt.split(' ') : [];
[calcW, calcH] = [viewBox[2], viewBox[3]];
//2. check width attributes
if (!calcW || !calcH) {
widthAtt = svg.getAttribute('width') ? parseFloat(svg.getAttribute('width')) : '';
heightAtt = svg.getAttribute('height') ? parseFloat(svg.getAttribute('height')) : '';
[calcW, calcH] = [widthAtt, heightAtt];
}
//3. calculate by getBBox()
if (!calcW || !calcH) {
let bb = svg.getBBox();
[calcW, calcH] = [(calcW ? calcW : bb.width), (calcH ? calcH : bb.height)];
}
// calculate relative radius: needed for non square aspect ratios
calcR = Math.sqrt(Math.pow(calcW, 2) + Math.pow(calcH, 2)) / Math.sqrt(2);
let attArr = [...atts];
let attObj = {};
attArr.forEach(function(att) {
let attName = att.nodeName;
// convert percentages to absolute svg units
let val = att.nodeValue;
let percentAtts = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'r', 'rx', 'ry', 'cx', 'cy', 'width', 'height']
if (val.toString().indexOf('%') !== -1 && percentAtts.indexOf(attName) !== -1) {
// strip units
val = parseFloat(val);
switch (attName) {
case 'cx':
case 'rx':
case 'width':
case 'x':
case 'x1':
case 'x2':
val = 1 / 100 * val * calcW;
break;
case 'cy':
case 'ry':
case 'height':
case 'y':
case 'y1':
case 'y2':
val = 1 / 100 * val * calcH;
break;
case 'r':
val = 1 / 100 * val * calcR;
break;
}
}
attObj[att.nodeName] = val;
});
return attObj;
}
/**
* convert primitive attributes to relative path commands
*/
function convertPrimitivesNative(svg, decimals = 3) {
let primitives = svg.querySelectorAll('line, polyline, polygon, circle, ellipse, rect');
if (primitives.length) {
primitives.forEach(function(primitive) {
let pathData = [];
let type = primitive.nodeName;
let atts = parseSvgAttributes(svg, primitive.attributes, 2);
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
switch (type) {
case 'rect':
let [rx, ry] = [atts.rx, atts.ry];
rx = !rx && ry ? ry : rx;
ry = !ry && rx ? rx : ry;
let [x, y, width, height] = [atts.x, atts.y, atts.width, atts.height];
let [widthInner, heightInner] = [width - rx * 2, height - ry * 2];
if (rx) {
pathData.push({
type: 'M',
values: [x, (y + ry)]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'h',
values: [widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'v',
values: [heightInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'h',
values: [-widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'z',
values: []
});
} else {
pathData.push({
type: 'M',
values: [x, y]
}, {
type: 'h',
values: [width]
}, {
type: 'v',
values: [height]
}, {
type: 'h',
values: [-width]
}, {
type: 'z',
values: []
});
}
break;
case 'line':
let [x1, y1, x2, y2] = [atts.x1, atts.y1, atts.x2, atts.y2];
pathData.push({
type: 'M',
values: [x1, y1]
}, {
type: 'l',
values: [(x2 - x1), (y2 - y1)]
});
break;
case 'circle':
case 'ellipse':
if (type == 'circle') {
let r = atts.r;
let [cX, cY] = [atts.cx, atts.cy - atts.r];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, -r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, -r]
}, {
type: 'z',
values: []
});
} else {
let rx = atts.rx;
let ry = atts.ry;
let [cX, cY] = [atts.cx, atts.cy - atts.ry];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'z',
values: []
});
}
break;
case 'polygon':
case 'polyline':
let closePath = type == 'polygon' ? 'z' : '';
let points = atts.points.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let pointArr = points.split(' ');
pathData.push({
type: 'M',
values: [+pointArr[0], +pointArr[1]]
});
for (let i = 2; i < pointArr.length; i += 2) {
let [x0, y0] = [+pointArr[i - 2], +pointArr[i - 1]];
let [x, y] = [+pointArr[i], +pointArr[i + 1]];
let com = {};
if (y == y0) {
com = {
type: 'h',
values: [x - x0]
}
} else if (x == x0) {
com = {
type: 'v',
values: [y - y0]
}
} else {
com = {
type: 'l',
values: [x - x0, y - y0]
}
}
pathData.push(com);
}
if (closePath) {
pathData.push({
type: 'z',
values: []
});
}
break;
//paths
default:
let dClean = atts.d.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let dArr = dClean.replace(/([a-zA-Z])/g, " | $1").split(' | ');
dArr.shift();
for (let i = 0; i < dArr.length; i++) {
let command = dArr[i].trim().split(' ');
let type = command.shift();
command = command.map((x) => {
return parseFloat(x);
});
pathData.push({
type: type,
values: command
});
}
break;
}
// copy primitive's attributes to path
setAttributes(path, atts, exclude);
// round coordinates and replace primitive with path
path.setPathDataOpt(pathData, decimals);
primitive.replaceWith(path);
})
}
};
function setAttributes(el, attributes, exclude = []) {
for (key in attributes) {
if (exclude.indexOf(key) === -1) {
el.setAttribute(key, attributes[key]);
}
}
}
function getAttributes(el) {
let attArr = [...el.attributes];
let attObj = {};
attArr.forEach(function(att) {
attObj[att.nodeName] = att.nodeValue;
});
return attObj;
}
/**
* return rounded path data
* based on:
* https://github.com/jarek-foksa/path-data-polyfill/blob/master/path-data-polyfill.js
*/
if (!SVGPathElement.prototype.setPathDataOpt) {
SVGPathElement.prototype.setPathDataOpt = function(pathData, decimals = 3) {
let d = "";
if (pathData.length) {
for (let i = 0; i < pathData.length; i++) {
let seg = pathData[i];
let [type, values] = [seg.type, seg.values];
let valArr = [];
if (values.length) {
for (let v = 0; v < values.length; v++) {
val = parseFloat(values[v]);
valArr.push(+val.toFixed(decimals));
}
}
d += type;
if (valArr.length) {
d += valArr.join(" ").trim();
}
}
d = d.
replaceAll(' -', '-').
replaceAll(' 0.', ' .').
replaceAll(' z', 'z');
this.setAttribute("d", d);
}
};
}
<p><button type="button" onclick="getConvertedMarkup(svg, svgMarkup, 2)">Convert Primitives</button></p>
<svg id="svg" xmlns="http://www.w3.org/2000/svg" data-width="150px" data-height="30px" viewBox="0 0 150 30">
<polygon id="polygon" fill="#CCCCCC" stroke="#E3000F" points="7.9,22.8 3,14.3 7.9,5.8 17.6,5.8 22.5,14.3 17.6,22.8 " />
<polyline id="polyline" fill="none" stroke="#E3000F" points="40.9,22.8 31.1,22.8 26.2,14.3 31.1,5.8
40.9,5.8 45.8,14.3 " />
<rect id="rect" x="37.5%" y="20%" rx="2%" ry="5%" fill="none" stroke="#E3000F" width="6%" height="56%" />
<line id="line" fill="none" stroke="#E3000F" x1="50.5" y1="22.8" x2="52.5" y2="5.8" />
<circle id="circle" fill="none" stroke="#E3000F" cx="52%" cy="49%" r="8%" />
<ellipse id="ellipse" fill="none" stroke="#E3000F" cx="68%" cy="49%" rx="7%" ry="25%" />
<path id="piechart" transform="scale(0.9) translate(130, 6)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z"
fill="red" class="segment segment-1 segment-class" id="segment-01" />
</svg>
<h3>Output</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>
关于d3.js - SVG - 将所有形状/基元转换为 <path>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31575550/
我正在学习构建单页应用程序 (SPA) 所需的所有技术。总而言之,我想将我的应用程序实现为单独的层,其中前端仅使用 API Web 服务(json 通过 socket.io)与后端通信。前端基本上是
当我看到存储在我的数据库中的日期时。 这是 正常 。日期和时间就是这样。 但是当我运行 get 请求来获取数据时。 此格式与存储在数据库 中的格式不同。为什么会发生这种情况? 最佳答案 我认为您可以将
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在尝试使用backbone.js 实现一些代码 和 hogan.js (http://twitter.github.com/hogan.js/) Hogan.js was developed ag
我正在使用 Backbone.js、Node.js 和 Express.js 制作一个 Web 应用程序,并且想要添加用户功能(登录、注销、配置文件、显示内容与该用户相关)。我打算使用 Passpor
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 8 年前。 Improve this ques
我尝试在 NodeJS 中加载数据,然后将其传递给 ExpressJS 以在浏览器中呈现 d3 图表。 我知道我可以通过这种方式加载数据 - https://github.com/mbostock/q
在 node.js 中,我似乎遇到了相同的 3 个文件名来描述应用程序的主要入口点: 使用 express-generator 包时,会创建一个 app.js 文件作为生成应用的主要入口点。 通过 n
最近,我有机会观看了 john papa 关于构建单页应用程序的精彩类(class)。我会喜欢的。它涉及服务器端和客户端应用程序的方方面面。 我更喜欢客户端。在他的实现过程中,papa先生在客户端有类
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我是一个图形新手,需要帮助了解各种 javascript 2D 库的功能。 . . 我从 Pixi.js 中得到了什么,而我没有从 Konva 等基于 Canvas 的库中得到什么? 我从 Konva
我正在尝试将一些 LESS 代码(通过 ember-cli-less)构建到 CSS 文件中。 1) https://almsaeedstudio.com/ AdminLTE LESS 文件2) Bo
尝试查看 Express Passport 中所有登录用户的所有 session ,并希望能够查看当前登录的用户。最好和最快的方法是什么? 我在想也许我可以在登录时执行此操作并将用户模型数据库“在线”
我有一个 React 应用程序,但我需要在组件加载完成后运行一些客户端 js。一旦渲染函数完成并加载,运行与 DOM 交互的 js 的最佳方式是什么,例如 $('div').mixItUp() 。对
请告诉我如何使用bodyparser.raw()将文件上传到express.js服务器 客户端 // ... onFilePicked(file) { const url = 'upload/a
我正在尝试从 Grunt 迁移到 Gulp。这个项目在 Grunt 下运行得很好,所以我一定是在 Gulp 中做错了什么。 除脚本外,所有其他任务均有效。我现在厌倦了添加和注释部分。 我不断收到与意外
我正在尝试更改我的网站名称。找不到可以设置标题或应用程序名称的位置。 最佳答案 您可以在 config/ 目录中创建任何文件,例如 config/app.js 包含如下内容: module.expor
经过多年的服务器端 PHP/MySQL 开发,我正在尝试探索用于构建现代 Web 应用程序的新技术。 我正在尝试对所有 JavaScript 内容进行排序,如果我理解得很好,一个有效的解决方案可以是服
我是 Nodejs 的新手。我在 route 目录中有一个 app.js 和一个 index.js。我有一个 app.use(multer....)。我还定义了 app.post('filter-re
我正在使用 angular-seed用于构建我的应用程序的模板。最初,我将所有 JavaScript 代码放入一个文件 main.js。该文件包含我的模块声明、 Controller 、指令、过滤器和
我是一名优秀的程序员,十分优秀!