I tried making a small program where in I wrote code to drag a rectangle in svg. The program is quite simple. My problem is that the rectangle drags only diagnolly on the screen and not on the entire web page.
Here is my code..
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%"
onload="Init( evt )" >
<script type="text/ecmascript">
//<![CDATA[
var svgDocument;
var svgRoot;
var newP;
var bmousedown=0;
var myCirc;
function Init(evt){
svgRoot= document.getElementsByTagName("svg")[0];
newP = svgRoot.createSVGPoint();
myCirc = document.getElementById("mycirc");
}
function getMouse(evt){
var position = svgRoot.createSVGPoint();
position.x = evt.clientX;
position.y = evt.clientY;
return position;
}
function onMouseDown(evt){
bmousedown=1;
newP=getMouse(evt);
doUpdate();
}
function onMouseMove(evt){
if(bmousedown){
newP=getMouse(evt);
doUpdate();
}
}
function onMouseUp(evt){
bmousedown=0;
}
function doUpdate(){
myCirc.setAttributeNS(null, "x", newP.x );
myCirc.setAttributeNS(null, "y", newP.y );
}
// ]]></script>
<rect id="mycirc" fill: #bbeeff" x="0" y="0" width="80" height="80"
pointer-events="visible"
onmousedown="onMouseDown(evt)"
onmousemove="onMouseMove(evt)"
onmouseup="onMouseUp(evt)"/>
</svg>
Please help me as I am unable to understand why does it not move on the entire screen.
i see the problem recreated on firefox, but it's not a single problem, your code is all over the place. i suggest going back to the drawing board before posting specific questions.
i'd also recommend a good reference on SVG or using a JS vector graphics library, as it would simplify things a little and will ease up the development a lot, if you're not interested in getting down to the nitty-gritty.
Here is the correct solution: http://jsfiddle.net/mihaifm/5GHJs/
The mistakes I think you made:
onload="Init( evt )" makes the variable evt global, a bad and useless thing to have. All the functions are also global, but this should be ok for this example.
the function calls for onmousedown etc. were using this global evt. (wrong). In order to get the correct event for each call you need to register some handlers.
Related
I have the following SVG code. To scale the circle only, I have to scale the container. I can't scale the circle inside the SVG.
I can access the circle element, but why can't I like other html elements? Is the code scaling the circle or the container?
<html>
<head>
<script>
function scale() {
document.getElementById('container').setAttribute('currentScale', 1.5);
}
</script>
</head>
<body>
<svg height="150" width="150" id="container">
<circle cx="25" r="20" cy="20" fill="blue" id="circle"></circle>
</svg>
<button id="zoom" onclick="scale()">scale</button>
</body>
</html>
I've never tried scaling the whole SVG (using the pure SVG transform attribute). But look like you can just scale each element inside SVG. In this case you have to target (select) the circle first before calling setAttribute on it to modify the transform attribute to scale(1.5,1.5) like this:
function scale() {
document.querySelector("#container > circle")
.setAttribute('transform', 'scale(1.5,1.5)');
}
Here is the Demo. Note that you have to select the option No Wrap - in <head> (on the right hand in the Frameworks & Extensions section). Or better you should attach click handler right in JS code editor (not inline as an attribute in HTML code).
Here is one way to scale just the circle using JS:
Demo: http://jsfiddle.net/hb4nnau0/1/
For convenience, give your circle a transform attribute with no change in scale added. (Alternatively you can add this programmatically on demand.)
`
In your event handler, modify the scale of this transform element:
var circle = document.querySelector('circle'); // Or however, e.g. by id
var scale = circle.transform.animVal.getItem(0); // The first transform
scale.setScale(4,3); // Modify the transform
Update: as of February 2014, Meteor supports reactive SVG, so no workaround is necessary.
Meteor 0.5.9
I would like to create a group of shapes, one for each document in the collection. I can create shapes one at a time in a template, but not inside of an {{#each loop}}.
This works:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
</svg>
</Template>
Template.map.color = function() {
return "green";
};
This does not:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
{{#each colors}}
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
{{/each}}
</svg>
</Template>
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
}
Anything I try to create inside of using {{#each}} just doesn't show up, even though I can create them manually, even with attributes inserted by Meteor through the template.
I also tried just sending a single object {color: "red"} to the template and using {{#with colors}}, and that does not work either. In addition to the SVG, I've also put plain s into the templates to make sure information gets to the template correctly, and those are all working as expected, with {{#each}} and with {{#with}}.
Should I be able to do what I'm trying to do?
(Updated April 1, 2013)
Found a way that combines Handlebars with insertion by Javascript. Have to give credit to this blog entry for figuring this one out:
http://nocircleno.com/blog/svg-and-handlebars-js-templates/
I created the following two files, placed them inside the client folder of a new Meteor directory and I got the html successfully.
Testing.js:
<head>
<title>testing</title>
</head>
<body>
</body>
<template name="map">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
{{#each colors}}
<rect x="0" y="{{yPosition}}" width="100" height="100" fill="{{color}}"/>
{{/each}}
</svg>
</template>
Testing.html:
(function () {
var count = 0;
Template.map.yPosition = function() {
count++;
return (count-1) * 100;
};
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
};
Meteor.startup(function() {
var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElement.width = 500;
svgElement.height = 600;
document.getElementsByTagName("body")[0].appendChild(svgElement);
var svgFragment = new DOMParser().parseFromString(Template.map(), "text/xml");
svgElement.appendChild(svgFragment.documentElement);
});
})();
I came across the same problem experimenting with Meteor and SVG elements and discovered that you can add elements and get them to show up with the two methods below. One option is to just wrap the elements in the each loop in an <svg></svg>, like this:
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
{{#each pieces}}
<svg xmlns="http://www.w3.org/2000/svg"><circle cx="{{x}}" cy="{{y}}" r="1" fill="{{color}}"></circle></svg>
{{/each}}
</svg>
Another options is to (on template render) create an svg element with jQuery that contains the element you want to insert, then use jQuery to grab that inner element and insert it into the svg element already in the DOM, like so (in coffeescript):
for piece in Pieces.find().fetch()
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' class='a'></circle></svg>")
$el.find('circle').appendTo #$('svg')
You could also use something like d3 or RaphaelJS to do the inserting. You can even make the individual elements reactive to your Collection and animate easily by using a library like d3 in the Collection observer callbacks like so (again, coffeescript):
Pieces.find().observe {
added: (piece)=>
# using jquery (could use d3 instead)
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' fill='#{piece.color}' data-id='#{piece._id}'></circle></svg>")
$el.find('circle').appendTo #$('svg')
changed: (newPiece, oldPiece)=>
# using d3 to animate change
d3.select("[data-id=#{oldPiece._id}]").transition().duration(1000).attr {
cx: newPiece.x
cy: newPiece.y
fill: newPiece.color
}
removed: (piece)=>
#$("[data-id=#{piece._id}]").remove()
}
These methods seem to work in latest Chrome, Safari, Firefox browsers on Mac, but I haven't tested in others.
According to the Using Blaze page, Meteor will have first class support of SVG when Blaze is released.
Box size known. Text string length unknown. Fit text to box without ruining its aspect ratio.
After an evening of googling and reading the SVG spec, I'm pretty sure this isn't possible without JavaScript. The closest I could get was using the textLength and lengthAdjust text attributes, but that stretches the text along one axis only.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</svg>
I am aware of SVG Scaling Text to fit container and fitting text into the box
I didn't find a way to do it directly without Javascript, but I found a JS quite easy solution, without for loops and without modify the font-size and fits well in all dimensions, that is, the text grows until the limit of the shortest side.
Basically, I use the transform property, calculating the right proportion between the desired size and the current one.
This is the code:
<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 1000 1000" width="1000" height="1000" xmlns="http://www.w3.org/2000/svg" >
<text id="t1" y="50" >MY UGLY TEXT</text>
<script type="application/ecmascript">
var width=500, height=500;
var textNode = document.getElementById("t1");
var bb = textNode.getBBox();
var widthTransform = width / bb.width;
var heightTransform = height / bb.height;
var value = widthTransform < heightTransform ? widthTransform : heightTransform;
textNode.setAttribute("transform", "matrix("+value+", 0, 0, "+value+", 0,0)");
</script>
</svg>
In the previous example the text grows until the width == 500, but if I use a box size of width = 500 and height = 30, then the text grows until height == 30.
first of all: just saw that the answer doesn't precisely address your need - it might still be an option, so here we go:
you are rightly observing that svg doesn't support word-wrapping directly. however, you might benefit from foreignObject elements serving as a wrapper for xhtml fragments where word-wrapping is available.
have a look at this self-contained demo (available online):
<?xml version="1.0" encoding="utf-8"?>
<!-- SO: http://stackoverflow.com/questions/15430189/pure-svg-way-to-fit-text-to-a-box -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="20cm" height="20cm"
viewBox="0 0 500 500"
preserveAspectRatio="xMinYMin"
style="background-color:white; border: solid 1px black;"
>
<title>simulated wrapping in svg</title>
<desc>A foreignObject container</desc>
<!-- Text-Elemente -->
<foreignObject
x="100" y="100" width="200" height="150"
transform="translate(0,0)"
>
<xhtml:div style="display: table; height: 150px; overflow: hidden;">
<xhtml:div style="display: table-cell; vertical-align: middle;">
<xhtml:div style="color:black; text-align:center;">Demo test that is supposed to be word-wrapped somewhere along the line to show that it is indeed possible to simulate ordinary text containers in svg.</xhtml:div>
</xhtml:div>
</xhtml:div>
</foreignObject>
<rect x="100" y="100" width="200" height="150" fill="transparent" stroke="red" stroke-width="3"/>
</svg>
I've developed #Roberto answer, but instead of transforming (scaling) the textNode, we simply:
give it font-size of 1em to begin with
calculate the scale based on getBBox
set the font-size to that scale
(You can also use 1px etc.)
Here's the React HOC that does this:
import React from 'react';
import TextBox from './TextBox';
const AutoFitTextBox = TextBoxComponent =>
class extends React.Component {
constructor(props) {
super(props);
this.svgTextNode = React.createRef();
this.state = { scale: 1 };
}
componentDidMount() {
const { width, height } = this.props;
const textBBox = this.getTextBBox();
const widthScale = width / textBBox.width;
const heightScale = height / textBBox.height;
const scale = Math.min(widthScale, heightScale);
this.setState({ scale });
}
getTextBBox() {
const svgTextNode = this.svgTextNode.current;
return svgTextNode.getBBox();
}
render() {
const { scale } = this.state;
return (
<TextBoxComponent
forwardRef={this.svgTextNode}
fontSize={`${scale}em`}
{...this.props}
/>
);
}
};
export default AutoFitTextBox(TextBox);
This is still an issue in 2022. There is no way to define bounds and get text to scale in a pure scalable vector graphic. Adjusting the font size manually is still the only solution it seems, and the examples given are quite buggy. Has anybody figured out a clean solution that works? Judging by the svg spec it looks like a pure solution doesn't exist.
And to provide some sort of answer myself, this resource is the best I've found, is hacky, but works much more robustly: fitrsvgtext - storybook | fitrsvgtext - GitHub
I don't think its the solution for what you want to do but you can use textLength
with percentage ="100%" for full width.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text x="0%" y="50%" textLength="100%">blabla</text>
</svg>
you can also add text-anchor="middle" and change the x position to center perfectly your text
this will not change the fontsize and you will have weird space letterspacing...
JSFIDDLE DEMO
i would like to know, which techniques should i use to apply svg filters to raphael paths?
I know that raphael tries to be as much cross browser with IE it can, but i was wondering if there was a way to add the filters using javascript.
I built a library to do this. You can do something like:
var paper = Raphael("test");
var circle = paper.circle(100, 100, 50, 50).attr({fill: "red", stroke: "black"});
circle.emboss();
Have a look at a fiddle: http://jsfiddle.net/chrismichaelscott/5vYwJ/
or the project page: http://chrismichaelscott.github.io/fraphael
It's quite possible to extend Raphaël to add svg filters, for blur look at raphael.blur.js. That can serve as a starting point for adding other filter effects. For a bit more info on filters (along with examples) see the SVG Primer.
One hacky way to use SVG filters with Raphael objects is the following technique. It creates Raphael rectangle and adds filter definition to the same SVG document. This of course doesn't work with older browsers which lack support for inline SVG. But this is not a big problem, because older browsers have also no SVG filter support.
Although this is not jQuery tagged question, for simplicity the code uses jQuery for DOM manipulations. The namespace problem is solutioned using dummy SVG element, which has the advantage that SVG elements can be created using text strings (instead of DOM methods). Let the browser do what browser can!
The working example is in http://jsbin.com/ilinan/1.
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.0.0/raphael-min.js"></script>
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var p = Raphael("cont", 300, 200);
$(p.canvas).attr("id", "p");
var rect = p.rect(10, 10, 100, 100);
$(rect.node).attr("id", "rect");
$("#rect").attr("filter", "url(#innerbewel)");
$("#rect").attr("fill", "red");
var f = "<filter id='innerbewel' x0='-50%' y0='-50%' width='200%' height='200%'>\
<feGaussianBlur in='SourceAlpha' stdDeviation='2' result='blur'/>\
<feOffset dy='3' dx='3'/>\
<feComposite in2='SourceAlpha' operator='arithmetic'\
k2='-1' k3='1' result='hlDiff'/>\
<feFlood flood-color='white' flood-opacity='0.8'/>\
<feComposite in2='hlDiff' operator='in'/>\
<feComposite in2='SourceGraphic' operator='over' result='withGlow'/>\
\
<feOffset in='blur' dy='-3' dx='-3'/>\
<feComposite in2='SourceAlpha' operator='arithmetic'\
k2='-1' k3='1' result='shadowDiff'/>\
<feFlood flood-color='black' flood-opacity='0.8'/>\
<feComposite in2='shadowDiff' operator='in'/>\
<feComposite in2='withGlow' operator='over'/>\
</filter>";
// Create dummy svg with filter definition
$("body").append('<svg id="dummy" style="display:none"><defs>' + f + '</defs></svg>');
// Append filter definition to Raphael created svg
$("#p defs").append($("#dummy filter"));
// Remove dummy
$("#dummy").remove();
$("#rect").attr("fill", "orange");
});
</script>
</head>
<body>
<div id="cont"></div>
</body>
I’m making a set of buttons which use dynamic gradients. I’ve taken care of Firefox 3.6+ and WebKit by using their proprietary CSS extensions and all I need to do is support Opera, iOS and IE9 by using background-image: url("gradient.svg").
This is relatively easy, I made an SVG file, linked it and got it working. However, I’m making a set so I need at least 6 gradients. When I normally do it in images, I create a sprite for fast HTTP access. I’m not sure how to achieve this in SVG – can I use one file and access different parts of its XML by using #identifiers, like XBL does?
My current SVG:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="select-gradient" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="rgb(231,244,248)"/>
<stop offset="100%" stop-color="rgb(207,233,241)"/>
</linearGradient>
<style type="text/css">
rect {
fill: url(#select-gradient);
}
</style>
</defs>
<rect x="0" y="0" rx="6" ry="6" height="100%" width="100%"/>
</svg>
And then I have CSS:
.button-1 {
background-image: url("gradient-1.svg");
}
.button-2 {
background-image: url("gradient-2.svg");
}
I want to do something like this:
.button-1 {
background-image: url("gradient.svg#gradient1");
}
.button-2 {
background-image: url("gradient.svg#gradient2");
}
Is it even possible? Can you help me out? I really don’t wanna push 6 XML files when I can do it with one.
If you just want gradients for button backgrounds, most of this can be acheived in css. For the remaining browsers, ie6 + can user ms filters:
http://msdn.microsoft.com/en-us/library/ms532847.aspx
iOS uses webkit to render, so you can use -webkit vendor prefix. Unfortunately you will still need svg for opera, but this may make it easier (or just use a normal image sprite for opera's 1% of users)
in theory - according to SVG documentation #Params it is possible. You could use 2 params for setting up both colors, you could create multiple rects with different gradients, height set to 0 and then make only one 100% (like ?gradient2=100%)
What you could do is load your SVG file that contains all of the definitions first, and then load your other SVG files.
Using Firefox, jQuery SVG , and a minor shot of framework...
in your XHTML:
<div id="common_svg_defs"><!--ieb--></div>
<div id="first_thing"><!--ieb--></div>
<div id="second_thing"><!--ieb--></div>
in your JavaScript:
var do_stuff = function()
{
// load your common svg file with this goo.
$('#common_svg_defs').svg({
loadURL: 'path/filename.svg',
onLoad: function(svg, error) { run_test(svg, error);} });
}
var run_test = function(svg, error)
{
if (typeof(error) !== "undefined")
{
if (typeof(console.log) !== "undefined")
{
console.log(error);
}
}
else
{
// load your other svg files here, or just
// set a flag letting you know it's ready.
$('#first_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
$('#second_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
}
}
Because the id can be found in the documents scope, the SVG are capable of finding the IRI reference.
This allows you to define things once (that would not otherwise be defined in a css) and avoid id collisions.
Cheers,
Christopher Smithson