Slow SVG rendering when setting src - javascript

I am having a problem when rendering SVG images to canvas. When I generate the svg markup and add it to the src of a new image then render that image to the canvas I get terrible performance. My app drops from real time to unusable. But if I save the same markup as an svg file and load that as an image it runs perfectly.
var svgText = `<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="239" height="268" id="testSVG" >
<defs>
<linearGradient id='grad2262398' x1='0' y1='0' x2='0' y2='1'>
<stop offset='0.0' stop-color='#bcbbbb'/>
<stop offset='1.0' stop-color='#d2a796'/>
</linearGradient>
</defs>
<path d="M 158.5 9.0 L 225.0 97.5 L 208.5 111.0 L 141.0 23.5 L 158.5 9.0 M 118.5 44.0 L 203.0 114.5 L 189.5 132.0 L 104.0 61.5 L 118.5 44.0 M 85.5 90.0 L 185.5 136.0 L 178.0 155.5 L 76.5 111.0 L 85.5 90.0 M 67.5 141.0 L 174.5 163.0 L 171.0 185.0 L 62.5 163.0 L 67.5 141.0 M 16.0 168.0 L 38.0 168.0 L 38.0 234.0 L 192.0 234.0 L 192.0 168.0 L 215.0 168.0 L 215.0 256.0 L 16.0 256.0 L 16.0 168.0 M 60.0 190.0 L 171.0 190.0 L 171.0 213.0 L 60.0 213.0 L 60.0 190.0 " style="fill:url(#grad2262398);"/>
</svg>`
var image = new Image();
image.src = svgText; // this runs very slowly
//while
var image = new Image();
image.src = "svgText.svg"; // this renders as fast as a simillar sized bitmap image
// yet the content is exactly the same.
Above is just an example of how the SVG is formated, the problem does not become an issue until the points in the path get to the several thousand. I can render a SVG with 100K+ points in real-time when loaded from a file, as a Javascript string I get less then 2 frames a second.
How can I fix this and still have the auto generated path content available so I can modify it when needed.

Related

SVG Path On Click Event

Referring to the code-snipplet below about getting data with on click on an svg path here, for my example which is displaying Number 2, I would like it to have the alert button pop up when the cursor is clicked only when close to number 2. My code below is wrong because when cursor is clicked anywhere which is far way from the Number 2, the pop up alert box is still shown. I will really appreciate any help I can get :)
function getKey(button) {
let key = button.querySelectorAll('path')[0].dataset.key;
alert('key is ' + key)
}
<div onClick="getKey(this)">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
<g enable-background="new">
<path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width=".74" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
</g>
</svg>
</div>
this in an element onclick attribute refers to the element
By setting an onclick attribute to the div containing the svg, a click anywhere within the div will call the function, and if this is sent to the function as an argument, it will always refer to the div element.
Once fired, your function is extracting the data attribute of the first path tag inside the div.
Instead, an eventListener can be attached to the path elements directly. When fired an event is created, which has a target property containing a reference to the tag that generated the event. It is that target element from which you need to extract the data attribute.
See: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
This working snippet demonstrates attaching event listeners to your paths. (I have had to change the line width of your path as the click must hit the line for the event to be triggered, see end note)
let paths = document.querySelectorAll('path');
// paths is an html collection of all paths;
// attach event listeners to each path;
for (let i=0; i<paths.length; i++) {
paths[i].addEventListener('click', event => alert(event.target.dataset.key));
}
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
<g enable-background="new">
<path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
</g>
</svg>
</div>
If you have many paths, the above process may not be memory-efficient because the function is duplicated for each of the paths. Instead, a single handler function can be used and called from each of the event listeners. Like this:
let paths = document.querySelectorAll('path');
// paths is an html collection of all paths;
// attach event listeners to each path;
for (let i=0; i<paths.length; i++) {
paths[i].addEventListener('click', displayAlert);
}
function displayAlert(event) {
alert(event.target.dataset.key)
}
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
<g enable-background="new">
<path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
</g>
</svg>
</div>
Note that the displayAlert function is referenced within the event listener without parentheses or argument. The event is passed automatically to the named external function (the declaration for which should include an argument if the automatically passed event is to be used inside the function). In practice, you are unlikely to have memory problems with such a trivial function but I've included this for completeness.
Alternatively, you can attach a single event listener to the div, and check that the target of the event is a path before displaying the data. This works because, although the event is attached to the div, the target of the event is set to whichever descendant of the div was clicked:
let div = document.getElementById('svg-container');
div.addEventListener('click', event => {
if (event.target.tagName == 'path') alert(event.target.dataset.key)
});
<div id="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
<g enable-background="new">
<path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
</g>
</svg>
</div>
note
If you have to use narrow strokes for your paths, it may be difficult for a user to place the cursor exactly on the line and so clicks may be missed. One way to mitigate this is to duplicate the paths, set the stroke of the the lower (first) one of each pair much wider and make it invisible by using the background colour. In the case of characters like your number 2, it might be simpler to draw an appropriately sized invisible rectangle behind each character and attach event listener to them instead, or as well as, the character paths (remembering to include the data attribute).

Why is my SVG sprite id reference not loading the corresponding svg?

I am using the SVG Sprite system in vanilla JS to load two SVGs on to my page. I have contained both SVGs in one icons.svg file using https://svgsprit.es/ service:
<svg width="0" height="0" class="hidden">
<symbol viewBox="0 0 0 0" id="delete">
<symbol viewBox="0 0 0 0" id="delete">
<symbol fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="edit">
<path d="M 18.414062 2 C 18.158062 2 17.902031 2.0979687 17.707031 2.2929688 L 15.707031 4.2929688 L 14.292969 5.7070312 L 3 17 L 3 21 L 7 21 L 21.707031 6.2929688 C 22.098031 5.9019687 22.098031 5.2689063 21.707031 4.8789062 L 19.121094 2.2929688 C 18.926094 2.0979687 18.670063 2 18.414062 2 z M 18.414062 4.4140625 L 19.585938 5.5859375 L 18.292969 6.8789062 L 17.121094 5.7070312 L 18.414062 4.4140625 z M 15.707031 7.1210938 L 16.878906 8.2929688 L 6.171875 19 L 5 19 L 5 17.828125 L 15.707031 7.1210938 z"></path>
</symbol>
</symbol>
</symbol>
<symbol viewBox="0 0 0 0" id="edit">
<symbol fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="edit">
<path d="M 18.414062 2 C 18.158062 2 17.902031 2.0979687 17.707031 2.2929688 L 15.707031 4.2929688 L 14.292969 5.7070312 L 3 17 L 3 21 L 7 21 L 21.707031 6.2929688 C 22.098031 5.9019687 22.098031 5.2689063 21.707031 4.8789062 L 19.121094 2.2929688 C 18.926094 2.0979687 18.670063 2 18.414062 2 z M 18.414062 4.4140625 L 19.585938 5.5859375 L 18.292969 6.8789062 L 17.121094 5.7070312 L 18.414062 4.4140625 z M 15.707031 7.1210938 L 16.878906 8.2929688 L 6.171875 19 L 5 19 L 5 17.828125 L 15.707031 7.1210938 z"></path>
</symbol>
</symbol>
</svg>
When I add the HTML through dynamic JS I use:
const container = document.createElement('div');
//add edit icon
const editIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
editIcon.classList.add('icon');
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', 'images/icons.svg#edit');
editIcon.appendChild(use);
//add delete icon
const deleteIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
deleteIcon.classList.add('icon');
const use2 = document.createElementNS("http://www.w3.org/2000/svg", "use");
use2.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', 'images/icons.svg#delete');
deleteIcon.appendChild(use2);
container.appendChild(editIcon);
container.appendChild(deleteIcon);
but only the edit icon appears successfully? I notice that the delete SVG has two symbol tags each with an id - am I referencing it wrong in my JS?
**and yes I know xlink:href is deprecated! this is just a small project for learning so browser compatibility is not highly important
Your sprite file is wonky. You should not have multiple nested <symbol> elements.
<symbol viewBox="0 0 0 0" id="delete">
<symbol viewBox="0 0 0 0" id="delete">
Each icon should only have one.
The reason your "delete" icon is not showing is because, when the browser tries to find the "delete" symbol, it has two that have id="delete". That is illegal for a start, because id attributes must be unique.
It will choose one of them. In this case it doesn't matter which one it chooses. That's because all that either "delete" symbol contains is a <symbol> element. Which is effectively nothing, because <symbol> elements by themselves are not rendered. They are only rendered when referenced by a <use>.
You got lucky with the "edit" symbol, because you have three of those. But luckily your browser is probably picking the first id match it finds. And for id="edit" the first one is three levels down inside the nested <symbol id="delete"> ones.
In other words, your sprite file looks like this to the browser
<svg width="0" height="0" class="hidden">
<symbol viewBox="0 0 0 0" id="delete">
</symbol>
<symbol fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="edit">
<path d="M 18.414062 2 C 18.158062 2 17.902031 2.0979687 17.707031 2.2929688 L 15.707031 4.2929688 L 14.292969 5.7070312 L 3 17 L 3 21 L 7 21 L 21.707031 6.2929688 C 22.098031 5.9019687 22.098031 5.2689063 21.707031 4.8789062 L 19.121094 2.2929688 C 18.926094 2.0979687 18.670063 2 18.414062 2 z M 18.414062 4.4140625 L 19.585938 5.5859375 L 18.292969 6.8789062 L 17.121094 5.7070312 L 18.414062 4.4140625 z M 15.707031 7.1210938 L 16.878906 8.2929688 L 6.171875 19 L 5 19 L 5 17.828125 L 15.707031 7.1210938 z"></path>
</symbol>
</svg>
Fix the nested symbol problem. It looks like you are passing, to that utility, SVG files that already contain only symbols. So it is simply wrapping symbols in other symbols.
I expect you should be passing renderable SVGs to that utility.
If your SVG files don't render anything when opened with a browser, they are probably already a "sprite sheet". Only use SVGs that display something when opened in a browser.
For your immediate problem, try this manually fixed file instead.
<svg width="0" height="0" class="hidden">
<symbol fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="delete">
<path d="M 18.414062 2 C 18.158062 2 17.902031 2.0979687 17.707031 2.2929688 L 15.707031 4.2929688 L 14.292969 5.7070312 L 3 17 L 3 21 L 7 21 L 21.707031 6.2929688 C 22.098031 5.9019687 22.098031 5.2689063 21.707031 4.8789062 L 19.121094 2.2929688 C 18.926094 2.0979687 18.670063 2 18.414062 2 z M 18.414062 4.4140625 L 19.585938 5.5859375 L 18.292969 6.8789062 L 17.121094 5.7070312 L 18.414062 4.4140625 z M 15.707031 7.1210938 L 16.878906 8.2929688 L 6.171875 19 L 5 19 L 5 17.828125 L 15.707031 7.1210938 z"></path>
</symbol>
<symbol fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="edit">
<path d="M 18.414062 2 C 18.158062 2 17.902031 2.0979687 17.707031 2.2929688 L 15.707031 4.2929688 L 14.292969 5.7070312 L 3 17 L 3 21 L 7 21 L 21.707031 6.2929688 C 22.098031 5.9019687 22.098031 5.2689063 21.707031 4.8789062 L 19.121094 2.2929688 C 18.926094 2.0979687 18.670063 2 18.414062 2 z M 18.414062 4.4140625 L 19.585938 5.5859375 L 18.292969 6.8789062 L 17.121094 5.7070312 L 18.414062 4.4140625 z M 15.707031 7.1210938 L 16.878906 8.2929688 L 6.171875 19 L 5 19 L 5 17.828125 L 15.707031 7.1210938 z"></path>
</symbol>
</svg>

Convert SVG Paths to Rect

I want to auto generate an imagemap type of result for a raster image. I was able to supply this image as a PNG:
The original SVG for this looks like this:
<svg width="580" height="400" xmlns="http://www.w3.org/2000/svg">
<g>
<rect fill="#fff" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<rect id="svg_1" height="67" width="54" y="119.5" x="125.5" stroke-width="1.5" stroke="#000" fill="#fff"/>
<rect id="svg_3" height="67" width="54" y="119.5" x="180.5" stroke-width="1.5" stroke="#000" fill="#fff"/>
</g>
</svg>
Once I traced it using the library: https://github.com/jankovicsandras/imagetracerjs I get back path data like this:
<svg width="156" height="114" version="1.1" xmlns="http://www.w3.org/2000/svg" desc="Created with imagetracer.js version 1.2.3" >
<path fill="rgb(60,60,60)" stroke="rgb(60,60,60)" stroke-width="1" opacity="1" d="M 20 20 L 131 20 L 131 89 L 20 89 L 20 20 Z M 22 22 L 22 87 L 74 87 L 74 22 L 22 22 Z M 77 22 L 77 87 L 129 87 L 129 22 L 77 22 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 0 0 L 156 0 L 156 114 L 0 114 L 0 0 Z M 20 20 L 20 89 L 131 89 L 131 20 L 20 20 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 22 22 L 74 22 L 74 87 L 22 87 L 22 22 Z " />
<path fill="rgb(255,255,255)" stroke="rgb(255,255,255)" stroke-width="1" opacity="1" d="M 77 22 L 129 22 L 129 87 L 77 87 L 77 22 Z " />
</svg>
I would like to go back to the rect or polygon method so I can measure the area of each object so that if there were traced text I could exclude it / flatten it by saying it's total area is lower than allowed as a polygon / rect object.
Is there a way to convert the path data back to where I have 2 separate objects? I want to be able to overlay the results over the original image and allow targeting each square
I try to answer your question as best as I can, but there are multiple solutions here.
If you force imagetracerjs to use only straight line edges (with qtres = 0.00001) , then SVG path elements are polygons, their coordinates are defined in the d attribute: d="M 20 20 L 131 20 ... " in the first example. (More info: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d)
But 1 d attribute is often not just 1 polygon. The shape can include holes, and they are represented as smaller polygons. (More info: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule)
You can extract the d attributes with e.g. Regex, split them by the Z tags (which mean roughly: this shape ends, a hole shape begins) , then use the Gauss's area formula (https://en.wikipedia.org/wiki/Shoelace_formula) on the coordinates to get the areas. You might need to subtract the areas of the holes from the area of the parent shape.
You have 4 separate objects on your image, but some are not trivial. I try to "translate" your SVG result so you can decide which path to process.
ImageTracer output is sorted by colors first. The 1. path is the blackish frame around the two smaller rectangles, technically this is a blackish rectangle with two rectangle holes. The 2. path is white frame around the blackish frame, technically a white rectangle with a big rectangle hole. The 3. and 4. paths are the smaller white rectangles, which might be relevant for you.
There's an alternative to splitting and parsing the SVG string, but it's a bit undocumented, sadly. You can get a tracedata object first, process it, and optionally render it to SVG string later. (More info: https://github.com/jankovicsandras/imagetracerjs#examples )
var options = {qtres:0.0001};
ImageTracer.imageToTracedata(
'example.png',
function(tracedata){
// color layers loop
for(var i=0; i<tracedata.layers.length; i++){
// paths loop
for(var j=0; j<tracedata.layers[i].length; j++){
var thispath = tracedata.layers[i][j];
// processing this path if it's not a hole
if( !thispath.isholepath ){
// accumulating coordinates in [ [x,y] , [x,y] , ... ] polygon
var thispolygon = [];
thispolygon.push( [ thispath.segments[0].x1 , thispath.segments[0].y1 ] );
for(var k=0; k<thispath.segments.length; k++){ thispolygon.push( [ thispath.segments[k].x2 , thispath.segments[k].y2 ] ); }
// TODO: calculate thispolygon area with https://en.wikipedia.org/wiki/Shoelace_formula here
}
}// End of paths loop
}// End of color layers loop
},// End of imageToTracedata callback()
options
);// End of ImageTracer.imageToTracedata()
I hope these help. :)

RaphaelJS - standardize svg path shape sizes

I'm building a site that has an svg map of the United States using the very easy usmap jquery plugin which uses Raphael. I have a click event that fires if you click an individual state.
On that occasion, I go to the same US States object to render a single state. The problem is that the paths for each state in the list are relative to each other as rendered in the entire US map of states.
But my requirements are that I need to render each state about the same size as each other when I'm viewing a single state.
I know I can put a multiplier on the path parameters and the overall state will increase/decrease, but I'm looking for a method to determine this multiplier dynamically.
I also tried to use setViewBox, hoping it would increase/decrease the <path> to fill the area. But it didn't work, as far as I could tell.
So, either I need to make the <path> fill the space or I need to figure out a multiplier to apply to scale the <path>
// state coords pulled from All states svg file
var statePolygon = "M 93.573239,6.3617734 L 97.938071,7.8167177 L 107.6377,10.564946 L 116.2057,12.504871 L 136.2516,18.162988 L 159.20739,23.821104 L 174.36801,27.215777 L 173.36373,31.099829 L 169.27051,44.909503 L 164.81238,65.714155 L 161.63584,81.854036 L 161.28429,91.232806 L 148.10315,87.33877 L 132.53264,83.955591 L 118.86585,84.551329 L 117.28528,83.01913 L 111.95881,84.916253 L 107.9821,84.665645 L 105.2606,82.904814 L 103.68223,83.430208 L 99.476903,83.201576 L 97.601755,81.829846 L 92.824862,80.093194 L 91.382778,79.886558 L 86.397035,78.560984 L 84.614222,80.069004 L 78.922841,79.726077 L 74.101997,75.931831 L 74.30643,75.131651 L 74.374575,67.197996 L 72.248826,63.31142 L 68.133618,62.57938 L 67.768708,60.225014 L 65.2543,59.597968 L 62.372763,59.063086 L 60.594498,60.033049 L 58.331251,57.123161 L 58.654572,54.213272 L 61.4028,53.889951 L 63.019405,49.84844 L 60.432837,48.716816 L 60.594498,44.998625 L 64.959331,44.351984 L 62.211103,41.603756 L 60.756158,34.490695 L 61.4028,31.580807 L 61.4028,23.659444 L 59.624535,20.426234 L 61.887782,11.049927 L 63.989368,11.534908 L 66.414275,14.444797 L 69.162503,17.031364 L 72.395712,18.97129 L 76.922205,21.072876 L 79.993756,21.719518 L 82.903645,23.174462 L 86.298518,24.144425 L 88.561764,23.982765 L 88.561764,21.557857 L 89.855048,20.426234 L 91.956634,19.13295 L 92.279955,20.264574 L 92.603276,22.042839 L 90.340029,22.52782 L 90.016708,24.629406 L 91.794974,26.084351 L 92.926597,28.509258 L 93.573239,30.449183 L 95.028183,30.287523 L 95.189843,28.994239 L 94.219881,27.700955 L 93.734899,24.467746 L 94.543201,22.689481 L 93.89656,21.234537 L 93.89656,18.97129 L 95.674825,15.41476 L 94.543201,12.828192 L 92.118294,7.9783781 L 92.441615,7.1700758 z M 84.116548,12.340738 L 86.137312,12.179078 L 86.622294,13.553197 L 88.158073,11.936582 L 90.502155,11.936582 L 91.310458,13.472361 L 89.774678,15.169801 L 90.42133,15.978114 L 89.693853,17.998875 L 88.319734,18.403021 C 88.319734,18.403021 87.430596,18.483857 87.430596,18.160536 C 87.430596,17.837215 88.885551,15.573958 88.885551,15.573958 L 87.188111,15.008141 L 86.86479,16.463095 L 86.137312,17.109737 L 84.60153,14.84648 z";
var width = 175,
height = 125,
paper = Raphael(document.getElementById("statemap"), 350, 250);
paper.setViewBox(0, 0, width, height, true);
// paper.canvas.setAttribute('preserveAspectRatio', true);
paper.path(statePolygon);
Anyone have any ideas?
Update
So, I figured out (thanks, Jordan) that you can get a "size" dimension by capturing the difference between all of the X coord (can be done with y, if you'd rather.) By calculating the diff between the lowest X and the highest X you can get a size.
If you then figure out what a 'normal' size would be, you can then determine a multiplier to apply to each state as a fraction of the state's size relative to the 'normal' one.
In my example, the diff in Kansas is 128. CT is 29, so CT needs a multiplier of 4.41 to bring it up to the relative size of Kansas. TX is 242, so it needs a 0.53 multiplier to bring it down.
This normalizes the sizes of the states. If you then, using Raphael, need to increase/decrease this, you can use the transform function:
p = paper.path(statePolygonNew);
var scale = 1.5;
p.transform("t" + scale*52 + "," + scale*32 + " s" + scale + "");
Thanks,
Scott
There are a number of different ways of approaching this. Here are two of them.
Match the viewbox to the existing path
You used this method incorrectly when you were first seeking a solution. The viewbox is essentially a mechanism for dynamically scaling the way that coordinates in the SVG are translated into coordinates on a screen. By setting the viewbox to the dimensions of the svg in screen coordinates, you are effectively telling it to do nothing -- that's what it does by default.
Instead, try setting the viewbox to the bounding box of the target path:
var statePathString = "..."; // your path here
var statePath = paper.path( statePathString ).attr( { /* your attributes */ } );
var bbox = statePath.getBBox();
paper.setViewBox( bbox.x, bbox.y, bbox.width, bbox.height, true );
This instantiates the path for the state, calculates its dimensions, and zooms the viewbox in to look at just that range of the SVG's coordinate systems. You may need to add additional logic to preserve aspect ratios and add margin, but this is definitely the easiest approach.
Transform the path to fit your existing viewbox
If you do opt to avoid viewbox manipulation -- there are a few valid reasons -- then you can adjust the path to match the default viewing rectangle using a handful of Raphael's built-in utility functions. Here's the approach I'd use, given existing variables width and height reflecting the dimensions of your paper:
var bbox = Raphael.pathBBox( statePathString ); // Handy function to retrieve a bounding box without instantiating a path
// Calculate larger of the two dimensional quotients
var scale = Math.max( bbox.width / width, bbox.height / height );
// Finally, transform the path such that it is translated to have its origin at 0,0 and scaled in such a way that it will fill the SVG
var transformedPathString = Raphael.transformPath( statePathString,
[ "T", 0 - bbox.x, 0 - bbox.y, /* Shift up and left so upper-left extent corresponds to 0,0 */
"S", 1.0 / scale, 1.0 / scale, 0, 0 ] );
At this point you can do whatever you'd like to do with transformedPathString -- presumably, pass it to paper.path and style it up.

RaphaelJS HTML5 Library pathIntersection() bug or alternative optimisation (screenshots)

I have a chart generated using RaphaelJS library. It is just on long path:
M 50 122 L 63.230769230769226 130 L 76.46153846153845 130 L
89.6923076923077 128 L 102.92307692307692 56 L 116.15384615384615 106 L 129.3846153846154 88 L 142.6153846153846 114 L 155.84615384615384 52
L 169.07692307692307 30 L 182.3076923076923 62 L 195.53846153846152
130 L 208.76923076923077 74 L 222 130 L 235.23076923076923 66 L
248.46153846153845 102 L 261.6923076923077 32 L 274.9230769230769 130 L 288.15384615384613 130 L 301.38461538461536 32 L 314.6153846153846
86 L 327.8461538461538 130 L 341.07692307692304 70 L
354.30769230769226 130 L 367.53846153846155 102 L 380.7692307692308 120 L 394 112 L 407.2307692307692 68 L 420.46153846153845 48 L
433.6923076923077 92 L 446.9230769230769 128 L 460.15384615384613 110 L 473.38461538461536 78 L 486.6153846153846 130 L 499.8461538461538 56
L 513.0769230769231 116 L 526.3076923076923 80 L 539.5384615384614 58
L 552.7692307692307 40 L 566 130 L 579.2307692307692 94 L
592.4615384615385 64 L 605.6923076923076 122 L 618.9230769230769 98 L 632.1538461538461 120 L 645.3846153846154 70 L 658.6153846153845 82 L 671.8461538461538 76 L 685.0769230769231 124 L 698.3076923076923 110 L 711.5384615384615 94 L 724.7692307692307 130 L 738 130 L 751.2307692307692 66 L 764.4615384615385 118 L 777.6923076923076 70 L 790.9230769230769 130 L 804.1538461538461 44 L 817.3846153846154 130 L 830.6153846153845 36 L 843.8461538461538 92 L 857.076923076923 130 L 870.3076923076923 76 L 883.5384615384614 130 L 896.7692307692307 60 L 910 88
Also below these chart I have a jqueryUI slider of the same width (860px) and centered with the chart. I want when I move the slider to move a dot on the chart accordingly with the slider position. See attached screenshot:
As you can see it seems to work fine. I've implemented this behaviour using the pathIntersection() method. On the slide event at each ui.value (x coordinate) I intersect my chartPath (the one from above) with a vertical straight line at the x coordinate.
But still there are some problems. One of them is that it runs very hard, and it kinda freezes sometimes.. and very weird sometimes it doesn't seem to intersect at all even it should.. I'll example below 2 cases I identified:
M 499.8461538461538 0 L 499.8461538461538 140
M 910 0 L 910 140
Could you please explain why this intersect behaviour happens (it should return a dot).. and the worst part it seems like it happens randomly.. if I use another chartdata.
Also if you can identify another (better) solution to syncronise the slider position with the dot on the chart.. would be perfect.
I thought about using Element.getPointAtLength(length), but I don't know how. I think I should save the pathSegments and for each to compute the start Length and the finish Length.
I think it would be better to use the source data to find the intersection point manually. Shouldn't be too hard as the points are already sorted by x.
Path intersection appears to fail when you check the intersection at one of the vertices, which can be considered a bug but is not really surprising, since the library was never intended for computation geometry in the first place.
By way of quick hack you could also try slightly modifying the x value when pathIntersection fails to produce a single value and checking again, e.g. try 499.8461538461538 + 0.0001 * (2 * Math.random() - 1) instead of just 499.8461538461538. You might need to try several times before you get a proper answer, but here it is unlikely. I believe the technique is generally known as perturbation.

Categories

Resources