[英]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"))
行的 Output:
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()
輕松訪問路徑元素的 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
屬性。 對於貝塞爾曲線、圓弧等,控制點也根據需要給出為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
命令和一個包含在地圖中的循環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]; });
與對路徑進行逆向工程相比,這將是一種更 d3 式的數據訪問方式。 也可能更容易理解。
有點麻煩,但您可以使用 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>
pathSegList
在舊版 Chrome 中受支持,並自 Chrome 48 起被移除。
但是 Chrome 還沒有實現新的 API 。
使用路徑段 polyfill來處理舊 API。
使用路徑數據 polyfill來處理新的 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 ]
}
]
我通過谷歌找到了這個問題。 我需要的只是一個 SVG path
對象的pathSegList
屬性:
var points = pathElement.pathSegList;
每個點看起來像
y: 57, x: 109, pathSegTypeAsLetter: "L", pathSegType: 4, PATHSEG_UNKNOWN: 0…}
看
我已經成功地使用它來呈現 x,y 點列表:
代碼不僅僅是我可以放入這個文本框的內容,但這里可能是一個好的開始: https : //github.com/Shinao/PathToPoints/blob/master/js/pathtopoints.js#L209
擴展@cuixiping 答案: 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>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.