editable field with in svg - svg

I am trying to provide edit option for label inside svg element using text box with in foreign object.
If text written in the text box is larger than width of the text box,text given overflows outer the text box field.
<svg>
<foreignObject x="30" y="15" width="200" height="40" class="editable" style="cursor: default;">
<div xmlns="http://www.w3.org/1999/xhtml">
<input type="text" class="editable-div" style="width:120px;">
</div>
</foreignObject>
</svg>
sample

Related

Sizing an SVG element according to png/jpeg image inside

Trying to wrap my head around SVGs and displaying images. I asked a related question just a few hours ago but I seems to have managed to figure that part out, now I'm stuck with trying to figure out the relation between viewport, viewbox and the image tag inside it.
My first understanding of viewbox was that it was basically defining the coordination system. having it set to "0 0 100 100" meant "upper left corner is called 0,0 and 100,100 is the bottom right coordinate" then if you draw something like a rect you use 0-100 to reach the corners. Then you use css around it set the size you want on the container and all inside the SVG tag is scaled accordingly.
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<div class="container-fluid">
<div class="row" style="height: 100vh;">
<div class="col-2" style="background: pink">
1 of 3
</div>
<div class="col-8 bg-dark">
<p>
A bit of text
</p>
<svg viewBox="0 0 100 100" style="outline: 1px solid red" preserveAspectRatio="xMidYMid meet">
<defs>
<mask id="image-mask" x="0" y="0" width="100" height="100">
<rect x="0" y="0" width="100" height="100" fill="#F000FF" />
<rect x="33" y="0" width="33" height="100" fill="white" />
</mask>
</defs>
<image width="100" height="100" xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Panorama_Gendarmenmarkt-Berlin-Huntke-2008.jpg/640px-Panorama_Gendarmenmarkt-Berlin-Huntke-2008.jpg" mask="url(#image-mask)" />
</svg>
<p>
Some more text
</p>
</div>
<div class="col-2" style="background: orange">
3 of 3
</div>
</div>
</div>
That seems very wrong.
After tweaking and testing and poking around my current understanding is "if I want to display a non-vector image inside the SVG will I have to set the viewport and the image height/width to match the aspect ratio of the image, the numbers don't matter as long as the ratio is correct". So the image I have here is 640x170px. Normalising to width 100 the height is 26.56. So I set viewport to 0 0 100 26.56 and image width/height to 100 and 26.56 respectively:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<div class="container-fluid">
<div class="row" style="height: 100vh;">
<div class="col-2" style="background: pink">
1 of 3
</div>
<div class="col-8 bg-dark">
<p>
A bit of text
</p>
<svg viewBox="0 0 100 26.56" style="outline: 1px solid red" preserveAspectRatio="xMidYMid meet">
<defs>
<mask id="image-mask" x="0" y="0" width="100" height="100">
<rect x="0" y="0" width="100" height="100" fill="#F000FF" />
<rect x="33" y="0" width="33" height="100" fill="white" />
</mask>
</defs>
<image width="100" height="26.56" xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Panorama_Gendarmenmarkt-Berlin-Huntke-2008.jpg/640px-Panorama_Gendarmenmarkt-Berlin-Huntke-2008.jpg" mask="url(#image-mask)" />
</svg>
<p>
Some more text
</p>
</div>
<div class="col-2" style="background: orange">
3 of 3
</div>
</div>
</div>
That seems to work, and it works for the commented out image as well when I adjust the numbers to fit it's aspect ratio of 100x66.56. I haven't really been able to find much info when it comes to the viewBox and images so my worry is that I'm just lucky and guessed something which works for two examples.
So the answers I'm looking for are:
Are my assumptions correct?
Does this mean that when I want to be able to show different aspect ratios of images do I have to adjust the viewport and the image width/height with javascript for each image?
Is there any more efficient way of solving this? Like "width: 100%; Height: auto;" and make it wrap all aspect rations of images?

How to turn a <circle> (that's inside an svg) into a checkbox?

PART 1 -
So I have a bunch of <circle>'s inside a SVG, and I want those circles to be checkboxes. And after that I want to:
PART 2 -
When circle 1 (which is now a checkbox) is clicked, then it is checked. But all the other circles now get unchecked.
This is what I've already tried:
PART 1 - Turning the SVG into a checkbox:
<circle opacity="0.5" cx="842" cy="451.814" r="25.582" class="svg_spot" id="1" fill="#FFB60C" >
<animate attributeName="r" values="25.582; 33; 25.582" keyTimes="0; 0.5; 1" begin="0s" dur="1s" repeatCount="indefinite" calcMode="linear" />
<input type="checkbox" id="spot1" name="spot" class="common_selector spot_id" value="spot1">
</circle>
PART 2 -
$('input[name=spot]').click (function (){
$(this).attr('checked', true);
$('input[name=spot]').not(this).attr('checked', false);
});
Thanks for your time guys. Would appreciate any help!
It is possible to get this working without any Javascript.
How this works:
Put the SVG in the label of the input. Clicking on the SVG (ie the label) will thus activate the field.
Make the actual <input> field invisible, so that all you see is the label.
Style the SVG based on whether the field is selected, by using the :checked pseudo-selector.
// just here to prove that the form value is changing
// prints value of spot field when inputs change
document.querySelectorAll("#spot1, #spot2, #spot3")
.forEach((elem) => elem.addEventListener("change", (evt) => console.log(document.myform.spot.value)));
.common_selector {
position: absolute;
opacity: 0;
}
.common_selector + label svg {
width: 50px;
height: 50px;
fill: red;
}
.common_selector:checked + label svg {
fill: green;
}
<form name="myform">
<input type="radio" id="spot1" name="spot" class="common_selector spot_id" value="spot1">
<label for="spot1">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50"/>
</svg>
</label>
<input type="radio" id="spot2" name="spot" class="common_selector spot_id" value="spot2">
<label for="spot2">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50"/>
</svg>
</label>
<input type="radio" id="spot3" name="spot" class="common_selector spot_id" value="spot3">
<label for="spot3">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50"/>
</svg>
</label>
</form>
<input> is not a valid SVG element - it is a HTML element, so this won't work. You can either:
wrap an input element inside a <foreignObject> element and do it that way, or
you could use positioning to place the input element over the circle. But fair warning - form elements haven't always played well when they're positioned over other types of elements. Or
You can manually draw SVG that looks like an input element and use JavaScript to make it behave like one. or
Since you just need a circle, why not wrap the input element in a Div with an appropriate border radius and make a circle that way.
PART 1:
Use <foreignObect> to display any HTML element inside an SVG:
<foreignObject x="20" y="20" width="100" height="100">
<input type="checkbox" id="spot1" name="spot" class="common_selector spot_id"
value="spot1">
</foreignObject>
Then you can use css to hide default styling of this input field and position your circle over it. You can read about it here: https://www.w3schools.com/howto/howto_css_custom_checkbox.asp
PART 2:
Use Radio Buttons instead of Checkboxes. Checkboxes allow more than one selection. Radio buttons are what you need here. Read about it here: https://www.w3schools.com/tags/att_input_type_radio.asp
I hope I understand your question. You can not change an svg element into an input however you can try to mimic one.
// selects all the circles with a class of radio
let inputs = document.querySelectorAll(".radio")
// for every circle
inputs.forEach(i =>{
//when the circle is clicked
i.addEventListener("click", ()=>{
// remove the class checked from all the circles but the clicked one
inputs.forEach(j =>{if(i!==j)j.classList.remove("checked") })
// toggle the class checked on the clicked one
i.classList.toggle("checked")
})
})
svg{border:1px solid}
.checked{fill:red}
<svg id="theSVG" viewBox="800 410 300 85" width="300">
<circle class="radio" opacity="0.5" cx="842" cy="451.814" r="25.582" fill="#FFB60C" stroke="#FFB60C" stroke-width="10" />
<circle class="radio" opacity="0.5" cx="950" cy="451.814" r="25.582" fill="#FFB60C" stroke="#FFB60C" stroke-width="10" />
<circle class="radio" opacity="0.5" cx="1050" cy="451.814" r="25.582" fill="#FFB60C" stroke="#FFB60C" stroke-width="10" />
</svg>
It sounds like you want to use an SVG as a type of "selector" like a color selector or, in my case a planet selector. Here I have a SVG representing the solar system where the user clicks on a planet to select it. Since it sounds like you only want to select a single item then a radio button is the better option, however the method below should work with either.
As others have suggested the foreignObject element can be used. I had issues getting it work without specifically declaring a prefixed namespaces for the svg and saving the file with a .xhtml extension.
Of course there are plenty of ways to do something similar with javaScript, however a big advantage of using an underlying radio button is that it takes care of all the logic and makes validation easier.
I used an empty div over the circle that is acting as a label for the radio button. Since clicking on the label activates the radio button the circle now functions as a radio button!
The SVG does not show up in the snippet, so the div's are shaded for visibility. The actual radio buttons can be hidden if desired.
div{
border: red solid 1px;
background-color: black;
opacity: 20%;
}
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
<p>Select a planet</p>
<form name="planetData">
<input id="venus" name="planet" type="radio" value="venus" />
<label for="venus">Venus</label>
<input id="earth" name="planet" type="radio" value="earth" />
<label for="venus">Earth</label>
<input id="mars" name="planet" type="radio" value="mars" />
<label for="venus">Mars</label>
</form>
<svg:svg height="100" width="100" viewbox="0 0 100 100">
<svg:circle cx="0" cy="50" r="10" fill="yellow"/>
<svg:circle cx="30" cy="75" r="10" fill="green"/>
<svg:circle cx="60" cy="30" r="10" fill="blue"/>
<svg:circle cx="90" cy="60" r="10" fill="red"/>
<svg:foreignObject x="20px" y="65px" height="20px" width="20px">
<label for="venus">
<div style="height: 30px; width: 30px;">
</div>
</label>
</svg:foreignObject>
<svg:foreignObject x="50" y="20" height="20" width="20">
<label for="earth">
<div style="height:20px; width: 20px;"></div>
</label>
</svg:foreignObject>
<svg:foreignObject x="80" y="50" height="20" width="20">
<label for="mars">
<div style="height:40px; width: 40px;"></div>
</label>
</svg:foreignObject>
</svg:svg>
</html>

Is there a way to apply clip-path to a div without CSS?

I'm trying to clip a div with svg clip-path, because css rule clip-path: url(#foo) have poor support. So I thought may be i can attach svg clip-path to a div with html attribute clip-path="#foo". Like in the example here - link. But it doesn't work.
This is my svg element and div.
<svg class="svg-graphic">
<defs>
<clippath id="hexagonal-mask">
<polygon points="130,0 0,160 0,485 270,645 560,485 560,160" />
</clippath>
</defs>
<div class="element" clip-path="#hexagonal-mask">
<div class="content">
<h2>How it works</h2>
<p>
Download<span class="icon"></span>
</p>
<p>
Watch video and Play games<span class="icon"></span>
</p>
<p>
Choose your <span class="icon"></span>
</p>
</div>
</div>
</svg>
When i open an element inspector, i see that my div somehow appears outside of the svg element.
http://prntscr.com/ay7kxt
And clip-path doesn't apply.
Am i doing something wrong or is this aproach just doesnt work with divs, only with shapes like , ?

Angular 2 - binding input form data to SVG text element

I'm looking to bind a form input string to an svg text element in an Angular 2 template. I'm not sure how to do this and would appreciate any help.
<svg class="box">
<rect class="largeFrame" x="40" y="10" rx="10" ry="10"/>
<text class="name" x ="400" y="40">Name</text>
<text class="personName" x="400" y="55" [textContent]="personName.value"></text>
</svg>
<form #f="ngForm">
<div class="input">
<label for="name">Enter Your Name</label>
<input type="text" [(ngModel)]="personName.value" id="name">
</div>
[textContent] is not working... can anyone clue me in on the best syntax? Or suggest a method for binding SVG and inputs in Angular 2.
Thanks!
Looks like I needed to use [()] to bind the data.
I also needed to give personName.value an initial value.
<text class="personName" x="400" y="55" [(textContent)]="personName.value"></text>
use interpolation binding, which will bind to the textContent property for you
use the safe navigation operator to guard against null and undefined, then you won't need to specify an initial value
<text class="personName" x="400" y="55">{{personName?.value}}</text>

How to create a flow layout in SVG using D3.js?

Say that I have an SVG container with a couple of shapes:
<svg>
<g class="container">
<rect id="first" x="0" y="0" width="100" height="100" fill="red" />
<rect id="second" x="100" y="0" width="100" height="100" fill="green" />
<rect id="third" x="200" y="0" width="100" height="100" fill="blue" />
</g>
</svg>
Using D3 I will manipulate the width of these shapes, for example in transitions. How do I make sure that the rects will always stay in this order, without any space between them? That is, if I modify the width of first, x of second and third will update instantaneously.
Option A: Create a treemap and set the sticky option to true: .sticky(true). The treemap layout provides you with x, y, width, and heigth values that you can use to manipulate your DOM/SVG. The sticky option takes care of smooth transitions.
Option B: Use plain html elements, such as div instead of svg:rect elements. If you really just manipulate the width, that should be the more reasonable option:
<style>
#container div{ float: left; }
</style>
<div id="container">
<div id="first" style="width:100px; height:100px; background-color:red;" ></div>
<div id="second" style="width:100px; height:100px; background-color:green;" ></div>
<div id="third" style="width:100px; height:100px; background-color:blue;" ></div>
</div>
Using plain html you can manipulate the width and the browser's layout/CSS engine handles the floating. D3 is not restricted to SVG, it can also handle normal html elements (the treemap example also uses div elements).
Btw: In d3 you should not manipulate the DOM directly. Always think of the underlying data and make the updates data driven, i.e., when using a treemap you would the set the item.value of a data item in your source data, e.g.:
data = [ {value: 100}, {value:200}, {value:100} ]
//...
updateData() //changes some values in your data
drawChart() //draws the whole chart based on the data, e.g., computes the x, y,
//width, height from the `item.value` (e.g., via d3.layout.treemap)
//and manipulates/animates the DOM via d3
I did a variation of a tree layout to make it into a flow layout. You can see the demo here and view the Gist here.

Resources