I am trying to draw a rectangle at the point in a canvas where the user clicks. I want my window to be scale- able so I have placed the Canvas in a viewbox but this doesn't draw the rectangle on the specified point but is variable based on the scaling of the viewbox.
here is my code
<Viewbox MouseLeftButtonDown="Viewbox_MouseLeftButtonDown" Width="300" Height="300">
<Canvas Name="Surface" MouseLeftButtonDown="Surface_MouseRightButtonDown" Background="Transparent" Width="300" Height="300"></Canvas>
</Viewbox>
here is the viewbox mouse down event
private void Viewbox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rect = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Black };
Surface.Children.Add(rect);
Canvas.SetLeft(rect, e.GetPosition(this).X);
Canvas.SetTop(rect, e.GetPosition(this).Y);
}
The result is the same even if I call the Canvas mouse event.
The Viewbox control has no background, so it can't receive mouse events. I would wrap the Canvas in a Border with the Background set to transparent, and attach my event handler there, instead. It also doesn't make much sense to set the width and height of the Viewbox, since you presumably want it to scale with the content.
<Viewbox Width="300" Height="300">
<Border Background="Transparent"
MouseLeftButtonDown="Border_MouseLeftButtonDown"
<Canvas Name="Surface" Background="Transparent" Width="300" Height="300"/>
</Border>
</Viewbox>
Your other problem is that you are getting the mouse position relative to this. I have no idea what this is, but you want to get the mouse position relative to the Canvas.
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var rect = new Rectangle { Width = 10, Height = 10, Fill = Brushes.Black };
Surface.Children.Add(rect);
Canvas.SetLeft(rect, e.GetPosition(Surface).X);
Canvas.SetTop(rect, e.GetPosition(Surface).Y);
}
Related
I have the SVG path, and I want to get point's y coordinate.
SVG line:
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="340" viewBox="0 0 1920 340">
<path clip-rule="evenodd" fill="none" stroke="#B3B3B3" stroke-miterlimit="10" d="M-.5 60.3c88.8-42 232.2-89.1 355.2-34.4C549.3 112.5 640.3 163 720.3 192.4c80 29.4 278.9 116.9 451.3 61.4 172.3-55.4 328-112.5 568.3-23.2 100.1 43 151.4 65.8 179.6 79.3"/>
</svg>
I try this answer code, but it doesn't fit my need. I think path.getTotalLength() is not great for my question.
How can I do?
Update:
What my project want to do: JSFiddle
I have no idea how red points can match the line for each device size.
I suggest that you try paperjs. It’s a JS graphics library that uses HTML <canvas> elements as its graphics context(s). Here’s an example using your curve and a vertical line at mouse.x, which intersections shown.
let wave_path
let bounds
window.onload = function() {
//paper is a library for working with canvases; it's like a graphics library that works with a canvas
//as its GUI window. Each canvas context is its own PaperScope, and the global paper variable is a reference
//to the currently active PaperScope.
paper.setup(document.getElementById('paper-canvas'))
//import wave svg. however, if you integrate paperjs into your page, you might as well draw the curve directly onto the
//canvas with paper, rather than creating an invisible svg element that you then import.
let wave_svg = paper.project.importSVG(document.getElementById('svg-wave'))
wave_svg.visible = true // Turn off the effect of display:none
//fit wave into paper canvas
bounds = paper.view.bounds
wave_svg.fitBounds(bounds)
wave_path = wave_svg.children[0] //get contained path
wave_path.strokeColor = 'black'
wave_path.fillColor = null
//set event handlers on paper canvas
paper.view.onMouseMove = mouse_move
}
function mouse_move(event) {
let mouse_location = event.point
//clear canvas before redrawing
paper.project.clear()
//when creating a graphical object with paper, it automatically gets drawn to the canvas at the end of an event handler
//draw vertical line to intersect with
let line = new paper.Path(new paper.Point(mouse_location.x, 0), new paper.Point(mouse_location.x, bounds.height))
line.strokeColor = 'black'
//redraw wave path
new paper.Layer(wave_path)
//draw intersections
let intersections = line.getIntersections(wave_path)
for (intersection of intersections) {
let circle = new paper.Path.Circle(intersection.point, 5)
circle.strokeColor = 'red'
circle.fillColor = 'white'
}
}
<!DOCTYPE html>
<html>
<head>
<title>SVG Intersection Demo</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.2/paper-core.min.js"></script>
</head>
<body>
<svg id="svg-wave" style="display:none;" xmlns="http://www.w3.org/2000/svg" width="1920" height="340">
<path clip-rule="evenodd" fill="none" stroke="#B3B3B3" stroke-miterlimit="10" d="M-.5 60.3c88.8-42 232.2-89.1 355.2-34.4C549.3 112.5 640.3 163 720.3 192.4c80 29.4 278.9 116.9 451.3 61.4 172.3-55.4 328-112.5 568.3-23.2 100.1 43 151.4 65.8 179.6 79.3"/>
</svg>
<div style="text-align:center;">
<canvas id="paper-canvas" style="width:80%;"></canvas>
</div>
</body>
</html>
I want something like below. Initially there will be a single word when user enters multiple words the size of the box increases. How can I achieve this? Anyone have any idea as to how to proceed on this ?
You can compute the length of the text using http://www.w3.org/TR/SVG/text.html#__svg__SVGTextContentElement__getComputedTextLength
and then you can resize the rect that depends on the textLength. You can call resize function when onkeydown event fires.
Here is an example of resizing the rect when the text length is changed by interval.
<svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect width="100" height="100" style="fill:rgb(255,255,255);stroke-width:3;stroke:rgb(0,0,0)" ></rect>
<text x="20" y="40">123</text>
</svg>
<script>
var textElement = document.getElementsByTagName('text')[0];
var rectElement = document.getElementsByTagName('rect')[0];
resizeRect();
setInterval(resizeRect, 1000);
function resizeRect(){
textElement.textContent += 0
var textLength = textElement.getComputedTextLength();
rectElement.setAttribute("width", 50 + textLength)
}
</script>
You can find a fiddle here: https://jsfiddle.net/0dvu604g/
I'm using this to select the color i'm going to paint my rectangle, and with rgb its working.
style = "fill:rgb(144,0,0)"
Now if I try hsl no matter which value I set, it will always be black.
style = "hsl(28, 87%, 67%)"
Whole page code:
<svg width='200.000' height='200.00' xmlns='http://www.w3.org/2000/svg'>
<rect x='10.000' y='10.000' width='50.00' height='50.00' style='hsl(28, 87%, 67%)' />
<rect x='70.000' y='10.000' width='50.00' height='50.00' style='hsl(28, 87%, 67%)' />
</svg>
Is there is browser-independant way getting the browser to centre on a particular shape (by 'id' attribute) ?
I have tried using xlinks wrapped around shapes like this:
<a xlink:href="#node24"> .... </a>
I have reasonably busy (100+ shapes) directed graph diagrams (generated from dot): and when I load them up in Chrome , more often than not, the intial screen is just blank - forcing the user to use scrollbars to find the diagram at all.
I'm afraid I don't have any good news for you.
For stand-alone SVG documents, you can manipulate the part of an SVG displayed when following a link by linking to a <view> element (distinct from, but making use of, the SVG "viewBox" attribute). The view element specifies the viewBox to use and possibly some other parameters, and the graphic will be displayed with those parameters instead of the default ones.
Example code:
<?xml version="1.0" standalone="no"?>
<!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"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMin meet" >
<circle cx ="50" r="40"/>
<view id="panUp" viewBox="0 -50 100 100" />
<view id="zoomIn" viewBox="25 25 50 50" />
</svg>
If you linked to the file as a whole it would show you an image with half a circle centered at the top of the screen.
If, however, you linked to it like http://example.com/sample.svg#panUp, the circle would be the same size but centered on screen. If you linked to http://example.com/sample.svg#zoomIn, you'd only see the bottom edge of a circle that is twice as big.
(I don't have anywhere to host the file that can serve up raw SVG files, but this CodePen uses data URI to show the effects, although the data URI fragment identifiers doesn't seem to work in Firefox.)
You are supposed to be able to even specify the desired viewBox, transforms, or other attributes as part of the URL fragment (like http://example.com/sample.svg#myView(viewBox(0,0,200,200))), but I don't think that's widely implemented -- it had no effect on either Firefox or Chrome.
And even <view> fragments don't seem to work when the SVG is embedded within an HTML document. So unless your SVG is stand-alone, creating a view for each element (or one view that your dynamically change to match the clicked element), isn't going to be worth the trouble.
So what does work?
The default behaviour, when linking to a fragment (element id) that is not a <view> is to display the nearest ancestor <svg> element that contains that element ("nearest ancestor" because an SVG can contain nested <svg> tags). So if your document has a natural structure to it, you could replace some <g> elements with <svg> with a specified x,y,height and width parameter, and then linking to an element within that sub-graphic would show that view. That should work even when the SVG is embedded within a larger HTML document. But if you've got hundreds of elements moving around, it's probably not a practical solution.
Which leaves #Ian's solution of programmatically manipulating the main SVG viewBox. If you don't want to zoom in, just pan, leave the width and height as the full size of your visualization, and just change the x and y offsets. Something like:
function centerViewOnElement( el ) {
var bbox = el.getBBox()
var elCenterX = bbox.x + bbox.width/2,
elCenterY = bbox.y + bbox.height/2;
svg.setAttribute("viewBox", [(elCenterX - width/2),
(elCenterY - height/2),
width,
height
].join(" ") );
//assuming you've got the svg, width and height already saved in variables...
}
Thought I would do a simpler example, as this feels quite useful in general...with a jsfiddle here
<svg id="mySvg">
<circle id="myCirc" cx="20" cy="20" r="20"/>
<rect id="myRect" x="50" y="50" width="50" height="50"/>
</svg>
var mySvg = document.getElementById("mySvg");
function getNewViewbox( el ) {
var bbox = el.getBBox();
return newViewbox = bbox.x + " " + bbox.y + " " + bbox.width + " " + bbox.height;
}
function focusElement( ev ) {
ev.stopPropagation();
mySvg.setAttribute("viewBox", getNewViewbox( ev.target ) );
}
//click on any element, or even the svg paper
document.getElementById("mySvg").addEventListener("click", focusElement);
I am trying to create a half filled circle with d3.js to be like this.
I didn't find any example of how to do it.
How can this be done with d3.js?
Yes, you can do that with an SVG gradient. All you have to do is define it and then use it as fill for the circle.
var grad = svg.append("defs").append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
svg.append("circle")
.attr("fill", "url(#grad)");
JSfiddle here.
You may not even require d3 for this simple task. You may use this simple technique, Using Clippath on a circle, I have written it in details in my blog http://anilmaharjan.com.np/blog/2013/11/create-filled-circle-to-visualize-data-using-svg
Use Two circles one above another in a tag.
Fill one with the color you wish and another with white or may be your background color just to make it look like its empty in there.
Then clip the later one using with rectangle in it, assign radius few pixel less than the earlier circle.
Place clip path at the top left .. assign width equal to the diameter of the circle and height will be defined by your data.
The data will act reversible to the filling so you may subtract the actual data from your max. EG: if data is 20/100 do 100-20 so u ll get 80 in this way the empty part will be 80 and filled will be 20.
You may switch between height or width to switch between vertical or horizontal filling axis.
The HTML should look like this.
<svg height="200"> <a transform="translate(100,100)">
<g>
<circle fill="#f60" r="50"></circle>
</g>
<g>
<clippath id="g-clip">
<rect height="50" id="g-clip-rect" width="100" x="-50" y="-50">
</rect>
</clippath>
<circle clip-path="url(#g-clip)" fill="#fff" r="47"></circle>
</g>
</a>
</svg>
I have created a jsfiddle to illustrate this at: http://jsfiddle.net/neqeT/2/
create a div having id name id_cirlce and paste this code inside script tag
<div id="id_circle"></div>
<script>
var svg = d3.select("#id_circle")
.append("svg")
.attr("width",250)
.attr("height",250);
var grad = svg.append("defs")
.append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", "50%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", "50%").style("stop-color", "white");
svg.append("circle")
.attr("r",50)
.attr("cx",60)
.attr("cy",60)
.style("stroke","black")
.style("fill","url(#grad)");
</script>