将SVG Path d属性转换为点数组

ank*_*usu 15 javascript svg d3.js

我可以按如下方式创建一行:

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))
Run Code Online (Sandbox Code Playgroud)

现在我想在此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;
Run Code Online (Sandbox Code Playgroud)

});

线的输出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.

jsh*_*ley 24

SVGPathElementAPI已获得此信息的内置方法.您不需要自己解析数据字符串.

由于您将行的选择存储为变量,因此您可以使用myLine.node()引用path元素本身轻松访问路径元素的api .

例如:

var pathElement = myLine.node();
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过访问pathSegList属性来访问用于构造路径的命令列表:

var pathSegList = pathElement.pathSegList;
Run Code Online (Sandbox Code Playgroud)

使用length此对象的属性,您可以轻松地遍历它以获取与每个路径段关联的坐标:

for (var i = 0; i < pathSegList.length; i++) {
  console.log(pathSegList[i]);
}
Run Code Online (Sandbox Code Playgroud)

检查控制台输出,你会发现,每个路段都有属性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
  })
}
Run Code Online (Sandbox Code Playgroud)

编辑:

至于在附加文本时使用原始数据...我假设您正在寻找向点附加标签,没有必要经历重建数据的所有麻烦.事实上,真正的问题是你从来没有使用数据绑定来制作你的折线图.尝试使用.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')
Run Code Online (Sandbox Code Playgroud)

  • @qwwqwwq他们弃用了它(打破了所有使用它的应用程序),因为新的API被**提议*作为新SVG标准的替代品.当然,好几个月过去了,Chrome仍然没有任何优化的API来获取路径段,而不是较旧的路径段,也不是新路径段.请参阅此处了解火焰派对:https://bugs.chromium.org/p/chromium/issues/detail?id = 539385 (5认同)
  • 这仍适用于大多数浏览器吗?我很确定我在Chrome 48中看到`.pathSegList`的`undefined` (3认同)
  • @qwwqwwq pathSegList已弃用.有关替代方案,请参阅此答案:http://stackoverflow.com/a/34359059/1525769 (2认同)

cke*_*sch 7

您可以通过在分割字符串断行成单独的命令L,MC字符:

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])/);
Run Code Online (Sandbox Code Playgroud)

这给出了用于呈现路径的命令序列.每个字符串都包含一个字符(L,M或C),后跟一串用逗号分隔的数字.它们看起来像这样:

"C66.66666666666666,66.66666666666666,83.33333333333331,
83.33333333333331,99.99999999999999,99.99999999999999"
Run Code Online (Sandbox Code Playgroud)

这描述了通过三个点的曲线,[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;
});
Run Code Online (Sandbox Code Playgroud)

这将返回一个包含每个命令的数组,作为长度为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]; });
Run Code Online (Sandbox Code Playgroud)

与对路径进行逆向工程相比,这将是一种更加d3式的访问数据的方式.也许更容易理解.

有关SVG路径元素的更多信息


cui*_*ing 6

pathSegList在旧版 Chrome 中受支持,自 Chrome 48 起被删除。
但 Chrome 尚未实现新的 API

使用路径段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 ]
    }
]
Run Code Online (Sandbox Code Playgroud)


Alo*_*lok 5

有点 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>
Run Code Online (Sandbox Code Playgroud)