SVG text and image centered within a rectangle - javascript

I'm really not familiar with SVG's so I'm sorry if this is actually a fairly easy problem..
I'm creating an SVG:
<svg height="100%" width="100%">
<rect x="0" y="0" width="100%" height="100%" fill="#da552f"></rect>
<image xlink:href="https://d30y9cdsu7xlg0.cloudfront.net/noun-svg/703414.svg?Expires=1481685113&Signature=hsa76aA6t5W6xisN8bYKk5t74cmOzTXmYUObaVwE0hUso99Gb4czprrsQAtkaC0aOQJBhNfAn8MjRpKyu8M~AzS5OS3rthGOLFqa3Pk2lCwAWjs-KtTa9fSo7w-sZSJwG6LDeRm5B6T5hYnoKQLibJzCtHvSdUYlp5XBUx1RNvs_&Key-Pair-Id=APKAI5ZVHAXN65CHVU2Q" transform="translate(-35.5,-31)" x="50%" y="50%" height="50px" width="50px"/>
<text fill="#ffffff" x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" font-size="48" font-family="Verdana">Kitty Cat</text>
</svg>
And as you can see both the image of the cat and the text are centered in the rectangle, but this isn't the desired effect I want.
I'd like the image to be next to the text and both of them be centered in the rectangle.. example:
How is this doable using SVGs? Is javascript required? Any help would be great! Thanks

Unlike HTML, SVG has no automatic layout of groups of elements.
You have two choices:
Cheat and put your image and text in HTML and use a <foreignObject> element to embed the HTML in your SVG. Although it is barely an SVG any more. And that only works in browsers.
Use Javascript to measure the text and then re-position it in the centre.
function reCentre() {
var svg = document.getElementById("mysvg");
var group = document.getElementById("centreMe");
// Get the bounding box of the image+text group
var groupBounds = group.getBBox();
// Get the size of the SVG on the page
var svgBounds = svg.getBoundingClientRect();
// Calculate new position for the group
var groupPosX = (svgBounds.width - groupBounds.width) / 2;
var groupPosY = (svgBounds.height - groupBounds.height) / 2;
// Calculate the difference between the groups current position
// and where it needs to be in order to be centred.
var dx = groupPosX - groupBounds.x;
var dy = groupPosY - groupBounds.y;
// Give the group a translate transform to move it to this new position
group.setAttribute("transform", "translate("+dx+","+dy+")");
}
// Initial centering
reCentre();
// Also recentre when window resizes
window.addEventListener("resize", reCentre);
<svg id="mysvg" height="100%" width="100%">
<rect x="0" y="0" width="100%" height="100%" fill="#da552f"></rect>
<g id="centreMe">
<image xlink:href="https://d30y9cdsu7xlg0.cloudfront.net/noun-svg/703414.svg?Expires=1481685113&Signature=hsa76aA6t5W6xisN8bYKk5t74cmOzTXmYUObaVwE0hUso99Gb4czprrsQAtkaC0aOQJBhNfAn8MjRpKyu8M~AzS5OS3rthGOLFqa3Pk2lCwAWjs-KtTa9fSo7w-sZSJwG6LDeRm5B6T5hYnoKQLibJzCtHvSdUYlp5XBUx1RNvs_&Key-Pair-Id=APKAI5ZVHAXN65CHVU2Q" x="0" y="-50" height="50px" width="50px"/>
<text fill="#ffffff" x="80" y="0" font-size="48" font-family="Verdana">Kitty Cat</text>
</g>
</svg>

Edit the x and y attributes of the image tag until the cat face is where you would like it.
<svg height="100%" width="100%">
<rect x="0" y="0" width="100%" height="100%" fill="#da552f"></rect>
<image xlink:href="https://d30y9cdsu7xlg0.cloudfront.net/noun-svg/703414.svg?Expires=1481685113&Signature=hsa76aA6t5W6xisN8bYKk5t74cmOzTXmYUObaVwE0hUso99Gb4czprrsQAtkaC0aOQJBhNfAn8MjRpKyu8M~AzS5OS3rthGOLFqa3Pk2lCwAWjs-KtTa9fSo7w-sZSJwG6LDeRm5B6T5hYnoKQLibJzCtHvSdUYlp5XBUx1RNvs_&Key-Pair-Id=APKAI5ZVHAXN65CHVU2Q" transform="translate(-35.5,-31)" x="25%" y="45%" height="50px" width="50px"/>
<text fill="#ffffff" x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" font-size="48" font-family="Verdana">Kitty Cat</text>
</svg>

you could use a webfont or an emoji instead of an image...
svg {
position: absolute;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px
}
<svg viewBox="0 0 400 200">
<rect x="0" y="0" width="400" height="200" fill="pink" />
<text x="200" y="100" dominant-baseline="middle" text-anchor="middle" font-size="30" font-family="sans serif">
<tspan font-size="50">šŸ±</tspan>Kitty Cat</text>
</svg>

Related

text is going out of <svg><rect> box once in 1000x times but on safari browser always reproduce this bug

Im trying to fix this bug, and I have tried alot things but nothing help, if I give value of x='23%' like <text id="badgeText" x = "23% ... I can fix bug on safari but than bug start repodruce everytime on other browsers(mozila,chrome etc...), anyhow the bug is reproducing once in many times on other browsers ...
bug image
<div class="CoveoShowIfFieldValue" data-field="#isRecommended" data-field-value="True">
<svg height="25px" style="overflow:visible!important;">
<g id="recommendedBadge" style="object-fit:contain!important;">
<rect x="0" y="0" rx="15px" ry="15px"/>
<text id="badgeText" y="66%" font-family="Metric" text-anchor="middle" class="CoveoText" data-value="RECOMMENDED" style="text-transform: uppercase;"></text>
<g class="tooltip" transform="translate(21,62)">
<rect x="-6%" width="450" y="-35" height="25" rx="5px" ry="5px"/>
<text y="-31" dy="1em" font-family="Metric" text-anchor="middle" font-weight="100" class="CoveoText" data-value="RecommendedTooltip"></text>
</g>
</g>
</svg> </div>
Itā€™s not a bug:
You need to add some width related properties like width or a viewBox.
Otherwise a browser canā€™t tell how to render a relative unit within a svg element.
Some browsers might be more forgiving by guessing or setting default width values as fallbacks. (Currently most firefox and chromium based browsers use 300px as a fallback).
<style>
svg {
border: 1px solid red;
height: 25px;
}
text {
fill: #fff;
}
</style>
<h2>Use viewBox</h2>
<svg viewBox="0 0 450 25" style="overflow:visible!important;">
<g id="recommendedBadge">
<rect x="0" y="0" rx="15" ry="15" width="100%" height="25" />
<text id="badgeText" x="50%" y="50%" dy="2" dominant-baseline="middle" font-family="Metric" text-anchor="middle" class="CoveoText" data-value="RECOMMENDED" style="text-transform: uppercase;">
Badge Text
</text>
<g class="tooltip" transform="translate(0 25)">
<rect x="0" y="0" width="100%" height="100%" rx="5" ry="5" fill="red" />
<text x="50%" y="50%" dy="5" font-family="Metric" text-anchor="middle" font-weight="100" class="CoveoText" data-value="RecommendedTooltip">tooltip</text>
</g>
</g>
</svg>
<br><br><br>
<h2>Use width</h2>
<svg height="25" width="100%" style="overflow:visible!important;">
<g id="recommendedBadge">
<rect x="0" y="0" rx="15" ry="15" width="100%" height="25" />
<text id="badgeText" x="50%" y="50%" dy="2" dominant-baseline="middle" font-family="Metric" text-anchor="middle" class="CoveoText" data-value="RECOMMENDED" style="text-transform: uppercase;">
Badge Text
</text>
<g class="tooltip" transform="translate(0 25)">
<rect x="0" y="0" width="100%" height="100%" rx="5" ry="5" fill="red" />
<text x="50%" y="50%" dy="5" font-family="Metric" text-anchor="middle" font-weight="100" class="CoveoText" data-value="RecommendedTooltip">tooltip</text>
</g>
</g>
</svg>

Set X and Y value for g element of SVG

I am relatively new in SVG drawing with HTML5.
What I want to do is to make a group of elements in SVG with g element so that all elements inside of that g element can work like a group and all the element's base x and y value can be received from the upper g element.
So, what I have done is something like this-
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g x="1000" y="1000">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
What I expected is all the elements inside the g element will get x="1000" and y="1000" so my expected output is like this-
But I am getting this-
Re-
I don't like to set x and y element in text element. I just want to set relative x and y into the text element if needed, but that should be relative to g element.
Can anyone help me what I need to do to achieve my target with a group in SVG?
<g> elements don't support x or y attributes. You can use a transform instead though.
I've decreased the values from 1000 to 100 as otherwise the output is outside the 500 x 300 canvas of the outer <svg> element.
I've provided additional x and y attributes on the text element so it appears positioned as in your example. If wanted you could put the text itself in a <g> element or an <svg> element.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
or using an additional <g> element to avoid x and y on the text itself.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<g transform="translate(100, 30)">
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</g>
</svg>
Alternatively you could use an inner <svg> element instead of a <g> element as that does support x and y attributes
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<svg x="100" y="100">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</svg>
</svg>

How to pack text inside svg rect

I wish to fit svg text inside a rect. I could use an approach to compare the widths and add line breaks to the text, but it's not tedious.
Is there a more elegant way than this? Maybe by using CSS or d3?
UPDATE:the following code appends foreignObject using d3 but the div is not displayed. (it is there in the code inspecter)
var group = d3.select("#package");
var fo = group.append("foreignObject").attr("x", 15).attr("y", 15).attr("width", 190).attr("height", 90);
fo.append("div").attr("xmlns", "http://www.w3.org/1999/xhtml").attr("style", "width:190px; height:90px; overflow-y:auto").text("Thiggfis the dgdexsgsggs wish to fit insidegssgsgs");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="p"></p>
<svg width="220" height="120" viewBox="0 0 220 120" id="package">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black"/>
</svg>
A namespace cannot be assigned by attr, it's a side effect of element creation. You need an html div so you need to tell d3 that by calling the element xhtml:div, once you do that, d3 will do the rest.
var group = d3.select("#package");
var fo = group.append("foreignObject").attr("x", 15).attr("y", 15).attr("width", 190).attr("height", 90);
fo.append("xhtml:div").attr("style", "width:190px; height:90px; overflow-y:auto").text("Thiggfis the dgdexsgsggs wish to fit insidegssgsgs");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="p"></p>
<svg width="220" height="120" viewBox="0 0 220 120" id="package">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black"/>
</svg>
Here's a simple example of a foreignObject used to insert HTML markup into an SVG:
<svg width="220" height="120" viewBox="0 0 220 120">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black" />
<foreignObject x="15" y="15" width="190" height="90">
<div xmlns="http://www.w3.org/1999/xhtml" style="width:190px; height:90px; overflow-y:auto"><b>This</b> is the <i>text</i> I wish to fit inside <code>rect</code></div>
</foreignObject>
</svg>

SVG element not visible on screen, can see element added in console

I have the following SVG code. The SVG with id "nestedsvg" is being appended in the HTML, I can view it on the console. But it's not visible on the screen. I tried assigning it a z-index of 99 but still it's invisible. Where am I going wrong?
<svg data="BusinessRoleFigure" x="144" y="95"
width="128" height="66" id="outer" style="position: relative;">
<rect x="0" y="0" width="100%" height="100%"
stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"
style="position: relative;"></rect>
<svg id="nestedsvg" x="100%" height="100" width="50">
<rect x="-50" rx="5" ry="5" width="20" height="10" stroke="black"
stroke-width="1" fill="black" z-index="99"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none"
stroke-linejoin="round" stroke="black"
z-index="1" stroke-width="1"></circle>
</svg>
Jsfiddle: http://jsfiddle.net/MxHPq/145/
This is because the rectangle you are drawing is outside of the nested SVG viewport.
That SVG has a width and height of 100x50, and you are drawing a 20x10 rectangle at (-50,0). Meaning the rectangle covers the area from (-50,0) to (-30,10). So it is not visible. By default, objects outside a nested SVG viewport are not visible.
There are two ways to fix this:
Make objects outside the viewport visible. You can do this by setting overflow="visible" on the nested SVG.
<svg data="BusinessRoleFigure" x="144" y="95" width="128" height="66" id="outer">
<rect x="0" y="0" width="100%" height="100%" stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"></rect>
<svg id="nestedsvg" x="100%" height="100" width="50" overflow="visible">
<rect x="-50" rx="5" ry="5" width="20" height="10" stroke="black" stroke-width="1" fill="black"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></circle>
</svg>
Move the rectangle inside the SVG viewport and reposition the SVG so that the rectangle ends up in the same place.
I don't know why you wanted the nested SVG to be at x="100%", but you would need to change that if you go with this solution.
<svg data="BusinessRoleFigure" width="128" height="66" id="outer">
<rect x="0" y="0" width="100%" height="100%" stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"></rect>
<svg id="nestedsvg" x="78" height="100" width="50">
<rect x="0" rx="5" ry="5" width="20" height="10" stroke="black" stroke-width="1" fill="black"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></circle>
</svg>
A few other notes about your original SVG:
x and y coordinates on the root <svg> element have no effect.
z-index currently has no meaning in SVGs. Although this may change for the upcoming SVG2 standard.
position: relative has no meaning in SVGs.
I've removed these things from my modified examples.

Understanding "this" reference in D3.js

I use this code:
d3.selectAll('rect')
.on('mousemove',function(){d3.select(this).attr("fill", "red");})
.on('mouseleave',function(){d3.select(this).attr("fill", "black");})
.on('click', function(){alert(this.tagName);})
;
d3.selectAll('text')
.on('mousemove',function(){d3.select(this.parentNode).select('rect').on('click').apply(this);})
;
.chart rect {
fill: steelblue;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.4/d3.min.js"></script>
<svg class="chart" width="420" height="120">
<g transform="translate(0,0)">
<rect width="40" height="19"></rect>
<text x="37" y="9.5" dy=".35em">4</text>
</g>
<g transform="translate(0,20)">
<rect width="80" height="19"></rect>
<text x="77" y="9.5" dy=".35em">8</text>
</g>
<g transform="translate(0,40)">
<rect width="150" height="19"></rect>
<text x="147" y="9.5" dy=".35em">15</text>
</g>
<g transform="translate(0,60)">
<rect width="160" height="19"></rect>
<text x="157" y="9.5" dy=".35em">16</text>
</g>
<g transform="translate(0,80)">
<rect width="230" height="19"></rect>
<text x="227" y="9.5" dy=".35em">23</text>
</g>
<g transform="translate(0,100)">
<rect width="420" height="19"></rect>
<text x="417" y="9.5" dy=".35em">42</text>
</g>
</svg>
I expect that when hovering over text element will be triggered event for sibling rect elements and showing message "rect". But the message "text" showed, although code select('rect') must be set "this" referenced to rect element.
Why reference to text element is passed to handler?
The this you're passing to .apply() refers to the text element that the event was fired for, not for the rect element you've selected afterwards (selecting an element doesn't change this). You need to call the click handler in the new context:
d3.selectAll('text')
.on('mousemove',function(){
d3.select(this.parentNode).select('rect').each(function() {
d3.select(this).on("click").apply(this);
});
});
Complete demo here.

Categories

Resources