Simplifying SVG path strings by reducing number of nodes - javascript

I am generating a large SVG path string that represents a line chart.
Beneath the chart I have a slider for selecting a time range slice. Behind the slider is a mini preview of the whole line chart.
I am currently scaling down the path to generate the preview however in doing so I am ending up with tens of nodes per pixel and therefore far more detail then is necessary. Of course this gives the browser more rendering to do than it has to.
There is plenty of info available on compressing svg strings (gzipping etc), though little on algorithms that actually simplify the path by reducing the nodes.
I am using Raphaeljs and am looking for a javascript based solution. Any ideas?

Simplify.js is probably what you're looking after.
Given your line chart consists of straight line segments only (which by definition it should), you can use it like this:
var tolerance = 3
var pathSegArray = []
for (var i=0; i<path.pathSegList.numberOfItems; i++) {
pathSegArray.push(path.pathSegList.getItem(i))
}
var newPathArray = simplify(pathSegArray,tolerance)
var newD = "M";
for (i=0; i<newPathArray.length; i++) {
newD += newPathArray[i].x + " " + newPathArray[i].y + " "
}
path.setAttribute("d",newD)

Related

What does it determine the placedItem.width and placedItem.height , when adding one without specifying those properties?

I'm in Adobe Illustrator 2022 26.064 and I'm placing an item. It is the thirdone I place in the same document.
var placedItem3 = doc.placedItems.add();
placedItem3.file = new File("/c/files/dotfiles-win/illustrator-scripts/lorem-picsum/3to2-1080x720.jpg");
placedItem3.name = "placedItem3";
For some reason it gets placedItem.width=3061,41 ,placedItem.height=2040,94
When the file is 1080x720
The previous items were placed keeping the same dimensions of the file.
You can find out ppi of a image with the formula:
72 / image.matrix.mValueA
For example here is the script that finds images with ppi less than 250 and selects them:
var min = 250; // target ppi
var rasters = app.activeDocument.rasterItems;
var i = rasters.length;
while (i--) if (72/rasters[i].matrix.mValueA < min) rasters[i].selected = true;
Illustrator does it in a rather uncommon way via the transformation matrix
Matrix
I'm not sure if there are another ways as well. And probably it might require additional steps for rotated images.

Generate various beautiful color

I want to generate light colors in RGB format.
A human being should be able to distinguish these colors easily.
I want a solution using JavaScript.
What I did:
var getRandom = function(min, max) {
return (Math.random() * (max - min) + min);
};
var randomColorGenerator = function(index) {
var p = getRandom(0,10)/10;
var q = getRandom(0,10)/10;
var r = getRandom(0,10)/10;
var max=235;
var min= 150;
var rgb = [Math.floor(min + p*(max-min)), Math.floor(min + q*(max-min)), Math.floor(min + r*(max-min))];
return rgb;
}
As you can see that can generate colors but we are not certain to get different colors...
A solution can be:
Two colors u,v are similar when their distance d(u,v) = sqrt [ (u(r)-v(r))^2 + ... + (u(b)-v(b))^2 ] / [sqrt(3)*255] < epsilon (where epsilon is small, assume that epsilon = 0.01);
Then we can create an array and use randomColorGenerator with that rule.
But I'm here to discuss it, about an efficient algorithm.
Basically, I will use it on the chart (Chart JavaScript) to generate a readable doughnut.
I think that we can group chart elements by three elements to do it.
When I have created charts, I would simply define a list of colors that I already knew looked good and distinguishable from each other, and used them in a specified order (looping back to the first if needed). I think this is the most reliable way to make sure your chart is both readable and aesthetically pleasing.
If your question was purely theoretical, I wouldn't have said anything, but since you said:
Basically, I will use it on chart (Chart Js) to generate a readable doughnut. I think that we can group chart elements by three elements to do it.
I just wanted to offer my two cents on it; and I would have just left a comment but my Stack Overflow reputation isn't high enough.

Convert stroke data to SCG Ink format

I'd like to use Seshat—a handwritten math expression parser—for a project I'm working on, but I'm having some trouble understanding how to provide the program its proper input, an InkML or SCG Ink file.
I've taken a long look at an online example that exists here, and I see that they get a Javascript array of stroke information from an HTML Canvas field with this JS library applied, but I don't know what happens that array after it gets POSTed to their server.
I've read the SCG Ink spec, and I think it might be relatively easy to parse the array into the format, but I'm hoping there's something obvious I'm missing that would make this trivial. Any help would be greatly appreciated.
I emailed the Seshat author and he suggested I convert the input to SCG Ink, which turned out to be pretty easy if you take the JavaScript libraries used at http://cat.prhlt.upv.es/mer/. Specifically, you want jquery.sketchable.memento.min.js, jquery.sketchable.min.js, and jsketch.min.js in addition to regular old jQuery. Here's what I did in case anyone else is interested in Seshat.
Notice from the same page that in main.js they apply the Sketchable library to the HTML canvas area with this block of code:
var $canvas = $('#drawing-canvas').sketchable({
graphics: {
strokeStyle: "red",
firstPointSize: 2
}
});
Now we can take a look at their submitStrokes() function to see how to take the strokes from Sketchable and convert to SCG Ink. The line var strokes = $canvas.sketchable('strokes'); gets the strokes, and then the line strokes = transform(strokes); applies a quick transformation to extract only the data they need. Here's the transform() function for reference:
function transform(strokes) {
for (var i = 0; i < strokes.length; ++i)
for (var j = 0, stroke = strokes[i]; j < stroke.length; ++j)
strokes[i][j] = [ strokes[i][j][0], strokes[i][j][1] ];
return strokes;
};
The value returned fro transform() is a three-dimensional array of strokes and points. (Each element of the first dimension is a stroke, each element of the second dimension is a point, and the third dimension is x-, y-coordinates.) On that site they go ahead and POST that array to the server which must handle the final conversion to SCG Ink. I wrote a JavaScript function to handle it:
function strokesToScg(strokes) {
var scg = 'SCG_INK\n' + strokes.length + '\n'
strokes.forEach(function (stroke) {
scg += stroke.length + '\n'
stroke.forEach(function (p) {
scg += p[0] + ' ' + p[1] + '\n'
})
})
return scg
}
And that's it. The value returned from strokesToScg() is a string describing a series of strokes in the SCG Ink format.

D3: Draw part of interpolated path with dashed stroke

I'm drawing a simple interpolated line using D3.js (output as path). However, I want part of the path to have a dashed stroke if a boolean in a data point is set to true:
Link to image
An easy solution would be to just draw it with <line> segments instead - but that way I lose the interpolation.
Then I found an example from mbostock, showing how to draw a gradient along a interpolated path. I modified it to just draw transparent path fills whenever the boolean was set to true and white fills when false - while my old path is all dashed.
That works (queue the above screenshot) - but by adding around thousand path elements to the DOM contra having only a single path.
It's not desirable with that many DOM elements, especially since I'm going to make more curves and the site needs to be mobile optimized. Am I missing a much simpler solution?
Wouldn't mind a modified version of mbostock's example doing the heavy calculations in advance, as long as the DOM output is simple.
Thanks!
I prepared this example for another SO question. Screenshot is here:
I think you have enough material there to devise a solution that fits your needs.
Take a look also at this page:
SVG Path Styling
You could use stroke-dasharray to add dashes in the stroke of the generated path in the right places. That would entail finding the proper dash lengths. This can be done by calling pathElm.getPathLength() on the path up to the point where you want it to start being dashed, and to where you want it to end.
Let's say path A that is the part that is before the dashes should start. Set the d attribute with that part and call getPathLength() on it. Let's call this length a.
Append the the part of the path that should be dashed to the d attribute, then call getPathLength() again. Let's call this length b.
Create a new path element with the remaining part of the path, then call getPathLength() on that. Let's call this length c.
Construct a stroke-dasharray property string something like this:
var a = getPathLengthA();
var b = getPathLengthB();
var c = getPathLengthC();
var dasharray = a + " ";
for(var usedlen = 0; usedlen < (b-a); ) {
dasharray += "5 10 "; // just whatever dash pattern you need
usedlen += 15; // add the dash pattern length from the line above
}
dasharray += c;
pathElement.style.strokeDasharray = dasharray;
Here's a static example of that.

Converting svg path to polygon in javascript

i am trying to convert an svg path to an svg polygon in javascript. i found this function to crawl along the path and extract its coordinates.
var length = path.getTotalLength();
var p=path.getPointAtLength(0);
var stp=p.x+","+p.y;
for(var i=1; i<length; i++){
p=path.getPointAtLength(i);
stp=stp+" "+p.x+","+p.y;
}
this works but it returns some hundreds of points for a polygon that has only six points originally. how would i get only the necessary points (all paths are straight lines, no curves)
ok got it.. the function getPathSegAtLength() returns the number of the actual path segment. with that it's easy then.
var len = path.getTotalLength();
var p=path.getPointAtLength(0);
var seg = path.getPathSegAtLength(0);
var stp=p.x+","+p.y;
for(var i=1; i<len; i++){
p=path.getPointAtLength(i);
if (path.getPathSegAtLength(i)>seg) {
stp=stp+" "+p.x+","+p.y;
seg = path.getPathSegAtLength(i);
}
}
path.getPointAtLength() is good for rough purposes where you don't need both speed and quality. If you get every pixel, you get thousands of points, but still the quality is low, because SVG path can have decimal values, eg. 0.1, 0.2.
If you want more precision by calling eg. path.getPointAtLength(0.1) you get easily tens of thousands of points in complex paths and the process last seconds or tens of seconds. And after that you have to reduce the count of point (https://stackoverflow.com/a/15976155/1691517), which last again seconds. But still the quality can be low, if wrong points are removed.
Better techique is to first convert all path segments to cubic curves eg. using Raphael's path2curve() and then use some adaptive method (http://antigrain.com/research/adaptive_bezier/) to convert cubic segments to points and you get at the same time both the speed and quality. And after that there is no need to reduce points because the adaptive process itself has parameters to adjust the quality.
I have made a function that does all that and I'm going to publish it when it is enough optimized for speed. The quality and reliability seems to be 100% after testing with thousands of random paths and the speed is yet significantly faster than with path.getPointAtLength().
To iterate over the segments, use something like this:
var segList = path.normalizedPathSegList; // or .pathSegList
for(var i=1; i<segList.numberOfSegments; i++){
var seg = segList.getItem(i);
}
If you want to reduce the number of vertices, then you can use Simplify.js as described here.

Categories