retrieving the aspect ratio of an svg <image> - javascript

TLDR;
given this svg element:
<image width="30" height="48" x="3.75" y="6" href="http://some/image.jpg">
How can I retrieve the image's actual height and width (seeing as it is defined in part by the image's aspect ratio).
I have a d3js script that draws a bunch of <rect>s and a bunch of <image>s.
Now stuff is laid out so that the images fall inside the rects, as though the rects were borders. There is other stuff inside the rects too.
Now each of these images has it's own unique and special aspect ratio and this bothers me because it means each of the rects then has a different amount of blank space. This is untidy.
To avoid this I want to load the images then get the actual image dimensions and then adjust the positions and sizes of the surrounding goodies. The crux of the matter is getting the actual image sizes. How can I do this?
I've googled around a bit and spent some quality time with the debugging console to no avail. Just nothing comes up. I'll keep hunting but an answer would be really nice.

First, set the width attribute only, keep height unspecified.
Then, call the getBBox for the image element.
Note that image box is available after it's properly rendered by the SVG
const image = parent.append('image').attr('xlink:href', url).attr('width', 100);
setTimeout(() => {
const box = image.node().getBBox();
const ratio = box.width / box.height;
}, 0);

This is the best I can come up with. I would be surprised and horrified if there isn't an easier way. Anyway:
add a normal html <img> element with a suitable src.
use js to fetch the image height and width
remove the extra html
Ugly but it works...
Here's some code:
var oImage = document.createElement("img");
oImage.setAttribute("src",sUrl);
document.body.appendChild(oImage);
var iWidth = oImage.width;
var iHeight = oImage.height;
oImage.remove();

Related

Create.js/Easel.js - createjs.Bitmap() - Getting SVG to load at original size?

I am using createjs.Bitmap to add an SVG to the stage. This is in Adobe Animate HTML5 Canvas which uses the Create.js/Easel.js frameworks. The project is using a responsive layout.
The problem is the SVG does not scale relative to the stage and other canvas objects around it. The SVG was created from Adobe Illustrator and it's size in Illustrator is 151.5px wide X 163.7px high.
var root = this;
var patientImagePath = data['patient_image_path']
patientImage = new createjs.Bitmap(patientImagePath);
patientImage.x = 96;
patientImage.y = 36.05;
patientImage.scaleX = 0.16;
patientImage.scaleY = 0.16;
root.stage.addChild(patientImage);
'Normal' view on large monitor showing the SVG (person's face). The surrounding elements are canvas objects...
and after reducing the browser size by dragging (not zoom...).
Also tried:
patientImage = new createjs.Bitmap(patientImagePath).set({scaleX: 0.16, scaleY: 0.16});
Makes no difference.
The odd thing is that the SVG code shows:
viewBox="0 0 151.5 163.7"
which is shows the correct width and height of the SVG, but if I don't apply any scale to the SVG on load in JS, it gets loaded in an enormous size, (virtually occupies the whole monitor...).
When I change the format to PNG (same dimensions), the PNG loads with createjs.Bitmap without any scaling at its original size, which is perfect. No issues and the PNG scales in relation to the other Canvas objects when the browser size is changed. Why is SVG different?
I want to use SVG, not PNG. It seems the SVG just gets scaled up significantly on load by create.js without reference to its original size in the SVG's viewbox...
So how do I get the SVG to load at it's original size??
See also my logged issue on GitHub on this for create.js
https://github.com/CreateJS/EaselJS/issues/1070
Without attaching or linking your SVG file it's hard to guess (beside one comment above).
I'll assume Adobe Illustrator (I don't have it anymore) saves SVG with viewBox attribute AND height and width attributes. What happens if you remove these two attributes and rely on browser (and create.js) automatic resizing of SVG?
If you are not aware of different SVG aspectRatio parameter values take a look on the specification:
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio

Responsive D3 Charts in React: ResizeObserver or the viewBox attribute?

I recently came across this video from a tutorial series on D3 + React. In this video, to resize the graph, a custom hook is created that uses ResizeObserver:
const useResizeObserver = ref => {
const [dimensions, setDimensions] = useState(null);
useEffect(() => {
const observeTarget = ref.current;
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
setDimensions(entry.contentRect);
});
});
resizeObserver.observe(observeTarget);
return () => {
resizeObserver.unobserve(observeTarget);
};
}, [ref]);
return dimensions;
};
I've always seen previously / used before the viewBox attribute to make SVG's responsive, as this article (and many others) discusses. My question is: is there a reason (performance, simplicity) to use this useResizeObserver custom hook over simply using viewBox? With viewBox, I don't need to create a custom hook, or have a wrapper div around my SVGs (as he explains in the video), or have to use refs... viewBox seems simpler. However, apparently ResizeObserver is a new API to get the dimensions of elements, and perhaps there are advantages to using it over viewBox that are not obvious.
Issues with viewBox
Although viewBox is the quickest solution, it has several downsides:
viewBox tries to satisfy the aspect ratio. If your chart doesn't have an aspect ratio of 1:1, this happens:
The chart didn't fit itself to the container. Instead viewBox kept the aspect ratio and added padding. We can stretch with preserveAspectRatio=none:
Chart (and text) scaling. Not preserving aspect ratio introduces another problem. If you use height and width, you can define your text to be a certain size in CSS - but not with viewBox. Take the case of a 0 0 600 300 viewBoxed chart fitting a 1200*600 page. In this case, everything gets scaled, including text, as you can see above: you can no longer set the font size. This won't happen just to text: other elements that don't stretch perfectly, like circles, will have the same issue.
At first glance, viewBox seems much simpler. But because of the footguns highlighted above, viewBox becomes very complex to use in practice.
Manual scaling
The solution is to size the chart manually using height and width, avoiding all the above issues. Say we have a chart component that renders itself based on height and width. The process becomes:
set the width and height on the first render, fit to the container. The container needs its own width and height set.
Use a ResizeObserver on the container, which will notify us when its size changes.
When the chart is resized, get the new height and width, and use them to render the chart.
When the height and width are set manually, we size only what needs to be scaled. For example, with the above bar chart:
We could set the bars' size relative to the height and width of the chart.
If we have text, we could position it relative to the height and width, but make the text size fixed, which would be impossible with viewBox, since it scales everything:
Conclusion
Use viewBox as a quick solution if you have a chart with elements that don't get affected by stretching: for example, with rectangles, or if you don't care about the above issues.
Use manual scaling if you have elements that cannot be stretched, like text or circles, and need text to stay the size you tell it to.
If you don't care about the aspect ratio issue, and can work around your text being scaled up or down, then viewBox can be the right choice, as long as you're aware of its pitfalls.
However, in practice, manual scaling ends up being the better choice in the vast majority of cases, because there are very little cases where you have only stretchable elements in a chart.
Sources
Images taken from Responsive SVG charts — viewBox may not be the answer.

Image within a div - faded edges

I have an image that was changed through Javascript and that image is placed on top of a background image, however when I look at it on the site you can see a clear square where the image was placed.
I wanted to know if there is a way to fade the images edges and have the background come in, making it look one.
Sidenote. I also wish to avoid JQuery at this time, If possible I would like to achieve this through javascript.
Here you can see the site page: http://imgur.com/a/vC9VV
Javascript code:
// attempting to move the images only
var y = document.getElementById("pic2");
// Sorted by pathing the image to the folder ( can use .. to get in the folder)
y.setAttribute("src", "../images/water_image1.png");
y.style.cssFloat = 'right';
// change the images to the same size as the replacing pic
y.style.width = "400px";
y.style.height = "250px";
// This is sorting out the alignment with the text
y.style.margin = "110px 20px 10px";
y.style.opacity = "0.8";
You could loop through each pixel of image and set the value of its alpha channel depending on distance to closest border of image, then render the result on <canvas>.
For easier but less generic approaches, take a look at this.

Is it possible to more accurately measure SVG text height?

I'm trying to measure the exact height used to render a given string with a given font with an SVG text tag.
I've tried using getBBox and getExtentOfChar, but the height returned by both of these includes some extra space above (and sometimes below) the actual text rendered.
http://upload.wikimedia.org/wikipedia/commons/3/39/Typography_Line_Terms.svg
Using the terms in this image, I'm trying to get the either the cap height + descender height of the text being rendered. Or, if that's not possible, just the cap height. Is there a good way to calculate these values?
Here's a quick codepen showing the extra space I'm talking about:
http://codepen.io/pcorey/pen/amkGl
HTML:
<div>
<svg><text>Hello</text></svg>
<svg><text>Age</text></svg>
</div>
JS:
$(function() {
$('svg').each(function() {
var svg = $(this);
var text = svg.find('text');
var bbox = text.get(0).getBBox();
svg.get(0).setAttribute('viewBox',
[bbox.x,
bbox.y,
bbox.width,
bbox.height].join(' '));
});
});
I understand that this is a fairly font-specific thing, so this might be totally impossible...
No. All the SVG DOM methods (getBBox(), getExtentOfChar()) are defined to return the full glyph cell height. That extra space above the cap height is allowance for taller glyphs - such as accented capitals. I think this is true for HTML DOM methods as well.
There are, however, JS libraries around which may be of use. For example:
https://github.com/Pomax/fontmetrics.js
I have not used this library myself, so I can't tell you how reliable or accurate it is.

What to do with SVG so it can be resized using Javascript?

Let's say I loaded SVG, displayed it in browser, and so far it is OK.
Now, I would like to resize it. All methods I found googling failed to give me the desired effect.
It is all variations of:
$svg.removeAttr('width')
.removeAttr('height')
//.attr('preserveAspectRatio','xMinYMin meet')
//.css('width',width+'px')
//.css('height',height+'px')
.css('width','100%')
.css('height','100%')
.attr('viewBox','0 0 '+width+' '+height)
;
Nothing. I get the view of desired size, but the image (SVG) is clipped to that view instead of being resized. Setting size via attributes does not change a thing. Like the size of that SVG is carved in stone.
Thanks to frenchie answer (see below) it appears JS tries hard to resize SVG and for basic SVG it just works. So the real question is -- what to do with SVG (real-world SVG, not just a rectangle) so Javascript would be able to resize it?
SVG I am testing: http://jsfiddle.net/D6599/
I created SVG with Inkscape, and this is how I load SVG in JS:
$.get(svg_url, function(data) {
// Get the SVG tag, ignore the rest
svg = $(data).find('svg')
.attr('id', 'SVG')
// Remove any invalid XML tags as per http://validator.w3.org
.removeAttr('xmlns:a')
[0];
on_load();
}, 'xml');
The code comes from How to change color of SVG image using CSS (jQuery SVG image replacement)?. What I have later on is svg element (I wrapped it using jQuery).
This is not the question how to load SVG, or what to do with cross-domain issue. SVG loads fine, it is the same domain as the script (my disk).
Your SVG markup should look like this:
<svg id="SomeID" height="20" width="20">......</svg>
So all you need to do is reset the css properties. Something like this should work:
$('#SomeID').css({'width':40, 'height' :40});
And if you can't change the id of the SVG markup then you can simply wrap it around a div like this:
<div id="SomeID"><svg>....</svg></div>
$('#SomeID').find('svg').css({'width': 40, 'height': 40});
Here's a jsFiddle:
function Start() {
$('#TheButton').click(ResizeSVG);
};
function ResizeSVG() {
$('#TheSVG').css({'width':40, 'height' :40});
}
$(Start);
Finally I found it. I hope it will change in future, but for now there is a crucial difference if you change attribute by editing actual SVG file, and changing SVG on-fly using Javascript.
In order JS could resize SVG you have to manually edit SVG file and change height and width attributes to following lines.
Before:
width="600px"
height="560px"
After:
viewBox="0 0 600 560"
width="100%"
height="100%"
Of course in your case the values will be different. Please note that you can change those attributes programmatically with Javascript but it does not work (at least not for me).
Now, you can load such file as usual and compute, let's say width/height ratio.
var vbox = $svg.attr('viewBox').split(' ');
var w_ratio = vbox[2]/vbox[3]; // width/height
Such fixed file is still readable by Inkscape, I don't know how about other programs.

Categories

Resources