With a view to making them hot pluggable / draggable / scaleable, I've been playing around with Mike Bostock's d3 three little circles examples.
While doing this, I stumbled across an issue (using Firefox 16.0.2, btw - normally fine for svg) which, though in itself not serious, somehow bugs me: namely that though always present in the resulting HTML, any attempt at overlaying the rectangular viewing area with the button element fails.
I've tried following each piece of advice at the foot of this exchange, but these have had no impact.
Here my base code, whereby the button is shown outside the circle's containing svg view area. The groupings are part of preparations for experiments with drag n drop / scalability:
var root = d3.selectAll("g#tool-2");
var g0 = root
.append("g")
.attr("class", "g0")
.attr("id", function (d, i) { return d.tool});
var g01 = g0
.append("g")
.attr("class", "g01")
.attr("id", function (d, i) { return d.tool});
var g02 = g0
.insert("g")
.attr("class", "g02")
.attr("id", function (d, i) { return d.tool});
var svg = g01
.insert("svg")
.attr("width", width)
.attr("height", height);
var button = g02
.append("div")
.attr("class", "button")
.attr("id", function (d, i) { return d.tool})
.append("button")
.text(function(d) { return "Run" });
svg.selectAll(".little")
.data(data)
.enter()
.append("circle")
.attr("class", "little")
.attr("cx", x)
.attr("cy", y)
.attr("r", 12);
console.log("Got past circle creation");
button
.on("click", function() {
svg.selectAll(".select").remove();
svg.selectAll(".select")
.data(data)
.enter()
.append("circle")
.attr("class", "select")
.attr("cx", x)
.attr("cy", y)
.attr("r", 60)
.style("fill", "none")
.style("stroke", "red")
.style("stroke-opacity", 1e-6)
.style("stroke-width", 3)
.transition()
.duration(750)
.attr("r", 12)
.style("stroke-opacity", 1);
});
Appended to any of root, g0, g01 or g02, the button is shown outside the rectangular container. All well and good. Here, for example, the html resulting from the code shown above:
<g id="tool-2" class="g0">
<g id="tool-2" class="g01">
<svg height="180" width="360">
<circle r="12" cy="45" cx="180" class="little"></circle>
<circle r="12" cy="90" cx="60" class="little"></circle>
<circle r="12" cy="135" cx="300" class="little"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="45" cx="180" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="90" cx="60" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="135" cx="300" class="select"></circle>
</svg>
</g>
<g id="tool-2" class="g02">
<div id="tool-2" class="button">
<button>Run</button>
</div>
</g>
</g>
Regardless of append element used, however, whether using
z-index
x & y coordinates
dx & dy coordinates
with or without id
with or without class
with or without positioning
..the button, though present in the resulting html, either continues to be shown outside the container, or is simply not displayed.
There seems to be an issue with svg overlaying with which I'm really not familiar. Any hints?
Thanks
Thug
As far as I can see you're mixing HTML elements into SVG. That's not valid. You can wrap the HTML elements in a <foreignObject> element and see if you're more lucky with that.
You have to make sure that the proper namespace for the foreign content is added. Here is a complete working example (try on jsfiddle):
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<g class="g0">
<g class="g01">
<svg height="180" width="360">
<circle r="12" cy="45" cx="180" class="little"></circle>
<circle r="12" cy="90" cx="60" class="little"></circle>
<circle r="12" cy="135" cx="300" class="little"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="45" cx="180" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="90" cx="60" class="select"></circle>
<circle style="fill: none; stroke: red; stroke-opacity: 1; stroke-width: 3;" r="12" cy="135" cx="300" class="select"></circle>
</svg>
</g>
<g class="g02">
<foreignObject width="100" height="50">
<div class="button" xmlns="http://www.w3.org/1999/xhtml">
<button>Run</button>
</div>
</foreignObject>
</g>
</g>
</svg>
Related
I'm creating a bar chart with SVG, by default bars are vertical. But in some cases it looks better if bars are horizontal, like when there are only 2 bars.
How can I reuse same SVG code and just switch X and Y axis to achieve that? It's actually more than just X and Y and also things like height and width for the rect.
Is that possible? I would like to avoid writing very similar code twice.
Example: I built two charts separately, ideally the second chart should be produced by reusing the code from the first chart.
<style>
svg { height: 20px; width: 100px; border: 1px solid #ccc;}
</style>
<svg>
<rect x="5%" y="60%" width="40%" height="40%" fill="black"/>
<rect x="55%" y="40%" width="40%" height="60%" fill="black"/>
</svg>
<svg>
<rect y="5%" x="0%" height="40%" width="40%" fill="black"/>
<rect y="55%" x="0%" height="40%" width="60%" fill="black"/>
</svg>
Maybe something like this:
function cloneWithTransformedAttributes(obj1, mapping) {
// returns copy of obj1 with child node attributes transformed according to mapping.
const obj2 = obj1.cloneNode(true);
[...obj1.children].forEach((child, idx)=>{
Object.keys(mapping).forEach((attribute) => {
const replacementVal = mapping[attribute].default ?
mapping[attribute].default :
child.getAttribute(mapping[attribute]);
obj2.children[idx].setAttribute(attribute, replacementVal);
})
})
return obj2;
}
mapping = {
x: {
default: 0
},
y: "x",
width: "height",
height: "width"
}
const verticalSvg = document.getElementsByTagName("svg")[0];
const horizontalSvg = cloneWithTransformedAttributes(verticalSvg, mapping);
const graphs = document.getElementById("graphs");
graphs.appendChild(horizontalSvg);
<style>
svg { height: 20px; width: 100px; border: 1px solid #ccc;}
</style>
<div id="graphs">
<svg>
<rect x="5%" y="60%" width="40%" height="40%" fill="black"/>
<rect x="55%" y="40%" width="40%" height="60%" fill="black"/>
</svg>
</div>
Edit: You could also do it with svg transforms but you need to put the inside of the SVG inside a group <g></g>. Here's an example with both a manually calculated transform and a JS solution that calculates the transform by itself by getting the original svg width and height:
const verticalSvg = document.getElementsByTagName("svg")[0];
const svgStyle = window.getComputedStyle(verticalSvg, null);
const width = parseInt(svgStyle.getPropertyValue("width"));
const height = parseInt(svgStyle.getPropertyValue("height"));
const whRatio = width / height;
const transform = `rotate(90) scale(${1 / whRatio} ${whRatio}) translate(0 -${height})`
const horizontalSvg = verticalSvg.cloneNode(true);
horizontalSvg.children[0].setAttribute("transform", transform);
const graphs = document.getElementById("graphs");
graphs.appendChild(horizontalSvg);
<style>
svg { height: 20px; width: 100px; border: 1px solid #ccc; overflow: visible}
</style>
<div id="graphs">
<svg>
<g>
<rect x="5%" y="60%" width="40%" height="40%" fill="black"/>
<rect x="55%" y="40%" width="40%" height="60%" fill="black"/>
</g>
</svg>
<svg>
<!-- Manually calculated and applied transform -->
<g transform="rotate(90)
scale(0.20 5)
translate(0 -20)
">
<rect x="5%" y="60%" width="40%" height="40%" fill="black"/>
<rect x="55%" y="40%" width="40%" height="60%" fill="black"/>
</g>
</svg>
<!-- JS generated SVG will get inserted here -->
</div>
In your case, you need to apply the transform to a group holding the SVG contents.
If you apply the transform to the SVG itself it will also scale the border according to the scale transform.
vector-effect="non-scaling-stroke" can not be applied to the border of the SVG which is not part of the SVG itself. But if you are using elements with a "stroke" property inside the SVG then you would probably want to also apply the vector-effect="non-scaling-stroke" attribute to them.
I'm able to center text for each path/shape explicitly, but I don't want to do the math for each one. I've tried this the code below but it just puts the two texts on top of one another, which would make sense if it's referring to the one <svg> and not each <g> or <path>.
<svg viewBox="0 0 91.742 214.2" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000" stroke-width="1">
<g>
<text class="room-text" x="50%" y="50%">145</text>
<path class="room" d="m 0.134593,0.134751 2.2679,133.799999 86.179,0.75595 -5.2917,-133.049999 z"/>
</g>
<g>
<text class="room-text" x="50%" y="50%">146</text>
<path class="room" d="m 2.402593,133.94175 0.75595,80.131 88.446,-0.75594 -3.0238,-78.619 z"/>
</g>
</svg>
I've also tried anchor-text and baseline-alignment but an explicit x and y value are still needed.
Yeah. I ended up just doing it through JavaScript.
document.addEventListener("DOMContentLoaded", function(event) {
var svg = document.getElementById("svg");
/***** Get the room box and text box *****/
for (i = 1; i < svg.childNodes[1].childNodes.length; i += 2) {
var room = svg.childNodes[1].childNodes[i].childNodes[3]; // path element
var textbox = svg.childNodes[1].childNodes[i].childNodes[1]; // text element
var new_x = document.createAttribute("x"); // make a new attribute to add to the textbox
new_x.value = room.getBBox().width / 2 + room.getBBox().x - textbox.getBBox().width / 2; // calculate the textbox's new x position
textbox.setAttributeNode(new_x) // assign the textbox the new x value
}
});
body {
box-sizing: border-box;
}
.main {
width: 100%;
}
svg {
display: block;
margin: 0 auto;
width: 400px;
height: 400px;
}
.room {
fill: rgba(220, 220, 220, .4);
}
.room:hover {
fill: rgba(0, 255, 255, 0.4);
}
.room-text {
fill: black;
font-size: 14px;
font-family: "Helvetica";
}
<div id="svg">
<svg viewBox="0 0 181.01 255.02" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000" stroke-width="1">
<g id="r145">
<text class="room-text" y="40">145</text>
<path class="room" d="m0.13374 1.6456 0.75595 68.792 135.32-2.2679-4.5357-68.036z"/>
</g>
<g>
<text class="room-text" y="130">146</text>
<path class="room" d="m0.88969 70.437 29.482 103.57 150.43-3.7798-44.601-102.05z"/>
</g>
<g>
<text class="room-text" y="220">147</text>
<path class="room" d="m30.372 174 42.333 79.375 96.762 1.5119 11.339-84.667z"/>
</g>
</svg>
</div>
I've created an SVG and put the width as a percentage because I want it to resize to fit different screen widths, but when I resize the screen, the svg moves up and down and doesn't move left/right to stay in the centre. If I use pixels instead of percentages, it doesn't resize with the screen.
Preview didn't work on here so here's the codepen link
.
HTML
<svg height="100%" width="100%" id="main">
<circle class="graph line line-1" cx="50%" cy="50%" r="25%" stroke-width="5%" stroke="#f1c40f" fill="none" />
<circle class="graph line line-2" cx="50%" cy="50%" r="20%" stroke-width="5%" stroke="#e67e22" fill="none" />
<circle class="graph line line-3" cx="50%" cy="50%" r="15%" stroke-width="5%" stroke="#00c0df" fill="none" />
</svg>
CSS
#main {
padding: 100px 0;
margin-top: 100px;
height: 200px;
background-color: pink;
}
.graph {
transform: rotate(270deg);
}
.graph.line {
transform-origin: center;
stroke-dasharray: 160%;
animation: graph 1.5s ease-in-out infinite alternate;
}
#keyframes graph {
from {
stroke-dashoffset: 160%;
}
to {
stroke-dashoffset: 90%;
}
}
That's what viewBox is for. With a viewBox, you establish a local coordinate system, which scales with your image. In your svg you simply use your local coordinates, and the image scales to any size...
#main {
position:absolute;
top:0px;left:0px;
right:0px;bottom:0px;
background:pink
}
.graph {
transform: rotate(270deg);
}
.graph.line {
transform-origin: center;
stroke-dasharray: 160%;
animation: graph 1.5s ease-in-out infinite alternate;
}
#keyframes graph {
from {
stroke-dashoffset: 160%;
}
to {
stroke-dashoffset: 90%;
}
}
<svg viewBox="0 0 100 100" id="main">
<circle class="graph line line-1" cx="50" cy="50" r="25" stroke-width="5" stroke="#f1c40f" fill="none" />
<circle class="graph line line-2" cx="50" cy="50" r="20" stroke-width="5" stroke="#e67e22" fill="none" />
<circle class="graph line line-3" cx="50" cy="50" r="15" stroke-width="5" stroke="#00c0df" fill="none" />
</svg>
How can I achieve something like this using SVG?
You would need two different circle elements, one for the underlying gray color and the other for the blue stroke, then apply a stroke-dasharray and stroke-dashoffset to the blue stroke.
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: #eee;
}
.filled {
stroke: blue;
stroke-dashoffset: 110;
stroke-dasharray: 440;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 190 190">
<circle class="track" cx="80" cy="80" r="70" />
<circle class="filled" cx="80" cy="80" r="70" />
</svg>
I am pretty new to SVG and wanted to ask for a best approach to the following design:
I believe SVG is the way to go here since I need hover and click effects on each of the red arc pieces. These values and this design are essentially hardcoded and will not change. Are there any tools / libraries (D3 or Raphael) that would make this easier for me?
Thanks In Advance.
meetamit's suggestion is a good one. Or you could look into the 'sector' method shown here:
Half circle using raphael
You don't really need d3 or even an editor. It's a design that is easy to code by hand.
I was bored, so I whipped this up in about 10 minutes.
document.getElementById("band4").addEventListener("click", function(e) {
alert("50+");
}, false);
document.getElementById("band3").addEventListener("click", function(e) {
alert("20-50");
}, false);
document.getElementById("band2").addEventListener("click", function(e) {
alert("10-20");
}, false);
document.getElementById("band1").addEventListener("click", function(e) {
alert("Less than 10");
}, false);
svg {
position: absolute;
top: 0px;
}
circle.band {
fill: #a20c3e;
}
circle.band:hover {
fill: #ca3f5e;
}
text {
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
fill: white;
}
tspan.sup {
font-size: 6px;
}
text.sub {
font-size: 5px;
font-weight: normal;
}
<img src="http://lorempixel.com/400/200/" width="100%"/>
<svg viewBox="-100 -100 200 100">
<defs>
<mask id="target">
<rect x="-100" width="100%" height="100%" fill="black"/>
<circle r="97" fill="white"/>
<circle r="77" fill="black"/>
<circle r="74" fill="white"/>
<circle r="54" fill="black"/>
<circle r="51" fill="white"/>
<circle r="31" fill="black"/>
<circle r="28" fill="white"/>
</mask>
</defs>
<circle id="band4" class="band" r="98" mask="url(#target)"/>
<circle id="band3" class="band" r="75" mask="url(#target)"/>
<circle id="band2" class="band" r="52" mask="url(#target)"/>
<circle id="band1" class="band" r="29" mask="url(#target)"/>
<text y="-82" text-anchor="middle" pointer-events="none">50<tspan class="sup" font-size="50%" dy="-0.7em">+</tspan></text>
<text y="-59" text-anchor="middle" pointer-events="none">20-50</text>
<text y="-36" text-anchor="middle" pointer-events="none">10-20</text>
<text y="-17" text-anchor="middle" class="sub" pointer-events="none">Less than</text>
<text y="-6" text-anchor="middle" pointer-events="none">10</text>
</svg>