Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
285 views
in Technique[技术] by (71.8m points)

javascript - Convert SVG Path d attribute to a array of points

When I can create a line as follows:

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))

Now I want to add a text to the second point of this 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;

} );

Output of the line 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

I can get the path data in string format. Can I convert this data back to lineData array? Or, is there any other and simple way to regenerate or get the lineData when appending a text?

Please refer to this JSFiddle.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The SVGPathElement API has built-in methods for getting this info. You do not need to parse the data-string yourself.

Since you stored a selection for your line as a variable, you can easily access the path element's api using myLine.node() to refer to the path element itself.

For example:

var pathElement = myLine.node();

Then you can access the list of commands used to construct the path by accessing the pathSegList property:

var pathSegList = pathElement.pathSegList;

Using the length property of this object, you can easily loop through it to get the coordinates associated with each path segment:

for (var i = 0; i < pathSegList.length; i++) {
  console.log(pathSegList[i]);
}

Inspecting the console output, you will find that each path segment has properties for x and y representing the endpoint of that segment. For bezier curves, arcs, and the like, the control points are also given as x1, y1, x2, and y2 as necessary.

In your case, regardless of whether you use this method or choose to parse the string yourself, you will run into difficulties because you used interpolate('basis') for your line interpolation. Therefore, the line generator outputs 6 commands (in your specific case) rather than 4, and their endpoints do not always correspond to the original points in the data. If you use interpolate('linear') you will be able to reconstruct the original dataset, since the linear interpolation has a one-to-one correspondence with the path data output.

Assuming you used linear interpolation, reconstructing the original dataset could be done as follows:

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
  })
}

EDIT:

As far as using the original data when appending text... I'm assuming you are looking to append labels to the points, there's no need to go through all the trouble of reconstructing the data. In fact the real issue is that you never used data-binding in the first place to make your line graph. Try binding the data using the .datum() method for your path, and using the .data() method for the labels. Also you might want to rename lineEnter since you're not using an enter selection and it simply represents a group. For example:

// 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')

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...