How to draw rectangle in SVG based on mouse move event - svg

i have created SVG chart. i want to perform zooming in that chart. for zooming i need to draw rectangle i.e selection marker to select area to zoom in chart. how can i draw a rectangle in mouse move event.
1.mouse down event triggered. (start position of the marker)
2.start dragging (mouse move event triggered) -> in that event need to draw the rectangle based on the mouse move
drop (mouse up event triggered)-> clear the rectangle
Please refer below below screenshot.
how can i draw rectangle based on mouse move ?
Thanks,
Siva

here is a possible solution:
At the end of your SVG (in this way it will be drawn on all elements) add a rect like this
<rect id="zoom_area" x=0 y=0 width=0 height=0 onmouseup="endDrag(evt)" style="fill: white; stroke:black; stroke-width:2px; opacity:0.5"/>
On your grid add the events onmouseup="endDrag(evt)" and onmousemove="moveMouse(evt)"
Now the javascript:
var zoom_box = {};
function startDrag(evt){
var offset = $("#bounds_grid").offset(); // Take the offset from the grid, change the ID as you need.
evt.stopPropagation();
zoom_box["start_x"] = evt.clientX-offset.left;
zoom_box["start_y"] = evt.clientY-offset.top;
zoom_box["boxing"] = true;
$('#zoom_area').attr("x",zoom_box["start_x"]);
$('#zoom_area').attr("y",zoom_box["start_y"]);
}
function endDrag(evt){
var offset = $("#bounds_grid").offset();
evt.stopPropagation();
zoom_box["end_x"] = evt.clientX-offset.left;
zoom_box["end_y"] = evt.clientY-offset.top;
zoom_box["boxing"] = false;
$('#zoom_area').attr("width",0);
$('#zoom_area').attr("height",0);
}
function moveMouse(evt){
var offset = $("#bounds_grid").offset();
evt.stopPropagation();
if(zoom_box["boxing"]){
zoom_box["end_x"] = evt.clientX-offset.left;
zoom_box["end_y"] = evt.clientY-offset.top;
$('#zoom_area').attr("width",(zoom_box["end_x"]-zoom_box["start_x"]));
$('#zoom_area').attr("height",(zoom_box["end_y"]-zoom_box["start_y"]));
}
}
Be carefull with the offset: in this way it take te offset from the margin of the document

Related

fabric.js: How to set stroke thickness of selection box and controls?

In fabric.js, how do you adjust stroke thickness for the object selection box and control handles?
There's a bunch of customization options available, however it isn't clear on how you can customized stroke thickness. Is it possible, perhaps through an indirect way?
Note: The properties selectionColor, selectionBorderColor, selectionLineWidth are misleading... they have to do with the temporary selection box that appears when attempting to do a fresh drag-select across the canvas. Once your selection is made, it disappears and then you see the persistent object selection box with control handles (the thing I'm trying to customize).
See links:
http://fabricjs.com/customization
http://fabricjs.com/controls-customization
Ok here's a 2-part solution:
https://codepen.io/MarsAndBack/pen/bGExXzd
For the selection box stroke thickness:
Use fabric.Object.prototype.set to customize any object selection globally. Also, borderScaleFactor is documented, but not included in the fabric.js customization demos:
fabric.Object.prototype.set({
borderScaleFactor: 6
})
For the control handle stroke thickness:
Here we override differently, and actually draw new elements using standard HTML5 canvas properties. Through this method you could also target specific control handles and even use image icons.
fabric.Object.prototype._drawControl = controlHandles
fabric.Object.prototype.cornerSize = 20
function controlHandles (control, ctx, methodName, left, top) {
if (!this.isControlVisible(control)) {
return
}
var size = this.cornerSize
// Note 1: These are standard HTML5 canvas properties, not fabric.js.
// Note 2: Order matters, for instance putting stroke() before strokeStyle may result in undesired effects.
ctx.beginPath()
ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = "pink"
ctx.fill()
ctx.lineWidth = 4 // This is the stroke thickness
ctx.strokeStyle = "red"
ctx.stroke()
}
SO code snippet:
const canvas = new fabric.Canvas("myCanvas")
canvas.backgroundColor="#222222"
this.box = new fabric.Rect ({
width: 240,
height: 100,
fill: '#fff28a',
myType: "box"
})
canvas.add(this.box)
this.box.center()
// Selection box border properties
// ----------------------------------------
fabric.Object.prototype.set({
borderColor: "white",
borderScaleFactor: 6
})
// Control handle properties
// ----------------------------------------
fabric.Object.prototype._drawControl = controlHandles
fabric.Object.prototype.cornerSize = 20
function controlHandles (control, ctx, methodName, left, top) {
if (!this.isControlVisible(control)) {
return
}
var size = this.cornerSize
// Note 1: These are standard HTML5 canvas properties, not fabric.js.
// Note 2: Order matters, for instance putting stroke() before strokeStyle may result in undesired effects.
ctx.beginPath()
ctx.arc(left + size/2, top + size/2, size/2, 0, 2 * Math.PI, false);
ctx.fillStyle = "pink"
ctx.fill()
ctx.lineWidth = 4
ctx.strokeStyle = "red"
ctx.stroke()
}
<script src="https://pagecdn.io/lib/fabric/3.6.3/fabric.min.js"></script>
<canvas id="myCanvas" width="700" height="400"></canvas>

Fading out two over-layed objects uniformly in RaphaelJS/SVG

I have two overlayed rectangles:
I'm trying to fade them out uniformly as if they were one object.
The problem:
When I animate their opacity from 1 to 0, the top rectangle becomes transparent and reveals the edges of the rectangle beneath it.
Here is my code:
var paper = Raphael(50,50,250,350)
var rect = paper.rect (20,40,200,200).attr({"fill":"red","stroke":"black"})
var rect2 = paper.rect (100,140,200,200).attr({"fill":"red","stroke":"black"})
var set=paper.set()
set.push(rect)
set.push(rect2)
set.click(function () {fadeOut()})
function fadeOut() {
rect.animate({"opacity":0},3000)
rect2.animate({"opacity":0},3000)
setTimeout(function () {
rect.attr({"opacity":1})
rect2.attr({"opacity":1})
},3100)
}
When the set is clicked, the rectangles fade out in 3 seconds. (look at the red rectangles in my fiddle, it will clarify my problem)
https://jsfiddle.net/apoL5rfp/1/
In my fiddle I also create a similar looking green path that performs the fade out CORRECTLY.
I can I achieve the same type of fadeout with multiple objects?
I think this is quite difficult in Raphael alone.
Few options spring to mind. Don't use Raphael, use something like Snap, put them in a group and change opacity in the group.
var g = paper.g(rect, rect2);
g.click(function () { fadeOut( this )} )
function fadeOut( el ) {
el.animate({"opacity":0},3000)
setTimeout(function () {
el.attr({"opacity":1})
},3100)
}
jsfiddle
However, you may be tied to Raphael, which makes things a bit tricky, as it doesn't support groups. You could place an 'blank' object over it (which matches same as background) and animate its opacity in the opposite way, like this..(note the disabling of clicks on top object in css)
var rectBlank = paper.rect(18,20,250,330).attr({ fill: 'white', stroke: "white", opacity: 0 });
var set=paper.set()
....
rectBlank.animate({"opacity":1},3000)
jsfiddle
Otherwise I think you may need to use a filter, which may help a bit. SO question

how to get text bounding box in famo.us

I am attempting to draw an SVG bezier curve that starts at the end of a text string that is in a Surface. I can set the size of the Surface to [true, true], which is supposed to make the size equal the text bounding box. But if I later try "mySurface.size[0]" in order to get the width, it returns "true"! I need to get a number for the width and height of that bounding box, in order to calculate the end point of my bezier curve! The equivalent DOM approach would just be to use the .getBBox() function.. how do I do this in Famo.us?
this is maybe because the surface hasn't rendered yet. there are a few similar questions here, one of them from me:
how can we get the size of a surface within famo.us?
you could also try deferring or using a setTimeout or Engine.nextTick() to check the size on the next loop through.
if you find an elegant solution let us know as this is a big problem in many places using famous - having to do multiple highjinks where you can't really position a scene on the initial setup - you have to let it render and then adjust...
You can use the 'getSize' function and specify 'true' to get the real size of the surface:
var realSize = surface.getSize(true);
#ljzerenHein, thanks for the hint.. unfortunately, surface.getSize(true) returns null!
#dcsan, thanks for the link. I believe you may be right, however the solution linked to ends up being much too involved for me.
After much searching, hacking, and experimenting, I've settled on the following approach:
-] use the DOM to get untransformed bounding boxes for text strings
-] format the text strings in SVG form
-] make it so the strings are invisible (set fill and stroke to none)
-] reuse the same "div" element for all the strings that I want to measure
-] once I have the untransformed bounding box, then set the famous surface size to that and then apply modifiers.
-] if I need the bounding box after all transforms have been applied, then get the total accumulated transforms for the surface and multiply that with the original untransformed bounding box
Here's the code to create the DOM element, insert SVG text, then get the bounding box:
//Do this part once, of creating a DOM element and adding it to the document
var el1 = document.createElement("div");
document.body.appendChild(el1); //only need to append once -- just set innerHTML after
//now set the SVG text string -- from this point down can be repeated for multiple
// strings without removing or re-adding the element, nor fiddling with the DOM
var text1_1_1_SVG = '<svg> <text x="0" y="0" style="font-family: Arial; font-size: 12;fill:none;stroke:none" id="svgText1">' + myFamousSurface.content + '</text> </svg>';
//note the id is inside the text element! Also the fill and stroke are null so nothing paints
el1.innerHTML = text1_1_1_SVG;
//now get the element -- this seems to be what triggers the bounding box calc
var test = document.getElementById("svgText1"); //this is ID in the text element
//get the box, take the values out of it, and display them
var rect = test.getBoundingClientRect();
var str = "";
for (i in rect) { //a trick for getting all the attributes of the object
str += i + " = " + rect[i] + " ";
}
console.log("svgText1: " + str);
FYI, all of the SVGTextElement methods seem to be callable upon gotElem.
SVGTextElement docs here: http://msdn.microsoft.com/en-us/library/ie/ff972126(v=vs.85).aspx
#seanhalle
I'm pretty sure .getSize(true) is returning null because the element has not yet been added to the DOM. Keep in mind that famo.us is synchronized with animation-frames, and updates to the DOM happen don't happen instantly. Accesssing the DOM directly (aka pinging) is strongly disadviced because you will loose the performance benefits that famo.us promises.
What I would do is create a custom view to wrap your surface inside and implement a render-method in it. In the render-method, use getSize(true) to get the size. If it returns null,
you know it has not yet been committed to the DOM.
view in action as jsfiddle
define('MyView', function (require, exports, module) {
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
function MyView() {
View.apply(this, arguments);
this.surface = new Surface();
this.add(this.surface);
}
MyView.prototype = Object.create(View.prototype);
MyView.prototype.constructor = MyView;
MyView.prototype.render = function() {
var size = this.getSize(true);
if (size) {
if (!this.hasSize) {
this.hasSize = true;
console.log('now we have a size: ' + size);
}
this.surface.setContent('Size: ' + JSON.stringify(size));
} else {
console.log('no size yet');
}
return this._node.render();
};
module.exports = MyView;
});

Dragging a circle

I am beginning to use snap.svg, and stuck in a probably simple question. I draw a circle, and want to let the user drag it around, and when the user stops dragging, I want to know the alert the final position of the circle.
I started with this:
var s = Snap("#svg");
var d = s.circle(20,20,5);
Then I created the following functions as drag event-handlers; I only need the drag-end event so I made the two other event-handlers null:
var endFnc = function(e) {
alert("end: "+e.layerX+","+e.layerY);
}
var startFnc = null;
var moveFnc = null;
Then I installed the drag event handlers:
d.drag(moveFnc,startFnc,endFnc);
Whenever I tried to drag a circle, the end event fired, but, the circle did not move.
So I figured maybe I have to also create the drag move event (although I don't need it):
var moveFnc = function(dx, dy, x, y) {
this.transform('t' + x + ',' + y);
}
Now the circle did move, but, not exactly at the mouse, but approximately 20 pixels at its bottom right.
I read the documentation here: http://snapsvg.io/docs and there seems to be no explanation about how to create working drag events.
How does anyone get this knowledge?!
After struggling for some hours to do this with snap.js, I finally discovered svg.js and its draggable plugin, with which it is so much easier:
var draw = SVG('svg');
var circle = draw.circle(10).attr({cx:30,cy:30,fill:'#f06'});
circle.dragend = function(delta, event) {
alert(this.attr('cx'))
}
circle.draggable();
So, I switched to svg.js ...

Raphaël-ZPD click to zoom in/out

Does anyone know how to trigger zoom in/out with Raphaël-ZPD by clicking on a button?
If take a look to the code of Raphael-ZPD you would see that all the zooming and panning are made by using svg matrices. In the particular case of zooming, it first draw a reference point where the mouse is, and calculates the zoom direction relative to that point, using the delta of the mouse wheel to apply the amount of zoom.
Now, if you want to use a button, you would have to decide the delta on your code, and you could maybe use the center of the screen as the relative point to calculate the direction of zooming.
For starters you could just use the svg scale property, something like this could be ok if you are using Raphael ZPD:
paper = Raphael('container',600, 600);
paper.ZPD({ zoom: true, pan: true, drag: false });
scale = 1;
var zoomin = document.getElementById('in')
var zoomout = document.getElementById('out')
zoomin.onclick = function(){
scale *= 1.2
paper.canvas.setAttribute("transform", "scale("+scale+")")
}
zoomout.onclick = function(){
scale *= 0.8
paper.canvas.setAttribute("transform", "scale("+scale+")")
}
Here is Fiddle with a working example
I reccomend to add some translate calculating the center of the screen, so the zoom doesn't go to corner. But think all this could point you in the right direction.

Resources