I want to manipulate an SVG object with blazor, is it possible to do this via C# client side, or do I need to use javascript.
For example, draw a line programatically based on clicks in the SVG area.
Any pointers would be greatly appreciated. I found a lot on adding SVG component, but nothing on adding elements to the already existing svg component
You can use SVG file contents as the markup in a Blazor component, and then do any of the Blazor-y things you'd normally do.
Put a variable in the svg markup, and build it as a string.
Here's a highly-simplified excerpt:
(MySvgComponent.blazor)
<svg blah blah blah>
<polyline fill="none" stroke="#0074d9"
stroke-width="2" points="#PointString" />
</svg>
#code {
public string #PointString {get;set;}
public void AddPoint (int X, int Y){
#PointString += " " + X + "," + Y;
}
You'll have to add your own code to figure out where you want to add the points. You could make a List<Point> parameter to pass in from a parent or something, and then call AddPoint in a foreach loop in OnInitialized(). You could also very easily replace the stroke color or anything else by replacing literals like "0074d9" with variables "#myColorString."
Handling mouse-click locations will require some fancy JS work. Try using Javascript Interop with something like the following:
How to get the click coordinates relative to SVG element holding the onclick listener?
Related
I am generating a grid, which is a SVG generated using highcharts and I want to reuse it as background of couple of pictures (<img src=https://xxx.xx.xxx.xx/pic_1.svg> & pic_2.svg) how to get this done?
I think might be possible to export the grid SVG to a file first then use it as a file. However, is there anyway simply save a Highchart.SVGRender or Highchart.SVGElement and use it, instead of exporting to a file, which I think will be slow I/O?
thank you.
You can parse a new string by concatenation of data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg">, generated SVG outerHTML value, and then setting yourimage.src by generated string. Here is how to achieve it 'step by step':
Create new SVG element using SVGRenderer from Highcharts:
var dot = chart.renderer.circle(10, 10, 10)
.attr({
fill: 'red',
zIndex: 99
})
.add()
Then, let's create new empty <img> element.
<img id="dot" src='' alt="">
And finally, set <img> src attribute by new generated value:
var svgString = dot.element.outerHTML
var dotImg = document.getElementById('dot')
var string = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">'+svgString+'</svg>'
dotImg.src = string
Live example: https://jsfiddle.net/864s9cwz/
Working with Scala.js, I have created some SVG elements like Text, Line, Rect, and now I am trying to set the style attribute with code like this, where element is of type svg.Stylable:
element.style.fillOpacity = "0.0"
element.style.stroke = "yellow"
element.style.strokeWidth = "2"
I tried different variations of above code, but the desired style does not realize, and when I inspect the element in the Browser, the style attribute is an empty String (""). I am able to set other attributes with no problem (e.g. x, y, width, height).
How do I set the style? Thanks!
For SVG modications in Scala.js I normally use the d3 library (scala.js facade: https://github.com/spaced/scala-js-d3).
Then u can use:
d3.select("#mySvgElement").attr("style", "stroke:yellow; stroke-width:2") // etc.
*Edit: mySvgElement would be the ID of the element i want to change the style for. You can also other different kind of selectors.
I have a webpage with an SVG. On some of its shapes I need to display a tooltip. However, I can't get the tooltip to appear where it should, just some pixels away from the shape itself.
It appears way on the right hand side of the screen, maybe some 300px away.
The code I am using to get the coordinates is as follows:
d3.select("body")
.select("svg")
.select("g")
.selectAll("circle")
.on("mouseover", function(){return tooltip.style("visibility", "visible");})
.on("mousemove", function(){
var svgPos = $('svg').offset(),
/*** Tooltip ***/
//This should be the correct one, but is displaying at all working at all.
/*x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;*/
//This displays a tool tip but way much to the left of the screen.
x = svgPos.left + d3.event.target.cx.animVal.value,
y = svgPos.top + d3.event.target.cy.animVal.value;
Tooltip
window.alert("svgPos: "+svgPos+" top: "+y+"px left: "+x+"px "+d3.event.target.cx.animVal.value);
return tooltip.style("top", x+"px").style("left",y+"px");
})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
I got to this code following this SO post.
I have changed $(ev.target).attr(cx) as it is not returning a value on my machine; d3.event.target.cx is, even though it seems it is not affecting the end result anyway.
What am I doing wrong? Could somebody help me please? Thank you very much in advance for your time.
If your tooltip is an HTML element, then you want to position it relative to the page as a whole, not the internal SVG coordinates, so accessing the cx/cy value is just complicating things. I can't say for sure without looking at your code, but if you have any transforms on your <svg> or <g> elements, then that could be what's throwing you off.
However, there is a much easier solution. Just access the mouse event's default .pageX and .pageY properties, which give the position of the mouse relative to the HTML body, and use these coordinates to position your tooltip div.
Example here: http://fiddle.jshell.net/tPv46/1/
Key code:
.on("mousemove", function () {
//console.log(d3.event);
return tooltip
.style("top", (d3.event.pageY + 16) + "px")
.style("left", (d3.event.pageX + 16) + "px");
})
Even with rotational transforms on the SVG circles, the mouse knows where it is on the page and the tooltip is positioned accordingly.
There are other ways to do this, including getting a tooltip to show up in a fixed location relative to the circle instead of following the mouse around, but I just checked the examples I was working on and realized they aren't cross-browser compatible, so I'll have to standardize them and get back to you. In the meantime, I hope this gets you back on track with your project.
Edit 1
For comparison, here is the same example implemented with both an HTML tooltip (a <div> element) and an SVG tooltip (a <g> element).
http://fiddle.jshell.net/tPv46/4/
The default mouse event coordinates may be great for positioning HTML elements that are direct children of <body>, but they are less useful for positioning SVG elements. The d3.mouse() function calculates the mouse coordinates of the current event relative to a specified SVG element's coordinate system, after all transformations have been applied. It can therefore be used to get the mouse coordinates in the form we need to position an SVG tooltip.
Key code:
.on("mousemove", function () {
var mouseCoords = d3.mouse(
SVGtooltip[0][0].parentNode);
//the d3.mouse() function calculates the mouse
//position relative to an SVG Element, in that
//element's coordinate system
//(after transform or viewBox attributes).
//Because we're using the coordinates to position
//the SVG tooltip, we want the coordinates to be
//with respect to that element's parent.
//SVGtooltip[0][0] accesses the (first and only)
//selected element from the saved d3 selection object.
SVGtooltip
.attr("transform", "translate(" + (mouseCoords[0]-30)
+ "," + (mouseCoords[1]-30) + ")");
HTMLtooltip
.style("top", (d3.event.pageY + 16) + "px")
.style("left", (d3.event.pageX + 16) + "px");
})
Note that it works even though I've scaled the SVG with a viewBox attribute and put the tooltip inside a <g> with a transform attribute.
Tested and works in Chrome, Firefox, and Opera (reasonably recent versions) -- although the text in the SVG tooltip might extend past its rectangle depending on your font settings. One reason to use an HTML tooltip! Another reason is that it doesn't get cut off by the edge of the SVG.
Leave a comment if you have any bugs in Safari or IE9/10/11. (IE8 and under are out of luck, since they don't do SVG).
Edit 2
So what about your original idea, to position the tooltip on the circle itself? There are definite benefits to being able to position the tip exactly: better layout control, and the text doesn't wiggle around with the mouse. And most importantly, you can just position it once, on the mouseover event, instead of reacting to every mousemove event.
But to do this, you can no longer just use the mouse position to figure out where to put the tooltip -- you need to figure out the position of the element, which means you have to deal with transformations. The SVG spec introduces a set of interfaces for locating SVG elements relative to other parts of the DOM.
For converting between two SVG transformation systems you use SVGElement.getTransformToElement(SVGElement); for converting between an SVG coordinate system and the screen, you use SVGElement.getScreenCTM(). The result are transformation matrices from which you can
extract the net horizontal and vertical translation.
The key code for the SVG tooltip is
var tooltipParent = SVGtooltip[0][0].parentNode;
var matrix =
this.getTransformToElement(tooltipParent)
.translate(+this.getAttribute("cx"),
+this.getAttribute("cy"));
SVGtooltip
.attr("transform", "translate(" + (matrix.e)
+ "," + (matrix.f - 30) + ")");
The key code for the HTML tooltip is
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"),
+this.getAttribute("cy"));
absoluteHTMLtooltip
.style("left",
(window.pageXOffset + matrix.e) + "px")
.style("top",
(window.pageYOffset + matrix.f + 30) + "px");
Live example: http://fiddle.jshell.net/tPv46/89/
Again, I'd appreciate a confirmation comment from anyone who can test this in Safari or IE -- or any mobile browser. I'm pretty sure I've used standard API for everything, but just because the API is standard doesn't mean it's universally implemented!
I want to put a rectangle around a text in SVG.
The height of the text is known to me (the font-size attribute of the text element). But the width is dependent on the actual content. Using getBBox() or getComputedTextLength() should work. But this only works after rendering.
Is there a way to specify that in an other way? For example defining the x and width attributes relative to other values? I didn't find anything like that in the SVG Spec.
Figuring where text ends presumably requires roughly the same underlying code path as the rendering itself implements - going through the width of each character based on the font and style, etc... As I am not aware the SVG standards define a method for directly getting this information without doing the actual full rendering, till such methods emerge or are reported here by others, the approach should be to render invisibly before doing the actual rendering.
You can do that in a hidden layer (z-index, opacity and stuff) or outside the visible viewport, whichever works best in experimentation. You just need to get the browser do the rendering to find out, so you render invisibly for that sake, then use getComputedTextLength()
I know this is old, but a few ideas:
If you can choose a mono-spaced font, you know your width by a simple constant multiplication with glyph count
If you are bound to proportional fonts, you can find an average glyph size, do the math as with mono-space, and leave enough padding. Alternatively you can fill the padding with text element textLength attribute. If the constant is chosen carefully, the results are not very displeasing.
EDIT: As matanster found it to be hacky
Predetermine glyph widths with getComputedTextLength() and build a lookup table. Downside is that it does not account for kerning, but if your cache size is not a problem, you can append glyph-pair widths to this lookup.
Going beyond that is to find some way to do server side rendering: Is there a way to perform server side rendering of SVG graphics using React?
It is possible using canvas with measureText():
// Get text width before rendering
const getTextWidth = (text, font) => {
const element = document.createElement('canvas');
const context = element.getContext('2d');
context.font = font;
return context.measureText(text).width;
}
// Demo
const font = '16px serif';
const text = 'My svg text';
const textWidth = getTextWidth(text, font);
document.body.innerHTML = `
<svg>
<text x="0" y="20" font="${font}">${text}</text>
<rect x="0" y="30" width="${textWidth}" height="4" fill="red" />
</svg>
`;
Adapted from https://stackoverflow.com/a/31305410/1657101
For fun I am trying to see how far I can get at implementing an SVG browser client for a RIA I'm messing around with in my spare time.
But have hit what appears to be a HUGE stumbling block. There is no word wrap!!
Does anyone know of any work around (I'm thinking some kind of JavaScript or special tag I don't know)?
If not I'm either going to have to go the xhtml route and start sticking HTML elements in my SVG (ouch), or just come back again in ten years when SVG 1.2 is ready.
This SVG stuff is baffling, isn't it ?
Thankfully, you can achieve some good results, but it takes more work than using the HTML 5 .
Here's a screenshot of my ASP.Net / SVG app, featuring a bit of "faked" word wrapping.
The following function will create an SVG text element for you, broken into tspan pieces, where each line is no longer than 20 characters in length.
<text x="600" y="400" font-size="12" fill="#FFFFFF" text-anchor="middle">
<tspan x="600" y="400">Here a realy long </tspan>
<tspan x="600" y="416">title which needs </tspan>
<tspan x="600" y="432">wrapping </tspan>
</text>
It's not perfect, but it's simple, fast, and the users will never know the difference.
My createSVGtext() JavaScript function takes three parameters: an x-position, y-position and the text to be displayed. The font, maximum-chars-per-line and text color are all hardcoded in my function, but this can be easily changed.
To display the right-hand label shown in the screenshot above, you would call the function using:
var svgText = createSVGtext("Here a realy long title which needs wrapping", 600, 400);
$('svg').append(svgText);
And here's the JavaScript function:
function createSVGtext(caption, x, y) {
// This function attempts to create a new svg "text" element, chopping
// it up into "tspan" pieces, if the caption is too long
//
var svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
svgText.setAttributeNS(null, 'x', x);
svgText.setAttributeNS(null, 'y', y);
svgText.setAttributeNS(null, 'font-size', 12);
svgText.setAttributeNS(null, 'fill', '#FFFFFF'); // White text
svgText.setAttributeNS(null, 'text-anchor', 'middle'); // Center the text
// The following two variables should really be passed as parameters
var MAXIMUM_CHARS_PER_LINE = 20;
var LINE_HEIGHT = 16;
var words = caption.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
if (testLine.length > MAXIMUM_CHARS_PER_LINE)
{
// Add a new <tspan> element
var svgTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
svgTSpan.setAttributeNS(null, 'x', x);
svgTSpan.setAttributeNS(null, 'y', y);
var tSpanTextNode = document.createTextNode(line);
svgTSpan.appendChild(tSpanTextNode);
svgText.appendChild(svgTSpan);
line = words[n] + " ";
y += LINE_HEIGHT;
}
else {
line = testLine;
}
}
var svgTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
svgTSpan.setAttributeNS(null, 'x', x);
svgTSpan.setAttributeNS(null, 'y', y);
var tSpanTextNode = document.createTextNode(line);
svgTSpan.appendChild(tSpanTextNode);
svgText.appendChild(svgTSpan);
return svgText;
}
The logic for word-wrapping is based on this HTML5 Canvas tutorial
I hope you find this useful !
Mike
http://www.MikesKnowledgeBase.com
UPDATE
One thing I forgot to mention.
That "Workflow diagram" screen that I've shown above was originally just written using an HTML 5 canvas. It worked beautifully, the icons could be dragged, popup menus could appear when you clicked on them, and even IE8 seemed happy with it.
But I found that if the diagram became "too big" (eg 4000 x 4000 pixels), then the would fail to initialise in all browsers, nothing would appear - but - as far as the JavaScript code was concerned, everything was working fine.
So, even with error-checking, my diagram was appearing blank, and I was unable to detect when this showstopper problem was occurring.
var canvasSupported = !!c.getContext;
if (!canvasSupported) {
// The user's browser doesn't support HTML 5 <Canvas> controls.
prompt("Workflow", "Your browser doesn't support drawing on HTML 5 canvases.");
return;
}
var context = c.getContext("2d");
if (context == null) {
// The user's browser doesn't support HTML 5 <Canvas> controls.
prompt("Workflow", "The canvas isn't drawable.");
return;
}
// With larger diagrams, the error-checking above failed to notice that
// the canvas wasn't being drawn.
So, this is why I've had to rewrite the JavaScript code to use SVG instead. It just seems to cope better with larger diagrams.
There is also foreignObject tag. Then you can embed HTML in SVG which gives the greatest flexibility. HTML is great for document layout and has been hacked to no end to support application layout, drawing, and everything us developers want. But it's strength is word wrapping and document layout. Let HTML do what it does best, and let SVG do what it does best.
http://www.w3.org/TR/SVG/extend.html
This works for most browsers FireFox, Opera, Webkit, except IE (as of IE11). :-( Story of the web ain't it?
SVGT 1.2 introduces the textArea element http://www.w3.org/TR/SVGTiny12/text.html#TextInAnArea , but it is only experimentally supported by Opera 10 at the moment. I don't know if other browsers will ever plan on implementing it, though I hope they will.
Per this document, it appears that tspan can give the illusion of word wrap:
The tspan tag is identical to the text tag but can be nested inside text tags and inside itself. Coupled with the 'dy' attribute this allows the illusion of word wrap in SVG 1.1. Note that 'dy' is relative to the last glyph (character) drawn.
The svg.js library has a svg.textflow.js plugin. It's not ultra fast but it does the trick. It even stores overflowing text in a data attribute so you can use it to create continuously flowing columns. Here the text flow example page.
An alternative method is to use Andreas Neuman's text box object.
These days, flowPara can do word wrapping, but I have yet to find a browser that supports it properly.
I've been looking for a solution about word wrapping in svg so many hours (or many days).
If you can in your app, edit your code to put some tspan, or any other method, go in it.
Text wrapping will be implement in the 1.2 version but except opera, no browser fully implement it yet (4 years, the specification are on the W3 ...).
Because I had to use some alignment settings, i couldn't use any of the code that many forum can provide (no foreign object, no carto script or anything).
If I post this message, it's just in order to be usefull to some other people when googling word wrapping svg because this post on the top result and in many case, this post doesn't help.
Here is a cool, easy and light solution :
http://dev.w3.org/SVG/profiles/1.1F2/test/svg/text-dom-01-f.svg