Solved

Dynamic SVG content with Image and Lines

Posted on 2015-01-14
7
190 Views
Last Modified: 2015-01-19
Hi all,

I have a jQuery / knockout / html / javascript application....
Within it, I wish to load an image into a DIV and then overlay it with lines using SVG.
The image comes in the form of "data:image/png;base64,XXXXXXXXXXXXXXX"

I guess the HTML part looks like this:
<div id="content_div">
     <svg id="content_SVG">
          <!-- SVG Line Code Goes Here! -->
     </svg>
</div>   

Open in new window



The HTML for the lines looks straight-forward (for example)
<path d="M 0 0 L 50 10 " stroke="green" stroke-width="1"/>

Open in new window

but this should also be added dynamically from the javascript

How in the javascript would I therefore do the following:
load the image
resize the SVG (unless it will auto-scale to the page size)
draw some lines.

Thanks for any advice you can offer me

James
0
Comment
Question by:jatkin
  • 4
  • 3
7 Comments
 
LVL 33

Expert Comment

by:Slick812
Comment Utility
greetings  jatkin, , I am somewhat unclear at what you say about an operation that "loads an image into a DIV", I have never seen this done, Except to have an HTML image as <img src="image.png">  inside of the <div>. You say something about - "image comes in the form of "data:image/png;base64,XXXXXXXXXXXXXXX", I do not remember this type of binary image file used (base64), Except as an operation on a HTML canvas to Export it to a server through HTTP protocol.

I can overlay an SVG image on a <div>, so that javascript can draw on the top layer SVG.
I really do not see any reason, to have an image in  base64, it is very inefficient.

I would think the best way to do this would be to start with a "container" <div>, with the CSS position : relative;  .
Then have an <img id="img1" src="image.png"> , inside that <div>, you can change the image in the <img> at anytime with javascript. Then add a <div> to the "container <div>" and have that CSS position : absolute;  and place a <svg> image inside that to draw on. . . Although it may be easier to use a canvas, instead of an SVG depending on the various drawing or other considerations, (like erasing the drawn content).

Maybe if you can tell us what you are trying to have the page user do and see for the results you need, it would help us?
0
 
LVL 4

Author Comment

by:jatkin
Comment Utility
Hi,

You are right, I don't think I was totally clear on exactly what I am trying to achieve, sorry...

Essentially I have a page that should display an image of a map within a DIV.
On top of this, I need to draw a number of items, such as lines.

My current plan is to use something like this:
<div id="ItemPreview" width="100%">                            
    <svg id="MapContentSVG" width="100%">
        <!-- SVG Line Code Goes Here! -->
    </svg>
</div>

Open in new window


Within the Javascript, an appropriate map is loaded and displayed in the SVG using an AJAX request. This data comes back in the base64 form. I now have this code which does indeed load the image and display correctly, although the parent container seems to retain a default height of ~150px

var svg = document.getElementsByTagName('svg')[0]; //Get svg element
var svgimg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
svgimg.setAttribute('name', 'TheBackgroundImage');
svgimg.setAttribute('viewBox', "0 0 " + data.Map.ImageWidth + " " + data.Map.ImageHeight);                     
svgimg.setAttribute('width', '100%');
svgimg.setAttribute('height', '100%');
svgimg.setAttribute('preserveAspectRatio', 'xMaxYMax meet');
svgimg.setAttribute('id', 'TheBackgroundImage');
svgimg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', "data:image/png;base64," + data.Map.ImageDataGUI);
svg.appendChild(svgimg);

Open in new window


Now I can use the svg to draw on using normal path type elements.
These can be removed within the code and redrawn without affecting the map.

With this in place I need to resize the DIV I guess to have the scaled height based on the scaled image I wish to drawer.

Are you able to tell me what I need to add for this?

The SVG scaling does seem to work correctly, as if I shrink the page down you can see the image getting smaller, but it never gets bigger than the default 150px
0
 
LVL 33

Expert Comment

by:Slick812
Comment Utility
OK,  The Scaling you speak of, may be of a different variety, than the usual scaling of a Image in in an <img> tag. The SVG Image, is NOT a raster scan (based on thousands of pixels) image, it is a Vector position based image, (no pixel grid definitions), That defines Vector positions (all relative to the other positions based on the AREA covered by the SVG boundries, So unlike size change (scaling) in a jpeg or png image, (which goes through EVERY pixel and creates another pixel grid) , the vector image just will redraw the the elements (lines, arcs) by repositioning the drawing points relative to the area covered.

If you import a raster Image into a vector environment as you do with this -
    svg1.setAttributeNS('http://www.w3.org/1999/xlink', 'href', "data:image/png;base64," + data.Map.Imgdata);
that DOES NOT change the raster (X-pixel by Y-pixel) import, into a vector, so scaling may not be what you expect?

I really wounder about this line -
    svg.appendChild(svgimg);
My programming logic can Not get this, you seem to place a created SVG image, INSIDE of your first existing SVG image, as a svg inside of another svg. Never heard of that, seems like the two images should be separate? I tried to do web searches for info about using one svg as a child in another svg image. But could not find much?
__________________

I really wounder why you go to the trouble of converting a PNG file data to a base64 string, and send it through an Ajax HTTP string, It would be better if you let the browser do the "digital work" of changing an image

I would start with an Image as -
<img id="img1" src="first.png">

and then in the AJAX , get a new Image file address in the returned JSON from server, as  data.newImage   . . . .
then just change the Image in JS -
    document.getElementById("img1").src = data.newImage; // as string "second.png"

OR you can just set the background STYLE of a <div> to that Image address -
   document.getElementById("div1").style.backgroundImage="url('"+data.newImage+"')";

but this does NOT resize the that div (id="div1"), so you might need to also send down the NEW width and height with the AJAX JSON -
   var d1 =document.getElementById("div1");
  d1.style.width = data.width+"px";
  d1.style.height = data.height+"px";
  d1.style.backgroundImage="url('"+data.newImage+"')";

If your SVG image is Inside the <div> (id="div1"), it will scale to any change in size for the container <div>

This is my thinking on a way to do this. Have you tried or considered this method?
I can do some example code if you like.
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 33

Expert Comment

by:Slick812
Comment Utility
I did some copy-paste from some svg image interloo of mine, for this HTML page -

<!doctype html>
<html lang="en"><head><title>SVG Map Lines</title>
<style>
body{background:#e7fbff;}
.svgDiv{width:400px; height:400px; overflow:hidden; background:#fff url(map1.jpg); 
  margin:10px auto; border:2px dotted #000;}

</style>
<script>/* <![CDATA[ */
var svg_ns = "http://www.w3.org/2000/svg", aNum = 0;
var ajaxs = [{w:521,h:337,img:"map2.jpg"},{w:352,h:477,img:"map3.jpg"},{w:400,h:400,img:"map1.jpg"}];


function nextMap() {
  if (aNum > 2) aNum = 0;
// the variable data MIGHT be like an AJAX JSON return
  var data = ajaxs[aNum];
  sDiv.style.width = data.w+"px"; // change the div width and height
  sDiv.style.height = data.h+"px";
  sDiv.style.backgroundImage="url('"+data.img+"')";// load the MAP image as backgroung
  
// IMPORTANT, to get correct placement, adjust the SVG viewbox to dimention of image
  document.getElementById("sv1").setAttribute("viewBox","0 0 "+data.w+" "+data.h)
  
  var gDis = document.getElementById("imgDisplay");// get the svg "g" element to place into
  var arEls = gDis.getElementsByTagName("*");
// the next Line ERASES all drawing on the SVG
  for (i = arEls.length-1; i > -1; --i) gDis.removeChild(arEls[i]);

switch (data.img){// change drawing for each map
case "map2.jpg":
  var  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '14');
  elm.setAttribute('y1', '14');
  elm.setAttribute('x2', '518');
  elm.setAttribute('y2', '330');
  elm.setAttribute('stroke', '#aa0000');
  elm.setAttribute('stroke-width', '6');
  gDis.appendChild(elm);

  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '244');
  elm.setAttribute('y1', '14');
  elm.setAttribute('x2', '318');
  elm.setAttribute('y2', '430');
  elm.setAttribute('stroke', '#00aa00');
  elm.setAttribute('stroke-width', '4');
  gDis.appendChild(elm);

  elm = document.createElementNS(svg_ns, 'text');
  elm.setAttribute('y', '16');
  elm.setAttribute('x', '475');
  elm.setAttribute('font-size', '16');
  elm.setAttribute('text-anchor',"middle");
  elm.appendChild(document.createTextNode("New Map2"));
  gDis.appendChild(elm);
  break;
  
case "map3.jpg":
  var  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '122');
  elm.setAttribute('y1', '122');
  elm.setAttribute('x2', '122');
  elm.setAttribute('y2', '184');
  elm.setAttribute('stroke', '#4444bb');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '122');
  elm.setAttribute('y1', '122');
  elm.setAttribute('x2', '176');
  elm.setAttribute('y2', '184');
  elm.setAttribute('stroke', '#4444bb');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '176');
  elm.setAttribute('y1', '184');
  elm.setAttribute('x2', '176');
  elm.setAttribute('y2', '122');
  elm.setAttribute('stroke', '#4444bb');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
elm = document.createElementNS(svg_ns, 'text');
  elm.setAttribute('y', '16');
  elm.setAttribute('x', '41');
  elm.setAttribute('font-size', '16');
  elm.setAttribute('text-anchor',"middle");
  elm.appendChild(document.createTextNode("Fresh Map3"));
  gDis.appendChild(elm);
  break;
  
case "map1.jpg":
  var  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '22');
  elm.setAttribute('y1', '42');
  elm.setAttribute('x2', '54');
  elm.setAttribute('y2', '42');
  elm.setAttribute('stroke', '#44bb44');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '54');
  elm.setAttribute('y1', '42');
  elm.setAttribute('x2', '22');
  elm.setAttribute('y2', '80');
  elm.setAttribute('stroke', '#44bb44');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
  elm = document.createElementNS(svg_ns, 'line');
  elm.setAttribute('x1', '22');
  elm.setAttribute('y1', '80');
  elm.setAttribute('x2', '54');
  elm.setAttribute('y2', '80');
  elm.setAttribute('stroke', '#44bb44');
  elm.setAttribute('stroke-width', '5');
  gDis.appendChild(elm);
  
elm = document.createElementNS(svg_ns, 'text');
  elm.setAttribute('y', '12');
  elm.setAttribute('x', '200');
  elm.setAttribute('font-size', '20');
  elm.setAttribute('text-anchor',"middle");
  elm.appendChild(document.createTextNode("Map1 reStart"));
  gDis.appendChild(elm);
  break;
}

aNum++; // increase counter
}

/* ]]> */</script>
</head><body><button onclick="nextMap()" style="position:absolute; top:4px; left:6px;">Touch New Map</button>
<div style="text-align:center;">
<h2>Map to Draw Lines</h2>

<div id="dvSvg" class="svgDiv">
<svg id="sv1" width="100%" height="100%" id="svgGrd1" viewBox="0 0 400 400" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="imgDisplay">
<rect id="bg" x="88" y="10" width="47" height="79" fill="#fcf3e9" stroke="#00d"/>
<line stroke-dasharray="4, 2, 2, 2" x1="98" y1="15" x2="158" y2="245" style="stroke:red;stroke-width:5"/>
</g>
</svg>
</div>
</div>
<script>
var sDiv = document.getElementById("dvSvg");
</script>
</body></html>

Open in new window


Hopefully there is javascript in this that you can use to do whatever it is that you might need to do. I change the container <div> background image, then resize the <div> to the image size, then reset the SVG  viewbox, and then erase the svg draws, then draw some simple lines and text on it.
0
 
LVL 4

Author Comment

by:jatkin
Comment Utility
Hi,
Thanks a lot for all your comments...
In answer to this bit:
I really wounder about this line -
    svg.appendChild(svgimg);
I thought I was adding an SVG Image 'object' into the SVG itself, the same way as I add subsequent lines. Looking at this I think you are right, the SVG itself should have the image associated with it, not added as a child!

Your sample code looks pretty good! Only thing I need to do is get this image auto 'stretched' to the width of the DIV, and adjusted in height accordingly.

For example, the following three images show essentially what I am trying to do. The Green boxes indicate a number of buttons positioned above the image, within a parent DIV of which the image DIV & SVG is a part of. The Red box is the scaled image, always the full width of the appropriate container, but with a changing height depending on the proportion of the particular image. Within this image area I wish to draw the lines
Image where width is a bit wider that it's heightImage where width is about twice the heightImage where width is about half the height
0
 
LVL 33

Accepted Solution

by:
Slick812 earned 500 total points
Comment Utility
OK, I now see that you use three section page template , Just a Note, In my page building now, I must guarantee "responsive" flexible pages for phones and laptops. But I will NOT place any responsive CSS in my code for you since that's not part of your question.

I used a map Image as a background for a <div>, because that seemed to be the least code work. I have NOT used any way to rescale, stretch, a background image as you now require. So I guess, this will require an <img> tag, that are very easy to rescale with style.width . If I use an <img> , then I will need to layer the SVG on top of the <img> so the  html structure goes like this -

<div id="allSvg" style="position:relative; width:400px;">
<img id="bkImg" src="map1.png" style="width:100%" />

<div id="dvSvg" style="position: absolute; top:0px; left:0px; width:400px;  overflow:hidden; z-index:2;">
   <svg id="sv1" width="100%" height="100%" id="svgGrd1" viewBox="0 0 400 400" version="1.1">
   <g id="imgDisplay">
   <rect id="bg" x="88" y="10" width="47" height="79" fill="#fcf3e9" stroke="#00d"/>
   </g>
   </svg>
   </div>
</div>

with the Image at  width:100%  It will always stretch and reshape the height and have the width of it's container, here as 400px;

the <div id="dvSvg"> will have absolute position and be layered on top of the <img>, since it is the same width as the main <div>, it will not need to be changed as new images are used.
As far as the SVG image in this new environment,, since the width does not change, you can just create a svg with a large height (more height than ANY image that you need), and then You never need to change the "viewBox" settings (or other width-height ratios), and just draw on it to the current <img> height.

That is how I would try to do it.
0
 
LVL 4

Author Closing Comment

by:jatkin
Comment Utility
Perfect!
Thanks so much for all the help - VERY much appreciated.
James
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

How to build a simple, quick and effective accordion menu using just 15 lines of jQuery and 2 css classes
SASS allows you to treat your CSS code in a more OOP way. Let's have a look on how you can structure your code in order for it to be easily maintained and reused.
In this tutorial viewers will learn how to embed an audio file in a webpage using HTML5. Ensure your DOCTYPE declaration is set to HTML5: : The declaration should display (CODE) HTML5 is supported by the most recent versions of all major browsers…
The viewer will receive an overview of the basics of CSS showing inline styles. In the head tags set up your style tags: (CODE) Reference the nav tag and set your properties.: (CODE) Set the reference for the UL element and styles for it to ensu…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now