I want to draw SVG path with mouse on canvas.
I don't want any library like rapheal.js here to draw shapes, I want pure JS.
I have creaed JS:
var svgCanvas = document.getElementById("svgCanvas");
var svgPath;
svgCanvas.addEventListener("touchstart", startDrawTouch, false);
svgCanvas.addEventListener("touchmove", continueDrawTouch, false);
svgCanvas.addEventListener("touchend", endDrawTouch, false);
function startDrawTouch(event)
{
var touch = event.changedTouches[0];
svgPath = createSvgElement("path");
svgPath.setAttribute("fill", "none");
svgPath.setAttribute("shape-rendering", "geometricPrecision");
svgPath.setAttribute("stroke-linejoin", "round");
svgPath.setAttribute("stroke", "#000000");
svgPath.setAttribute("d", "M" + touch.clientX + "," + touch.clientY);
svgCanvas.appendChild(svgPath);
}
function continueDrawTouch(event)
{
if (svgPath)
{
var touch = event.changedTouches[0];
var pathData = svgPath.getAttribute("d");
pathData = pathData + " L" + touch.clientX + "," + touch.clientY
svgPath.setAttribute("d", pathData);
}
}
function endDrawTouch(event)
{
if (svgPath)
{
var pathData = svgPath.getAttribute("d");
var touch = event.changedTouches[0];
pathData = pathData + " L" + touch.clientX + "," + touch.clientY
svgPath.setAttribute("d", pathData);
svgPath = null;
}
}
function createSvgElement(tagName)
{
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
This take time on tablet to draw path. Having performance issue, in case you have better idea please share.
Thanks in advance.
You are reconstructing the path element in each continueDrawTouch call. That means converting it from the internal representation to a string then appending to the string and converting it back again.
Most browsers (Firefox for certain for instance) will be more performant if you avoid this and use the SVG DOM instead. The code would become:
if (svgPath)
{
var touch = event.changedTouches[0];
var newSegment = svgPath.createSVGPathSegLinetoAbs(touch.clientX, touch.clientY);
svgPath.pathSegList.appendItem(newSegment);
}
The same comment applies to the endDrawTouch function.
Maybe you can try if <polyline> and its .points property work and can give you better performance. Untested modification of your code:
var svgCanvas = document.getElementById("svgCanvas");
var svgPolyline;
svgCanvas.addEventListener("touchstart", startDrawTouch, false);
svgCanvas.addEventListener("touchmove", continueDrawTouch, false);
svgCanvas.addEventListener("touchend", endDrawTouch, false);
function startDrawTouch(event)
{
var touch = event.changedTouches[0];
svgPolyline = createSvgElement("polyline");
svgPolyline.setAttribute("fill", "none");
svgPolyline.setAttribute("shape-rendering", "geometricPrecision");
svgPolyline.setAttribute("stroke-linejoin", "round");
svgPolyline.setAttribute("stroke", "#000000");
svgCanvas.appendChild(svgPolyline);
continueDrawTouch(event);
}
function continueDrawTouch(event)
{
if (svgPolyline)
{
var touch = event.changedTouches[0];
var point = svgPolyline.ownerSVGElement.createSVGPoint();
point.x = touch.clientX;
point.y = touch.clientY;
var ctm = event.target.getScreenCTM();
if (ctm = ctm.inverse())
{
point = point.matrixTransform(ctm);
}
svgPolyline.points.appendItem(point);
}
}
function endDrawTouch(event)
{
continueDrawTouch(event);
svgPolyline = null;
}
function createSvgElement(tagName)
{
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
Edit: .clientX/Y doesn't necessarily give you the coordinates you want, depending on the structure of your document, scroll or transformations. I therefore edited the code with some inspiration from another question (but using .screenX/Y, which should be more appropriate in connection with .getScreenCTM). The method name .getScreenCTM() caused me some confusion. .clientX/Y is indeed what's needed, see the specs.
Related
Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.
I have been able to draw a directed graph using dagre. However, now I would like to delete a node/edge on clicking it. I can use g.delEdge and g.delNode for deleting but how do I get to know that someone has clicked on the node? Please see my javascript dagre code below and advise me what I need to add to it.(I am reading the required nodes and edges from a json file, parsing the data and plotting them)
function MyFunc()
{
var buffer = JSON.parse(data);
var nodesarray = new Array();
for(var i=0;i<Object.keys(buffer[0].nodes).length;i++)
{
nodesarray.push(buffer[0].nodes[i].name);
}
// Create the input graph
var g = new dagreD3.Digraph();
for(i=0;i<nodesarray.length;i++)
{
//To give styles to nodes
//g.addNode(0, { label: 'Female', labelStyle: 'font-weight: bold;', style: 'stroke: #f66; stroke-width: 10px;', nodeclass: 'type-TOP' });
g.addNode(nodesarray[i],{label:nodesarray[i]});
}
for(var i=0;i<Object.keys(buffer[0].edges).length;i++)
{
var source = buffer[0].edges[i].source;
var destination = buffer[0].edges[i].destination;
var weight = buffer[0].edges[i].weight;
var strokewidth = weight*10;
var mystyle='stroke:#f66; stroke-width:';
mystyle = mystyle + strokewidth + 'px';
//To give styles to edges
//g.addEdge(null, 5, 7, { style: 'stroke: #f66; stroke-width: 3px;',label: "Label for the edge" });
g.addEdge(i,source,destination,{style: mystyle,label:weight});
}
/* Deleting a node/edge example */
//g.delNode(nodesarray[0]);
//g.delNode(nodesarray[1]);
//g.delEdge(0);
// Create the renderer
var renderer = new dagreD3.Renderer();
var l = dagreD3.layout()
.nodeSep(100)
.rankSep(200)
.edgeSep(80)
.rankDir("LR");
renderer.layout(l);
// Override drawNodes to add nodeclass as a class to each node in the output
// graph.
var oldDrawNodes = renderer.drawNodes();
renderer.drawNodes(function(graph, root) {
var svgNodes = oldDrawNodes(graph, root);
svgNodes.each(function(u) { d3.select(this).classed(graph.node(u).nodeclass, true); });
return svgNodes;
});
// Disable pan and zoom
renderer.zoom(false);
//renderer.edgeInterpolate('linear');
// Set up an SVG group so that we can translate the final graph.
var svg = d3.select('svg'),
svgGroup = svg.append('g');
// Run the renderer. This is what draws the final graph.
var layout = renderer.run(g, d3.select('svg g'));
// Center the graph
var xCenterOffset = (svg.attr('width') - layout.graph().width) / 2;
svgGroup.attr('transform', 'translate(' + xCenterOffset + ', 80)');
svg.attr('height', layout.graph().height + 200);
}
I see that you've overridden functionality to add class selector, you can add click event handler similarly, see the following example.
// Override drawNodes to set up the click.
var oldDrawNodes = renderer.drawNodes();
renderer.drawNodes(function(g, svg) {
var svgNodes = oldDrawNodes(g, svg);
// on click event handler
svgNodes.on('click', function(d) { console.log('Clicked on node - ' + d); });
return svgNodes;
});
for adding click event handler for edges, try something like below given code
var oldDrawEdges = renderer.drawEdgePaths();
renderer.drawEdgePaths(function(g, svg) {
var edges = oldDrawEdges(g, svg);
edges.on('click', function (edgeId) { alert('Clicked on - ' + edgeId);});
return edges;
});
This worked for me for handling node clicks:
svg.selectAll("g.node").on("click", function(id) {
console.log("Clicked " + id);
});
I used the following code to change the stroke-width when mouseover the path, but it doesn't work... I have checked many solutions on this matter, they seem to use the same solution as mine. My canvas is Raphael("svgContainer", 100, 100);
function drawPath(i,floorlevel,pointsNum){
var x1 = floorlevel[i].x;
var y1 = floorlevel[i].y;
var x2 = floorlevel[i+1].x;
var y2 = floorlevel[i+1].y;
var p = canvas.path("M"+x1 +" "+ y1);
p.attr("stroke", get_random_color());
p.attr("stroke-width",4);
p.attr("id",floorlevel[i].node+floorlevel[i+1].node);
p.animate({path:"M"+x1 +" "+ y1+ " L" + x2 +" "+ y2}, 1000);
var set = canvas.set();
var hoverIn = function() {
this.attr({"stroke-width": 10});
};
var hoverOut = function() {
this.attr({"stroke-width": 10});
}
p.hover(hoverIn, hoverOut, p, p);
set.push(p);
}
It seems to work fine when I sub in dummy values for the arguments you pass to the function:
http://jsfiddle.net/hKCDg/
I noticed you have the same stroke-width for hoverIn and hoverOut, which defeats the purpose.
var hoverIn = function() {
this.attr({"stroke-width": 10});
};
var hoverOut = function() {
this.attr({"stroke-width": 10});
};
I changed the latter to 5 in the demo here for visual effect.
Perhaps there's an error in the values you pass to the function?
I want to move a specific node (say i have the node reference), to a desired location (say to the top left corner of the canvas). I tried doing something similar to one in the sample project, where they move nodes while dragging with the mouse. But it doesn't seem to work. I am not seeing the node move as i expected. This is the code i have.
$("#someElement").click(function() {
sys.eachNode(function(node, pt) {
if (node.name === "specificNode") {
// moveToOrigin
var s = arbor.Point(1, 1);
var p = sys.fromScreen(s);
node.fixed = true;
node.p = p;
node.fixed = false;
node.tempMass = 1000;
}
}
});
To move a node to a desired position get the desired position relative to the canvas and set it via particle system's fromScreen(...) function:
var point = point;
var pos = canvas.offset();
var s = arbor.Point(point.x-pos.left, point.x-pos.top);
node.p = particleSystem.fromScreen(s);
You need to set position of node first, then again iterate each loop for nodes.
redraw: function () {
gfx.clear()
particleSystem.eachNode(function (node, pt) {
//var node = particleSystem.getNode("Carrol Wahi")
if (node.data.color == "yellow") {
var pos = $(canvas).offset();
var point = particleSystem.fromScreen(arbor.Point(pos.left + 150, pos.top));
node._fixed = true;
node._p = point;
//console.log("x=" + point.x + ", y=" + point.y);
node.tempMass = .1
}
});
particleSystem.eachEdge(function (edge, pt1, pt2) {
// your code goes here
}
particleSystem.eachNode(function (node, pt) {
//your code goes here
})
}
I am creating a diagramming tool using RaphaelJS and have run into a problem, i cannot see how i can edit the shapes that have been painted onto the canvas paper. For example, below is the code i use to create a UML Class shape and would now like to know how to modify the elements contained within, Im using MooTools BTW:
var uml_Class = new Class(
{
initialize: function(name)
{
this.className = name;
this.pointA_X = 1; this.pointA_Y = 1;
this.pointB_X = 150; this.pointB_Y = 1;
this.pointC_X = 1; this.pointC_Y = 40;
this.pointD_X = 150; this.pointD_Y = 40;
this.pointE_X = 1; this.pointE_Y = 100;
this.pointF_X = 150; this.pointF_Y = 100;
this.pointG_X = 1; this.pointG_Y = 160;
this.pointH_X = 150; this.pointH_Y = 160;
this.generate_Shape();
},
generate_Shape: function()
{
this.classSet = paper.set();
this.classSet.push
(
this.shapeBase = paper.rect(this.pointA_X,this.pointA_Y,this.pointH_X,this.pointH_Y).attr({"fill":"white"}),
this.line_Attrib = paper.path("M " + this.pointC_X + " " + this.pointC_Y + " L " + this.pointD_X + " " + this.pointD_Y),
this.line_Method = paper.path("M " + this.pointE_X + " " + this.pointE_Y + " L " + this.pointF_X + " " + this.pointF_Y),
this.classText = paper.text(this.pointB_X/2, this.pointA_Y+20, this.className).attr({"font-size":"14"}),
this.attribText = paper.text(this.pointD_X/2, this.pointC_Y+10, "Attributes").attr({"font-size":"10"}),
this.methodText = paper.text(this.pointF_X/2, this.pointE_Y+10, "Methods").attr({"font-size":"10"})
);
this.shapeBase.draggable.enable();
},
add_new_Attrib: function()
{
},
add_new_Attrib: function()
{
}
});
The above code works fine and on my canvas classes are created which show there name and are constructed using the rectangle for the base and two line to create the three sections:
name area
attrib area
method area
By making the shapeBase rectangle variable draggable i means that the user can click anywhere within this shape to drag, again this functionality works fine.
i would now like to code the two functions add_new_Attrib and add_new_Method. The attrib function should first resize or grow the cube by adding 20 to the overall height (via point_H_X) to make space for a new attrib entry and then move the method line (line_Method) and text (method_Text) down by 20.
the add_new_method line should also grow the shapeBase rectangle by 20 to make room for the new method entry.
I cant seem to find a way to do this, for example, when i put the following code into the add_new_Attrib shape, i am trying to redraw the shapeBase but instead it draws an entirely new rectangle:
add_new_Attrib: function()
{
this.shapeBase = paper.rect(this.pointA_X,this.pointA_Y,this.pointH_X,this.pointH_Y+20).attr({"fill":"white"});
},
Could anyone tell me how to resize or reposition the rectangle and paths that are inside my class?
Thanks for any input your may have!
RaphaelJS's getBBox and attr methods is what you are looking for:
add_new_Attrib: function()
{
var bbox = this.shapeBase.getBBox();
this.shapeBase.attr({'height': bbox.height + 20, "fill":"white"})
}
To reposition, look at translate (can't link, but it is in the same doc as above).