Two different views of the same svg drawing - javascript

I want to make a fairly large diagram using SVG (I have been using Snap.svg in JavaScript). I'd like to display a zoomable portion of the diagram in an element, and also a smaller version of the whole thing in a different element, in which the user can navigate.
One strategy is this:
Make two identical SVGs, except that they have different viewBoxes, and every time I change one of the svg elements, make an identical change to the other copy. The viewBox attributes cause each view to show the right part of the diagram.
Is that a good strategy? Seems fragile and wasteful to me. Is there some other, smarter approach? Do I really have to draw everything twice?
Hoping for "D'oh!"

Yes it's possible to have a master SVG and then "thumbnail" and/or "zoomed" versions of the same image that update automatically.
document.getElementById("but").addEventListener("click", function() {
var svg = document.getElementById("mainsvg");
var c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
c.setAttribute("cx", 1000*Math.random());
c.setAttribute("cy", 1000*Math.random());
c.setAttribute("r", 50);
c.setAttribute("fill", "red");
svg.appendChild(c);
});
#main {
width: 400px;
height: 400px;
}
#thumb,
#zoom {
display: inline-block;
width: 80px;
height: 80px;
}
svg {
border: solid 1px grey;
}
<div id="main">
<svg id="mainsvg" viewBox="0 0 1000 1000">
<rect x="100" y="100" width="500" height="500" fill="green"
transform="rotate(10,350,350)"/>
<rect x="400" y="400" width="500" height="500" fill="orange"
transform="rotate(-10,650,650)"/>
</svg>
</div>
<div id="thumb">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 1000">
<use xlink:href="#mainsvg" />
</svg>
</div>
<div id="zoom">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 1000">
<use xlink:href="#mainsvg"
x="-500" y="-1200"
width="3000" height="3000" />
<!-- control the zoom and position with x, y, width and height -->
</svg>
</div>
<div>
<button id="but">Click me</button>
</div>

Related

Adding/removing css class breaks the js sometimes?

Alright, something strange is occurring here. First, I have these <span>'s set up, with svgs that have a gaussian blur filter - 7 of them:
<span name = "sunlight">
<div id = "svgContainer">
<div id = "inner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 650">
<defs>
<filter id="Blur"><feGaussianBlur stdDeviation="25" /></filter>
<g id="" data-name="Layer 1"><rect class="cls-1" width="700" height="558"/></g><g id="Img" data-name="Layer 2"><path class="cls-2" d="M403.71,111.69q-47.58,19.77-94,42.26-45.18,22-89,46.69c-13.09,7.37-26.15,14.91-38.11,24A160,160,0,0,0,163.94,241a71.68,71.68,0,0,0-8.7,10.37,16.54,16.54,0,0,0,2.12,20.7c3.48,3.41,8.14,5,12.85,5.71l13,1.94,26,3.89,51.71,7.73L364.68,306.8l13,1.95-7.17-17.63A117.3,117.3,0,0,1,361.32,305l1.87-2.43c-10.79,14-24.25,25.51-37.89,36.59-13.79,11.19-28,22-40.41,34.74a151.85,151.85,0,0,0-20.2,25.28c-3.89,6.18-.36,16.17,7.17,17.62a91,91,0,0,1,18,5.5L287,421.08a93.25,93.25,0,0,1,20.12,11.77L304.72,431a90.48,90.48,0,0,1,14.91,14.56c1.82,2.24,5.73,3.51,8.49,3.51a12.22,12.22,0,0,0,8.48-3.51,12.1,12.1,0,0,0,3.52-8.49l-.43-3.19a11.93,11.93,0,0,0-3.09-5.29c-11.62-14.33-27.45-24.86-44.72-31.15a90.6,90.6,0,0,0-13.64-3.76l7.17,17.63q3.13-5,6.73-9.63l-1.88,2.43c11.28-14.54,25.42-26.48,39.66-38s28.95-22.83,41.52-36.31a139.45,139.45,0,0,0,19.8-26.54,11.88,11.88,0,0,0,.6-10.68c-1.31-3.1-4.21-6.41-7.78-6.95l-83.92-12.54-83.92-12.55L193,257l-11.77-1.76c-3.28-.49-6.76-.7-9.87-1.91l2.87,1.21a9.41,9.41,0,0,1-2-1.16l2.43,1.87a8.2,8.2,0,0,1-.78-.78l1.88,2.43a5.42,5.42,0,0,1-.55-.93l1.21,2.87a5.84,5.84,0,0,1-.29-1.09l.43,3.19a4.3,4.3,0,0,1,0-1l-.43,3.19a7.34,7.34,0,0,1,.38-1.37l-1.21,2.87a12.1,12.1,0,0,1,1.27-2.1l-1.87,2.43c6.15-7.92,13.78-14.75,21.68-20.88l-2.43,1.88c11.16-8.6,23.29-15.81,35.53-22.75q20.44-11.61,41.22-22.6,41.77-22.14,84.74-41.9,24.18-11.12,48.72-21.48l-2.86,1.21q4.41-1.86,8.83-3.69c3-1.26,5.45-2.58,7.17-5.52a12.3,12.3,0,0,0,1.21-9.25A12.15,12.15,0,0,0,413,112.9c-2.64-1.4-6.34-2.41-9.24-1.21Z"/>
</g>
</defs>
<use style="fill:pink;" filter="url(#Blur)" xlink:href="#Img"
transform="translate(0,0)"/>
<use style="fill:white;" xlink:href="#Img"/>
</svg>
</div>
</div>
<h4>[ sunlight ]</h4>
<h5>5.2</h5>
</span>
Next, I have this css that either sets the <span> to display:inline-block or display:none:
#center > span.active {
display: inline-block;
}
#center > span {
display: none;
margin-left: -35px;
margin-right: -35px;
}
With js, I then (onscroll) iterate through a list of arrays. I need to add or removing the active class from each <span> depending on whether its name is in the array at the current index.
$('#center').children().each(function(i, child) {
if(localThis.currentDataPt[0].includes($(child).attr('name')))
{
$(child).addClass('active');
} else {
$(child).removeClass('active');
}
This is fine. However, when we get past this down to the part where I set via js the "feGaussianBlur" attribute, on MOST of the arrays, the attribute is visually set to zero. This doesnt happen when I dont do the show/hide:
$(child).find("feGaussianBlur").attr("stdDeviation", ((localThis.currentDataPt[1]/10)*45).toString());
Why would this happen?

Large gaussian blur filter without revealing SVG border

I am trying to create a kind of "spotlight" effect over a large background image where the user can use the cursor to view parts of an image.
I'm currently applying a gaussian blur filter to a mask element which is positioned at the cursor and reveals parts of the large image.
I would like to have a higher blur amount in order to get a softer edge, but when I increase the value of the stdDeviation attribute inside the filter, the border of the SVG is revealing – I have attached two images to illustrate, also you can see it in this pen https://codepen.io/moevbiz/pen/YbwErx
Here is my code:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
<defs>
<filter id="filter">
<feGaussianBlur stdDeviation="50"/>
</filter>
<mask id="mask">
<ellipse id="ellipse" cx="50%" cy="50%" rx="100" ry="100" fill="white" filter="url(#filter)"></ellipse>
</mask>
</defs>
<image xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/Harry-Potter-1-.jpg" width="100%" height="100%" mask="url(#mask)"></image>
</svg>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/Harry-Potter-1-.jpg" alt="" />
body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
overflow:hidden;
}
img {
width: 100vw;
height: 100vh;
mask: url(#mask);
}
document.onmousemove=function(e) {
var mousecoords = getMousePos(e);
var mouseX = mousecoords.x;
var mouseY = mousecoords.y;
var ellipse = document.getElementById('ellipse');
ellipse.setAttribute('cx', mouseX);
ellipse.setAttribute('cy', mouseY)
};
function getMousePos(e) {
return {x:e.clientX,y:e.clientY};
}
current(i want to have a larger blur than this, not just increase the ellipse radius)
increased blur value with visible borders
Thankful for any hints!
You need to increase your Filter Region.
By default it is <filter x="-10%" y="-10%" width="120%" height="120%">. But if you want to use a large blur, you will need to increase it, to cater for the way that the blur spreads the pixels out further.
Something like <filter id="filter" x="-75%" y="-75%" width="250%" Height="250%"> works for your example.
https://codepen.io/PaulLeBeau/pen/qGboxR

How to use and style (selector) nested symbols in SVG

I have something like the following:
<html>
<head>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
grove > tree > triangle {
/* some triangle styles */
}
#grove1 > tree > triangle {
/* some triangle styles for grove 1 */
}
#grove2 > tree > triangle {
/* some triangle styles for grove 2 */
}
#grove3 > tree > triangle {
/* some triangle styles for grove 3 */
}
#grove3 > tree {
/* animate the tree */
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3000 3000">
<defs>
<symbol id="triangle" viewBox="0 0 100 100">
<polygon points="0,100 50,0 100,100" class="triangle" />
</symbol>
<symbol id="tree" viewBox="0 0 100 100">
<use href="#triangle" width="100" height="100" />
</symbol>
<symbol id="grove" viewBox="0 0 100 100">
<use href="#tree" width="10" height="10" />
<use href="#tree" width="10" height="10" x="20" />
<use href="#tree" width="10" height="10" x="40" />
<use href="#tree" width="10" height="10" x="60" />
<use href="#tree" width="10" height="10" x="80" />
</symbol>
</defs>
<use id="grove1" href="#grove" x="10" y="10" height="10"/>
<use id="grove2" href="#grove" x="30" y="100" height="100"/>
<use id="grove3" href="#grove" x="50" y="600" height="600"/>
</svg>
</body>
</html>
Notice that symbols are nested 3 levels deep. And the sizing definitions change at each symbol. For example, in the grove symbol, the viewBox is 100x100, but the trees which it contains also have their own view box of 100x100. The trees are sized to 10x10 in the grove, so they have a different scaling system.
Basically though, I compose these symbols into the grove symbol, then add those using use in 3 different places. Each one appears larger/closer than the one before. Or that's how it should work.
The question is how I can change the colors on these symbols efficiently and properly (best-practice). If I can do something like the example CSS above:
grove > tree > triangle {
/* some triangle styles */
}
Or if I have to use JavaScript somehow to change the styles in this situation, or otherwise not really use symbols at all, or create symbol for every color variation I want (somehow).
Please let me know how I should structure this SVG system so I can properly style each chunk of stuff (each "grove" for example, and its contents).
Another example I'd like for this to handle is, say each tree has branches. I want to have 5 rows of trees, as if it's parallax and each row is further in the distance than the one before, and as such they get lighter and lighter. So I would like to set the color of the branches and the "leaves" (main tree triangle) to be lighter and lighter as they move back. In addition, maybe I want to slightly change the color of the trees at a specific row. So I should be able to do something like this:
#grove1 > tree > branch { /* style */ }
#grove1 > #tree1 { /* style */ }
#grove1 > #tree2 { /* style */ }
#grove1 > #tree2 > #branch1 { /* style */ }
#grove1 > #tree2 > #branch2 { /* style */ }
#grove1 > etc...
Basically how to style the nested symbols individually. If it's not possible, then what the design pattern is I should be using instead.
Styles can inherit into the children of a <use> see below:
<html>
<head>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
#grove {
/* some triangle styles */
}
#grove1 {
/* some triangle styles for grove 1 */
fill: red;
}
#grove2 {
/* some triangle styles for grove 2 */
fill: green;
}
#grove3 {
/* some triangle styles for grove 3 */
fill: gold;
}
#grove3 > tree {
/* animate the tree */
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3000 3000">
<defs>
<symbol id="triangle" viewBox="0 0 100 100">
<polygon points="0,100 50,0 100,100" class="triangle" />
</symbol>
<symbol id="tree" viewBox="0 0 100 100">
<use href="#triangle" width="100" height="100" />
</symbol>
<symbol id="grove" viewBox="0 0 100 100">
<use href="#tree" width="10" height="10" />
<use href="#tree" width="10" height="10" x="20" />
<use href="#tree" width="10" height="10" x="40" />
<use href="#tree" width="10" height="10" x="60" />
<use href="#tree" width="10" height="10" x="80" />
</symbol>
</defs>
<use id="grove1" href="#grove" x="10" y="10" height="10"/>
<use id="grove2" href="#grove" x="30" y="100" height="100"/>
<use id="grove3" href="#grove" x="50" y="600" height="600"/>
</svg>
</body>
</html>
You can style the <symbol> and it's children.
You can style the <use> element.
However you can't have selectors that cross the <use> "boundary".
#myuse > #mysymbol-child
will not work. You cannot use a selector to target a specific instance of that symbol. Except by inheritance from the <use> as I have done in my example.
A symbol is a definition. If you give it a direct style, then that symbol with whatever style it has, will be used everywhere. That includes things like animation. If you animate the symbol, all instances of it will be animated the same way.
If I remember correctly, the trick to do that is to set the properties you want to set differently for each instance of <use> to "inherit" or "currentColor" (or if they're inherited by default, don't set them, if you don't need to) and then place each <use> in a <g> and apply the style declarations to each <g>.
SVG - inherit multiple colors

Combine two SVG elements into one using Javascript?

Let's say I have the following html context, which I don't have access to its creation :
<div id="outer">
<div id="chart-div">
<svg id="chart"></svg>
</div>
<div id="legend-div">
<svg id="legend"></svg>
</div>
</div>
What I'm trying to do is export this SVG to an image using canvg library, the problem here is that they are separated and so I get two canvas, and canvg library accepts a SVG definition string as input.
How can I modify the html elements above so that they are only one SVG? Using Javascript as this is a browser extension.
I have tried just switching the DIVs tags to SVG but it just broke everything and the SVG became blank
This is my solution to your problem: I make the outer div display:none, I'm creating another svg element (you may do it dynamicaly) end inside the #new svg I'm using the #chart and the #legend. I hope it helps.
svg{border:1px solid;}
#outer {display:none}
#outer div{position:absolute; width:500px;}
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
<svg id="new" viewBox="0 0 300 150" width="500">
<use xlink:href="#chart" />
<use xlink:href="#legend" />
</svg>
This is a Javascript solution for merging two svg accessibles in the document through DOM manipulation.
var svgNS = "http://www.w3.org/2000/svg";
var outer = document.getElementById('outer');
// get chart content
var chart = document.getElementById('chart-div');
var chartSvg = chart.getElementsByTagName('svg')[0];
var chartContent = Array.from(chartSvg.childNodes);
// get legend content
var legend = document.getElementById('legend-div');
var legendSvg = legend.getElementsByTagName('svg')[0];
var legendContent = Array.from(legendSvg.childNodes);
// create a merged-div where we are going to merge the svgs
var merged = document.createElement('div');
merged.setAttribute('id', 'merged-div');
outer.appendChild(merged);
// createElementNS for svg
var mergedSvg = document.createElementNS(svgNS, 'svg');
mergedSvg.setAttribute('id', 'merged');
// keep the viewBox of the chart
mergedSvg.setAttribute('viewBox', chartSvg.getAttribute('viewBox'));
merged.appendChild(mergedSvg);
// adding the content of both svgs
for (let i = 0; i < chartContent.length; i++) {
mergedSvg.appendChild(chartContent[i]);
}
for (let i = 0; i < legendContent.length; i++) {
mergedSvg.appendChild(legendContent[i]);
}
// the unmerged svgs can be removed
chart.remove();
legend.remove();
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
Resulting markup:
<div id="outer">
<div id="merged-div">
<svg id="merged" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20"></circle>
<rect fill="skyBlue" x="200" y="100" width="80" height="30"></rect>
</svg>
</div>
</div>

Click events stop working after replacing attribute of <use> element in <svg> (Win7/IE11)

We are using multiple svg symbols for displaying icons.
<!-- defining them at the start of the page -->
<div id="icon-container">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol xmlns="http://www.w3.org/2000/svg"
id="rect" ...>
<rect... />
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg"
id="circle" ...>
<circle... />
</symbol>
</svg>
</div>
<!-- using them in the page somewhere -->
<svg>
<use xlink:href="#rect"></use>
</svg>
In some cases we need to replace them later on with another icon (like on a collapse control), therefore I created a little helper function to change the xlink:href to the new symbol name.
$.fn.replaceSVGIcon = function(id) {
$(this).find('svg')
.andSelf()
.filter('svg')
.find('use')
.attr('xlink:href', '#' + id);
}
This works in every browser except for IE10 + IE11 on Windows 7 (but weirdly enough it works with Windows 8).
When you open the snippet below in IE11 and click on the red box everything is fine, but as soon as you start clicking on the element within, it breaks the whole page after the icon is changed for the first time.
(function($){
$.fn.replaceSVGIcon = function(id) {
$(this).find('svg').andSelf().filter('svg').find('use').attr('xlink:href', '#' + id);
}
})(jQuery);
clickFunction = function() {
var $elem = $('#icon');
if ($elem.find('use').attr('xlink:href') == '#rect')
{
$elem.replaceSVGIcon('circle');
} else {
$elem.replaceSVGIcon('rect');
}
};
#icon-container {
visibility: collapse;
display: none;
}
#icon {
height: 40px;
width: 40px;
fill: #454545;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="icon-container">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol xmlns="http://www.w3.org/2000/svg" id="rect" viewBox="0 0 50 50">
<rect x="15" y="15" width="20" height="20"></rect>
</symbol>
<symbol xmlns="http://www.w3.org/2000/svg" id="circle" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="10"></circle>
</symbol>
</svg>
</div>
<svg id="icon" onclick="clickFunction()">
<rect fill="red" height="40" width="40"></rect>
<use xlink:href="#rect"></use>
</svg>
Why is this happening and is this a known Internet Explorer bug? What are my options to work around this issue?
Yes, this is a known IE bug. https://connect.microsoft.com/IE/feedback/details/796745/mouse-events-are-not-delivered-at-all-anymore-when-inside-an-svg-a-use-is-removed-from-the-dom
If you can, you should set pointer-events: none; for the use tag in your CSS. It's crazy, but it should work.

Categories

Resources