Wrong SVGSVGElement width in Firefox - firefox

On th following document:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width = "100%"
height = "100%"
id="pic"
version="1.1"
style="background-color:blue"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xlink="http://www.w3.org/1999/xlink">
</svg>
I'm trying to get the width value of the root SVGSVGElement:
document.getElementById ("pic").width.baseVal.value
Chromium says: 969
Firefox says: 1
Sure value maybe a little implementation dependent, but (what indeed must be independent) when i try to get a converted value:
var w = evt.target.width.baseVal;
w.convertToSpecifiedUnits (5);
alert(w.valueInSpecifiedUnits);
chrome gives again 969, but Firefox' answer is 1.
I need this value to adjust some elements in my scripts, but they don't work in Firefox.
How can i obtain the real value of width?

This is the way I got it to give consistent values cross-browser:
This was the way I fixed it:
var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
var svgCalculateSize = function (el) {
var gCS = window.getComputedStyle(el), // using gCS because IE8- has no support for svg anyway
bounds = {
width: 0,
height: 0
};
heightComponents.forEach(function (css) {
bounds.height += parseFloat(gCS[css]);
});
widthComponents.forEach(function (css) {
bounds.width += parseFloat(gCS[css]);
});
return bounds;
};
Not very pretty, but works.

2 years later this is still an issue..
I found a temporary solution:
var style = window.getComputedStyle(svg,null);
var svgWidth = style.getPropertyValue("width").slice(0, -2); // "1240px" -> "1240"
but in my case this is very expensive and the browser gets super slow..
so, has anyone got a better solution?
I also tried to convert "the magic 1" into a pixel width following this article:
https://developer.mozilla.org/en/docs/Web/API/SVGLength
but no luck either..

From Firefox 33 onwards you will be able to use getBoundingClientRect to get the width/height you want. Firefox 33 will be released on 14th October 2014 but you could try a nightly right now if you want to.

Related

Alternative to unescape() and saving images as base64

I am trying to convert svg to png, using the code here as basis of my conversion.
Please note the following code (it has been shortened to only what's relevant to the error that I will describe later):
let svgData = new XMLSerializer().serializeToString(target); // 'target' is an svg element that is passed to us
let canvas = document.createElement('canvas');
let ctxt = canvas.getContext('2d');
let img = document.createElement('img');
img.setAttribute(
'src',
'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgData)))
);
ctxt.drawImage(img, 0, 0);
The above code works fine. However there is a problem with it. Notice that here is this line of code:
'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgData)))
In this line of code, the unescape() function is used, however it is deprecated, and I have to use an alternative. According to documentation, I should use decodeURIComponent(), but this not a viable solution, because when I update the code above with decodeURIComponent(), the result is:
'data:image/svg+xml;base64,' + window.btoa(decodeURIComponent(encodeURIComponent(svgData)))
So basically I would be encoding then decoding. Which is the same as doing this:
'data:image/svg+xml;base64,' + window.btoa(svgData)
Now, with the new updated code, if the source svg contains only English characters, everything is OK. However, if an svg contains characters other than English (e.g., contains the Japanese word トマト), then the next line of code, ctxt.drawImage(img, 0, 0); , throws an error. The error message is DOMException: String contains an invalid character.
So, to summarise so far:
the original code works fine with both English and non-English characters, but uses the unescape() function which is depracated. So I should use something else.
The updated code, which does not encode or decode, causes an exception when using non-English characters.
Now, question 1, what do I do to be able to create an image from an svg that contains non-English characters using base64?
As an alternative solution, I did this
'data:image/svg+xml;utf8,' + svgData
Basically, I did not use base64, but rather utf8. This worked just fine, but I am not sure if it is the correct solution.
So, question 2, is using utf8 a common practice? Is there any issue with it as opposed to base64?
Thank you.
According to the comment below from #RobertLongson , I updated the code to:
'data:image/svg+xml;utf8,' + encodeURIComponent(svgData)
This worked in svg elements containg either English and non-English characters.
Now, question 3, is this correct?
Apologies about the basic question. I am not familiar with this. Thanks.
With this example I'm trying to avoid using the mentioned functions for converting to base64 and instead use a FileReader and particular the readAsDataURL function for returning the data URI. I know the code is a bit more complicated with the callbacks, but it works.
As I understand Google Translate トマト translates into 🍅.
let svg01 = document.getElementById('svg01');
let canvas = document.getElementById('canvas01');
let img = document.getElementById('img01');
let ctx = canvas.getContext('2d');
canvas01.width = svg01.getAttribute('width');
canvas01.height = svg01.getAttribute('height');
let svgData = new XMLSerializer().serializeToString(svg01);
// create a File object
let file = new File([svgData], 'svg.svg', {
type: "image/svg+xml"
});
// and a reader
let reader = new FileReader();
reader.addEventListener('load', e => {
let img = new Image();
// wait for it to got load
img.addEventListener('load', e => {
// update canvas with new image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(e.target, 0, 0);
// create PNG image based on canvas
img01.src = canvas.toDataURL("image/png");
});
img.src = e.target.result;
});
// read the file as a data URL
reader.readAsDataURL(file);
<p>SVG embedded:</p>
<svg id="svg01" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 20" width="300" height="60">
<rect width="100" height="20" fill="silver" rx="5"/>
<text font-size="7" x="50" y="10" text-anchor="middle"
dominant-baseline="middle">Japanese word: トマト 🍅</text>
</svg>
<p>Canvas image:</p>
<canvas id="canvas01"></canvas>
<p>PNG image:</p>
<p><img id="img01" /></p>

How to change the height of d3 observable notebook embed

so I understand that I can change the fixed width instead of a responsive width, by importing Library, then re-assigning the width value. But I also want to change the height and including ,height: 500 doesn't seem to work. What am I doing wrong and what other way is there to embed the d3 observable notebook chart with customizable width and height without using an iframe?
<div class="chart"></div>
<p>Credit: <a href="https://observablehq.com/#tripletk/mmcovid19-confirmedcases">Myanmar COVID-19 Total Lab
Confirmed Cases by Timmy Kyaw</a></p>
<script type="module">
import {
Runtime,
Inspector,
Library
} from "https://cdn.jsdelivr.net/npm/#observablehq/runtime#4/dist/runtime.js";
const runtime = new Runtime(Object.assign(new Library, {
width: 500
}));
import define from "https://api.observablehq.com/d/1adf72a9a09835c3.js?v=3";
const main = runtime.module(define, name => {
if (name === "chart") return Inspector.into(".chart")();
});
</script>
Width works differently than height in Observable notebooks. You’ve already found how to change width: it’s a reactive variable that you can override with a constant like you’re doing; by default it uses with width of the document body.
For height, or any other value you want to inject like data or zipCode, you should use main.redefine("height", 500). (I’m using 200 in the example below, just change it to 500.)
<div class="chart"></div>
<p>Credit: <a href="https://observablehq.com/#tripletk/mmcovid19-confirmedcases">
Myanmar COVID-19 Total Lab Confirmed Cases by Timmy Kyaw</a></p>
<script type="module">
import {
Runtime,
Inspector,
Library
} from "https://cdn.jsdelivr.net/npm/#observablehq/runtime#4/dist/runtime.js";
const runtime = new Runtime(Object.assign(new Library, {
width: 500
}));
import define from "https://api.observablehq.com/d/1adf72a9a09835c3.js?v=3";
const main = runtime.module(define, name => {
if (name === "chart") return Inspector.into(".chart")();
});
main.redefine("height", 200)
</script>
Often it’s useful to make the figure fill an enclosing div instead of hardcoding a width and height: see this example of that approach. Also for reference, the docs have more information about both the redefine and new Library techniques.

How can I change react-map-gl icons?

I'm trying to customize my web application's map, I'm using react-map-gl (Uber opensource code) I try to change the map's icon pins but I could not figure out, what does the string code ICON mean?
import React, { PureComponent } from 'react';
const ICON = M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
C20.1,15.8,20.2,15.8,20.2,15.7z;
const pinStyle = {
cursor: 'pointer',
fill: '#d00',
stroke: 'none'
};
export default class CityPin extends PureComponent {
render() {
const { size = 20, onClick } = this.props;
return (
<svg
height={size}
viewBox="0 0 24 24"
style={{ ...pinStyle, transform: `translate(${-size / 2}px,${-size}px)` }}
onClick={onClick}
>
<path d={ICON} />
</svg>
);
}
}
What does it mean all those numbers in ICON const? How can I change the style based on this code? Please help, thanks :)
All of the gibberish that is the ICON constant is SVG Path notation and is actually drawing your current symbol. If you want to learn more about it, here is another resource. There are even websites that can help you build your own SVG path string, Option 1 Option 2, and searching the web will pull up many more.
Note that you likely need to update the viewbox numbers to fit your new thing once you have the symbol you want. It otherwise chops off the symbol at those dimensions.
In trying to figure this out, my example update was:
const ICON = 'm 10 35 l 15 30 l 15 -30 A 20 20 180 1 0 10 35 z'
with a viewbox in the svg tag of:
viewBox="-8 0 55 65"
EDIT:: you can also use any image as explained in the other answers. There is an article on using custom images at Medium with a good explanation and more details.
You mentioned you're using react-map-gl, so you could use the Marker component to change your icon pins.
Is there a specific image you'd like to use for your icons, say a .png file? If so, add that image to the directory called "public." Then, in your map where you create the markers, you can display this specific image.
For example, if you have an image called mapicon.png, you'd add that to your public folder. Then, when you're creating your markers, you could do something like this.
import ReactMapGL, {Marker} from 'react-map-gl';
<Marker key={} latitude={} longitude={}>
<img
src="mapicon.png"
alt="map icon"
/>
</Marker>
From what I understand, the string is just a reference to where the icon image is stored. Hope this helps!
You can create a custom map marker with an SVG file. If you are using create-react-app, you can import your svg icon as a component like this:
import { ReactComponent as Pin } from '../../images/map-marker-icon.svg';
Then, in your example code above you can replace the
<path d={ICON} />
with this component. Here is an example:
export default class MapPin extends PureComponent {
render() {
<Marker longitude={this.props.longitude} latitude=this.props.latitude}>
<svg
onClick={() => onClick()}
viewBox="0 0 60 100"
enable-background="new 0 0 60 100">
<Pin/>
</svg>
</Marker>
));
}
}

OpenLayers 3: Scaling canvas with text with 125% DPI since v4.x

I've created two identical fiddles with different OpenLayers-Versions:
OpenLayers v3.18.0 and OpenLayers 4.1.1
The objective is to export a PNG in high resolution. I didn't include the actual exporting of the file. It is explained here if interested.
It all worked fine up to the newer version (I think until a 4.x version).
If you have the DPI-Setting in windows on 100%, both fiddles do the same - but if you change your DPI-Setting to 125%, the latter fiddle does not update the text Some text! and it becomes really small and is located in the wrong place.
The map stays like that, until I click into it (or I call map.updateSize()). So I thought, I add map.updateSize() at the end of precompose - but no matter where I do it, the exported image is wrong as the updateSize() seems to be async and happening AFTER postcompose.
I didn't find a breaking change regarding this issue. Am I overlooking something or is it a bug? Any suggestion for a workaround?
Thanks to the issue I opened on github I came up with the following solution. The most interesting part is the creation of a second ol.Map with a desired pixelRatio:
saveToFile = function (fileName, opt_ChangeSize, opt_PixelRatio, opt_DelayRenderPromise) {
var newMapComponent,
originalSize = mapComponent.getSize();
opt_ChangeSize = opt_ChangeSize || { width: originalSize[0], height: originalSize[1] };
var div = $(document.createElement("div"));
div.attr('id', 'DIV_SaveToFile_Renderer_' + (new Date()).getTime());
div.css('position', 'absolute');
div.css('top', '0');
div.css('left', '0');
div.css('visibility', 'hidden');
div.css('width', opt_ChangeSize.width + 'px');
div.css('height', opt_ChangeSize.height + 'px');
$('body').append(div);
newMapComponent = new ol.Map({
target: div[0].id,
layers: mapComponent.getLayers(),
pixelRatio: opt_PixelRatio,
view: mapComponent.getView()
});
// opt_DelayRenderPromise in this case returns when loading of some features has completed. It could also be postrender of map or whatever.
$q.when(opt_DelayRenderPromise).then(function () {
$timeout(function () {
var data,
canvas = div.find('canvas')[0];
data = canvas.toDataURL('image/png');
data = data.replace(/^data:image\/(png);base64,/, "");
MyUtilities.SaveBlobFromBase64(data, fileName);
div.remove();
mapComponent.setSize(originalSize);
mapComponent.renderSync();
});
});
mapComponent.setSize([opt_ChangeSize.width, opt_ChangeSize.height]);
mapComponent.renderSync();
};

MouseOver or hover() or css() for changing positions

I have an image on my page and want that it change the position on mouseover or on click, so I tried several things but I cant find the right start.
So I started to change the top-position but that's not working, I would like that on mouseover the image it jumps to position x/y (maybe random x/y) if I mouseover it again it will jump to another position.
$('#image').one('click', function () {
$(this).css( 'top' : '+=200' );
});
But that's not working so please someone could give me some input that I can figure out what to do?
Note that while setting values for top, jQuery will not automatically get the value of top set previously and add 200 to it(like a normal programming language). You could have seen this as an error in executing if you try to see in a console. You can get the value of top first and then add 200px to it and then set it again.
This should work:
$('#image').one('click', function () {
var top = parseInt($(this).css("top"));
$(this).css( 'top' , top+ 200);
});
I had to parse the string to an integer returned by css, you could also substitute that extra operation by some method that returns only the value and not the value with "px" added to it, dunno if there is one already.
Try something like this:
<html>
<head>
<script type="text/javascript">
function switchPos(obj)
{
var divobj = document.getElementById('div1')
var x = divobj.offsetHeight - obj.height - 5;
var y = divobj.offsetWidth - obj.width - 5;
var randomx = Math.floor(Math.random()*x+1);
var randomy = Math.floor(Math.random()*y+1);
obj.style.top = randomx + 'px';
obj.style.left = randomy + 'px';
}
</script>
</head>
<body>
<div id="div1" style="width: 800px; background: red;">
<img src="image.jpg" style="position:relative;" onmouseover="switchPos(this);" onmouseout="switchPos(this);"/>
</div>
</body>
</html>

Resources