将SVG路径d属性转换为点数组
Convert SVG Path d attribute to a array of points
我可以创建如下一行:
var lineData = [{ "x": 50, "y": 50 }, {"x": 100,"y": 100}, {"x": 150,"y": 150}, {"x": 200, "y": 200}];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("basis");
var myLine = lineEnter.append("path")
.attr("d", lineFunction(lineData))
现在我想给这个lineArray的第二个点添加一个文本:
lineEnter.append("text").text("Yaprak").attr("y", function(d){
console.log(d); // This is null
console.log("MyLine");
console.log(myLine.attr("d")) // This is the string given below, unfortunately as a String
// return lineData[1].x
return 10;
});
console.log(myLine.attr("d"))
:
M50,50L58.33333333333332,58.33333333333332C66.66666666666666,66.66666666666666,83.33333333333331,83.33333333333331,99.99999999999999,99.99999999999999C116.66666666666666,116.66666666666666,133.33333333333331,133.33333333333331,150,150C166.66666666666666,166.66666666666666,183.33333333333331,183.33333333333331,191.66666666666663,191.66666666666663L200,200
我可以得到字符串格式的路径数据。我能把这个数据转换回lineData数组吗?或者,在附加文本时,是否有其他简单的方法来重新生成或获取lineData ?
请参考这个JSFiddle。
SVGPathElement
API具有获取此信息的内置方法。您不需要自己解析数据字符串。
由于您将一行的选择存储为变量,因此可以使用myLine.node()
轻松访问path元素的api,以引用path
元素本身。
var pathElement = myLine.node();
然后您可以通过访问pathSegList
属性来访问用于构建路径的命令列表:
var pathSegList = pathElement.pathSegList;
使用该对象的length
属性,您可以轻松地循环遍历它以获得与每个路径段相关的坐标:
for (var i = 0; i < pathSegList.length; i++) {
console.log(pathSegList[i]);
}
检查控制台输出,您将发现每个路径段都具有代表该段端点的x
和y
的属性。对于bezier曲线,圆弧等,控制点也根据需要给出x1
, y1
, x2
和y2
。
在您的示例中,无论您是使用此方法还是选择自己解析字符串,您都会遇到困难,因为您使用interpolate('basis')
进行行插值。因此,行生成器输出6个命令(在您的特定情况下)而不是4个,并且它们的端点并不总是对应于数据中的原始点。如果您使用interpolate('linear')
,您将能够重建原始数据集,因为线性插值与路径数据输出具有一对一的对应关系。
假设您使用线性插值,重构原始数据集可以这样做:
var pathSegList = myLine.node().pathSegList;
var restoredDataset = [];
// loop through segments, adding each endpoint to the restored dataset
for (var i = 0; i < pathSegList.length; i++) {
restoredDataset.push({
"x": pathSegList[i].x,
"y": pathSegList[i].y
})
}
编辑:至于在追加文本时使用原始数据…我假设你想给这些点附加标签,没有必要经历重建数据的所有麻烦。事实上,真正的问题在于您从一开始就没有使用数据绑定来制作线形图。尝试对路径使用.datum()
方法绑定数据,并对标签使用.data()
方法。此外,您可能希望重命名lineEnter
,因为您没有使用输入选择,它只是代表一个组。例如:
// THIS USED TO BE CALLED `lineEnter`
var lineGroup = svgContainer.append("g");
var myLine = lineGroup.append("path")
// HERE IS WHERE YOU BIND THE DATA FOR THE PATH
.datum(lineData)
// NOW YOU SIMPLY CALL `lineFunction` AND THE BOUND DATA IS USED AUTOMATICALLY
.attr("d", lineFunction)
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
// FOR THE LABELS, CREATE AN EMPTY SELECTION
var myLabels = lineGroup.selectAll('.label')
// FILTER THE LINE DATA SINCE YOU ONLY WANT THE SECOND POINT
.data(lineData.filter(function(d,i) {return i === 1;})
// APPEND A TEXT ELEMENT FOR EACH ELEMENT IN THE ENTER SELECTION
.enter().append('text')
// NOW YOU CAN USE THE DATA TO SET THE POSITION OF THE TEXT
.attr('x', function(d) {return d.x;})
.attr('y', function(d) {return d.y;})
// FINALLY, ADD THE TEXT ITSELF
.text('Yaprak')
您可以通过在L
、M
和C
字符上分割字符串来将该行分割成单独的命令:
var str = "M50,50L58.33333333333332,58.33333333333332C66.66666666666666,
66.66666666666666,83.33333333333331,83.33333333333331,
99.99999999999999,99.99999999999999C116.66666666666666,116.66666666666666,
133.33333333333331,133.33333333333331,150,150C166.66666666666666,
166.66666666666666,183.33333333333331,183.33333333333331,191.66666666666663,
191.66666666666663L200,200"
var commands = str.split(/(?=[LMC])/);
这给出了用于呈现路径的命令序列。每个都是一个字符串,由一个字符(L、M或C)后跟一串用逗号分隔的数字组成。它们看起来像这样:
"C66.66666666666666,66.66666666666666,83.33333333333331,
83.33333333333331,99.99999999999999,99.99999999999999"
描述一条曲线经过三个点,[66,66],[83,83]和[99,99]。您可以使用另一个split
命令和一个映射中包含的循环将它们处理成成对点数组:
var pointArrays = commands.map(function(d){
var pointsArray = d.slice(1, d.length).split(',');
var pairsArray = [];
for(var i = 0; i < pointsArray.length; i += 2){
pairsArray.push([+pointsArray[i], +pointsArray[i+1]]);
}
return pairsArray;
});
这将返回一个数组,其中包含每个命令作为长度为2的数组,每个数组都是路径对应部分中一个点的(x,y)坐标对。
您也可以修改map
中的函数,使其返回既包含命令类型又包含数组中的点的对象。
编辑:如果您希望能够访问lineData
,您可以将其作为数据添加到组中,然后将路径添加到组中,并将文本添加到组中。
var group = d3.selectAll('g').data([lineData])
.append('g');
var myLine = group.append('path')
.attr('d', function(d){ return lineFunction(d); });
var myText = group.append('text')
.attr('text', function(d){ return 'x = ' + d[1][0]; });
这将是一种更3d式的访问数据的方式,而不是对路径进行逆向工程。也可能更容易理解。
关于SVG路径元素的更多信息
pathSegList
在旧Chrome中支持,自Chrome 48起被删除。
但是Chrome还没有实现新的API。
使用path seg polyfill来使用旧的API
使用路径数据填充来使用新的API。推荐使用
var path = myLine.node();
//Be sure you have added the pathdata polyfill to your page before use getPathData
var pathdata = path.getPathData();
console.log(pathdata);
//you will get an Array object contains all path data details
//like this:
[
{
"type": "M",
"values": [ 50, 50 ]
},
{
"type": "L",
"values": [ 58.33333333333332, 58.33333333333332 ]
},
{
"type": "C",
"values": [ 66.66666666666666, 66.66666666666666, 83.33333333333331, 83.33333333333331, 99.99999999999999, 99.99999999999999 ]
},
{
"type": "C",
"values": [ 116.66666666666666, 116.66666666666666, 133.33333333333331, 133.33333333333331, 150, 150 ]
},
{
"type": "C",
"values": [ 166.66666666666666, 166.66666666666666, 183.33333333333331, 183.33333333333331, 191.66666666666663, 191.66666666666663 ]
},
{
"type": "L",
"values": [ 200, 200 ]
}
]
有点hacky,但是你可以使用animateMotion来动画一个对象(例如一个矩形或一个圆圈)沿着路径,然后采样对象的x/y位置。你必须做出一系列选择(游戏邦注:例如,你要以多快的速度动画对象,你要以多快的速度采样x/y位置,等等)。您也可以多次运行此过程,并取某种平均值或中位数。
完整代码(参见:http://jsfiddle.net/mqmkc7xz/)
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<path id="mypath"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 70,67 15,0 c 0,0 -7.659111,-14.20627 -10.920116,-27.28889 -3.261005,-13.08262 9.431756,-13.85172 6.297362,-15.57166 -3.134394,-1.71994 -7.526366,-1.75636 -2.404447,-3.77842 3.016991,-1.19107 9.623655,-5.44678 0.801482,-9.67404 C 76.821958,10 70,10 70,10"
/>
</svg>
<div id="points"></div>
<script>
/**
* Converts a path into an array of points.
*
* Uses animateMotion and setInterval to "steal" the points from the path.
* It's very hacky and I have no idea how well it works.
*
* @param SVGPathElement path to convert
* @param int approximate number of points to read
* @param callback gets called once the data is ready
*/
function PathToPoints(path, resolution, onDone) {
var ctx = {};
ctx.resolution = resolution;
ctx.onDone = onDone;
ctx.points = [];
ctx.interval = null;
// Walk up nodes until we find the root svg node
var svg = path;
while (!(svg instanceof SVGSVGElement)) {
svg = svg.parentElement;
}
// Create a rect, which will be used to trace the path
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
ctx.rect = rect;
svg.appendChild(rect);
var motion = document.createElementNS("http://www.w3.org/2000/svg", "animateMotion");
motion.setAttribute("path", path.getAttribute("d"));
motion.setAttribute("begin", "0");
motion.setAttribute("dur", "3"); // TODO: set this to some larger value, e.g. 10 seconds?
motion.setAttribute("repeatCount", "1");
motion.onbegin = PathToPoints.beginRecording.bind(this, ctx);
motion.onend = PathToPoints.stopRecording.bind(this, ctx);
// Add rect
rect.appendChild(motion);
}
PathToPoints.beginRecording = function(ctx) {
var m = ctx.rect.getScreenCTM();
ctx.points.push({x: m.e, y: m.f});
ctx.interval = setInterval(PathToPoints.recordPosition.bind(this, ctx), 1000*3/ctx.resolution);
}
PathToPoints.stopRecording = function(ctx) {
clearInterval(ctx.interval);
// Remove the rect
ctx.rect.remove();
ctx.onDone(ctx.points);
}
PathToPoints.recordPosition = function(ctx) {
var m = ctx.rect.getScreenCTM();
ctx.points.push({x: m.e, y: m.f});
}
PathToPoints(mypath, 100, function(p){points.textContent = JSON.stringify(p)});
</script>
</body>
</html>
我是通过Google找到这个问题的。我所需要的只是SVG path
对象的pathSegList
属性:
var points = pathElement.pathSegList;
每个点看起来都像
y: 57, x: 109, pathSegTypeAsLetter: "L", pathSegType: 4, PATHSEG_UNKNOWN: 0…}
看- www.w3.org/TR/SVG/paths
- https://msdn.microsoft.com/en-us/library/ie/ff971976 (v = vs.85) . aspx
我已经成功地使用它来渲染x,y点的列表:
https://shinao.github.io/PathToPoints/
代码超出了这个文本框所能容纳的范围,但这里可能是一个好的开始:https://github.com/Shinao/PathToPoints/blob/master/js/pathtopoints.js#L209
扩展@翠西萍的回答:getPathData()
还包括一个规范化选项:
getPathData({normalize:true})
将转换相对和简写命令,只使用M
, L
, C
和z
。
所以你不必担心高度优化/缩小的d
字符串(包含相对命令,速记等)。
let pathData = path1.getPathData({ normalize: true });
let lineData = pathDataToPoints(pathData);
pointsOut.value=JSON.stringify(lineData, null, ''t')
/**
* create point array
* from path data
**/
function pathDataToPoints(pathData) {
let points = [];
pathData.forEach((com) => {
let values = com.values;
let valuesL = values.length;
// the last 2 coordinates represent a segments end point
if (valuesL) {
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
points.push(p);
}
});
return points;
}
/**
* render points from array
* just for illustration
**/
renderPoints(svg, lineData);
function renderPoints(svg, points) {
points.forEach(point=>{
renderPoint(svg, point);
})
}
function renderPoint(svg, coords, fill = "red", r = "2") {
if (Array.isArray(coords)) {
coords = {
x: coords[0],
y: coords[1]
};
}
let marker = `<circle cx="${coords.x}" cy="${coords.y}" r="${r}" fill="${fill}">
<title>${coords.x} ${coords.y}</title></circle>`;
svg.insertAdjacentHTML("beforeend", marker);
}
svg{
width:20em;
border:1px solid red;
overflow:visible;
}
path{
stroke:#000;
stroke-width:1
}
textarea{
width:100%;
min-height:20em
}
<svg id="svg" viewBox='0 0 250 250'>
<path id="path1" d="M50 50l8.33 8.33c8.33 8.33 25 25 41.67 41.67s33.33 33.33 50 50s33.33 33.33 41.67 41.67l8.33 8.33" stroke="#000" />
</svg>
<h3>Points</h3>
<textarea id="pointsOut"></textarea>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.4/path-data-polyfill.min.js"></script>
- 使用JS将数组转换为json对象
- 本地存储中的字符串到字节数组转换
- Javascript函数,用于将数组转换为“;2-元组”;
- php数组转换为javascript
- 在Javascript中将一个值和字符串数组转换为if语句
- 将数组转换为javascript格式的字符串
- 如何将数组转换为有效的json
- JSON数组转换为JS对象数组
- 如何将数组转换为映射
- JavaScript:将字符串数组转换为文本区域
- javascript |数组转换
- 数组转换为多个字符串
- Javascript将数组转换为字符串并使其反向输出
- 将Byte数组转换为Angularjs中的Base64字符串
- 将对象数组转换为与nodejs/pg/unnest兼容的数组
- 将Javascript数组转换为PHP数组
- 将数组转换为键值对
- 使用Ramda将对象数组转换为普通对象
- javascript将对象数组转换为字符串以存储在s3中
- 使用.join方法将数组转换为不带逗号的字符串