Raphael SVG move object along straight horizontal line - svg

I want to move a green button along a horizontal line path but the green button is not following my mouse. How to make green button follow my mouse when dragging it?
code :
<script>
var pdefs = {
horizontalLine: {
path: [
['M',50,240],
['l',640,0]
],
transform: 'r0'
}
},
useDef = 'wiggles';
function run()
{
var paper = Raphael( $('.wrapper')[0], 600, 600 ),
path = paper.path( Raphael.transformPath(pdefs['horizontalLine'].path, pdefs['horizontalLine'].transform) )
.attr( 'stroke-width', 10 )
.attr( 'stroke', 'rgb(80,80,80)' ),
knob = paper.ellipse( 0, 0, 25, 15 )
.attr( 'fill', 'lime' )
.attr( 'stroke', 'rgba(80,80,80,0.5)' ),
$shim = $('<div>')
.css( {position: 'absolute', width: 50, height: 50 } )
.appendTo( $('.wrapper') ),
len = path.getTotalLength(),
bb = path.getBBox(),
mid = {x: bb.x+bb.width/2, y: bb.y+bb.height/2},
pal = path.getPointAtLength(0);
knob.translate(pal.x,pal.y).rotate(pal.alpha);
$shim.css({ left: pal.x-5, top: pal.y-5 });
$shim.draggable({
drag: function ( e, ui ) {
// Find lines and then angle to determine
// percentage around an imaginary circle.
var t = ( Raphael.angle( ui.position.left-25, ui.position.top-25, mid.x, mid.y ) ) /360;
// Using t, find a point along the path
pal = path.getPointAtLength( (t * len) % len );
// Move the knob to the new point
knob.transform( 't' + [pal.x, pal.y] + 'r' + pal.alpha );
},
stop: function ( e, ui ) {
$shim.css({ left: pal.x-25, top: pal.y-25 });
}
});
}
run();
</script>
Demo : https://jsfiddle.net/zac1987/zea53w7f/

Your drag function looks like a leftover from some different widget (a circular knob perhaps?).
Instead of:
drag: function ( e, ui ) {
// Find lines and then angle to determine
// percentage around an imaginary circle.
var t = ( Raphael.angle( ui.position.left-25, ui.position.top-25, mid.x, mid.y ) ) /360;
// Using t, find a point along the path
pal = path.getPointAtLength( (t * len) % len );
// Move the knob to the new point
knob.transform( 't' + [pal.x, pal.y] + 'r' + pal.alpha );
}
Try:
drag: function ( e, ui ) {
var t = ui.position.left - 50;
// Using t, find a point along the path
pal = path.getPointAtLength( Math.max(t, 0) );
// Move the knob to the new point
knob.transform( 't' + [pal.x, pal.y] + 'r' + pal.alpha );
}
https://jsfiddle.net/1Lzqhm9o/2/

Related

Phaser Scrollable Text Box Tutorial not working on mobile

I recreated the scrolling text box tutorial in my game. However, it is running a bit glitchy on mobile. For example, if I swipe up, the text first goes down for a second and then follows my finger up. You’ll see the problem if you open the tutorial on mobile. Any thoughts? I copied my code below.
var graphics = scene.make.graphics();
graphics.fillRect(x, y + 10, width, height - 20);
var mask = new Phaser.Display.Masks.GeometryMask(scene, graphics);
var text = scene.add.text(x + 20, y + 20, content, {
fontFamily: 'Assistant',
fontSize: '28px',
color: '#000000',
wordWrap: { width: width - 20 }
}).setOrigin(0);
text.setMask(mask);
var minY = height - text.height - 20;
if (text.height <= height - 20) {
minY = y + 20;
}
// The rectangle they can 'drag' within
var zone = scene.add.zone(x, y - 3, width, height + 6).setOrigin(0).setInteractive({useHandCursor: true});
zone.on('pointermove', function (pointer) {
if (pointer.isDown) {
text.y += (pointer.velocity.y / 10);
text.y = Phaser.Math.Clamp(text.y, minY, y + 20);
}
});
I had the same issue. My solution is using "pointer.y" instead of "pointer.velocity.y".
Here is my code:
var previousPointerPositionY;
var currentPointerPositionY;
zone.on('pointerdown', function (pointer) {
previousPointerPositionY = pointer.y;
});
zone.on('pointermove', function (pointer) {
if (pointer.isDown) {
currentPointerPositionY = pointer.y;
if(currentPointerPositionY > previousPointerPositionY){
text.y += 5;
} else if(currentPointerPositionY < previousPointerPositionY){
text.y -= 5;
}
previousPointerPositionY = currentPointerPositionY;
text.y = Phaser.Math.Clamp(text.y, -360, 150);
}
});

how to set color with leaflet glify

I'm using the glify plugin for Leaflet, and can't for the life of me figure out how to set the color of my points to a function.
This works fine:
L.glify.points({
data: data,
map: map,
opacity: 1,
size: 10,
color: 'red',
However this returns all black points:
L.glify.points({
data: data,
map: map,
opacity: 1,
size: 10,
color: function(){
if ( 1 > 0 ){ return 'red';}else{return 'blue';}
},
Does anyone have any idea what I need to do here?
figured it out, needs to be formatted like this:
color: function(){
if (1 > 0){
return {
r: 0,
g: .51,
b: .1
};
}else{
return {
r: 30,
g: 1,
b: 2
};
}
},
You need to convert your hex color code into rgb form using below function
function fromHex(hex) {
if (hex.length < 6) return null;
hex = hex.toLowerCase();
if (hex[0] === '#') {
hex = hex.substring(1, hex.length);
}
var r = parseInt(hex[0] + hex[1], 16),
g = parseInt(hex[2] + hex[3], 16),
b = parseInt(hex[4] + hex[5], 16);
return {
r: r / 255,
g: g / 255,
b: b / 255
};
}
and then return it like this :
L.glify.points({
data: data,
map: map,
opacity: 1,
size: 10,
color: function(){
if ( 1 > 0 ){
return fromHex("#FF0000");
}else{
return fromHex("#0000FF");
}
}
});

fabricJS Not persisting Floodfill

I have an algorithm for Floodfilling a canvas. Im trying to incorporate this with fabricJS. So here is the dilemna.... I create a fabric.Canvas(). Which creates a wrapper canvas and also an upper-canvas canvas. I click on the canvas to apply my Floodfill(). This works fine and applies my color. But as soon as i go to drag my canvas objects around, or add additional objects to the canvas, the color disappears and looks like it resets of sort.
Any idea why this is?
This happen because fabricjs wipe out all canvas every frame and redraw from its internal data.
I made a JSfiddle that implements Flood Fill for Fabric JS. Check it here: https://jsfiddle.net/av01d/dfvp9j2u/
/*
* FloodFill for fabric.js
* #author Arjan Haverkamp (av01d)
* #date October 2018
*/
var FloodFill = {
// Compare subsection of array1's values to array2's values, with an optional tolerance
withinTolerance: function(array1, offset, array2, tolerance)
{
var length = array2.length,
start = offset + length;
tolerance = tolerance || 0;
// Iterate (in reverse) the items being compared in each array, checking their values are
// within tolerance of each other
while(start-- && length--) {
if(Math.abs(array1[start] - array2[length]) > tolerance) {
return false;
}
}
return true;
},
// The actual flood fill implementation
fill: function(imageData, getPointOffsetFn, point, color, target, tolerance, width, height)
{
var directions = [[1, 0], [0, 1], [0, -1], [-1, 0]],
coords = [],
points = [point],
seen = {},
key,
x,
y,
offset,
i,
x2,
y2,
minX = -1,
maxX = -1,
minY = -1,
maxY = -1;
// Keep going while we have points to walk
while (!!(point = points.pop())) {
x = point.x;
y = point.y;
offset = getPointOffsetFn(x, y);
// Move to next point if this pixel isn't within tolerance of the color being filled
if (!FloodFill.withinTolerance(imageData, offset, target, tolerance)) {
continue;
}
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
if (x < minX || minX == -1) { minX = x; }
if (y < minY || minY == -1) { minY = y; }
// Update the pixel to the fill color and add neighbours onto stack to traverse
// the fill area
i = directions.length;
while (i--) {
// Use the same loop for setting RGBA as for checking the neighbouring pixels
if (i < 4) {
imageData[offset + i] = color[i];
coords[offset+i] = color[i];
}
// Get the new coordinate by adjusting x and y based on current step
x2 = x + directions[i][0];
y2 = y + directions[i][1];
key = x2 + ',' + y2;
// If new coordinate is out of bounds, or we've already added it, then skip to
// trying the next neighbour without adding this one
if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height || seen[key]) {
continue;
}
// Push neighbour onto points array to be processed, and tag as seen
points.push({ x: x2, y: y2 });
seen[key] = true;
}
}
return {
x: minX,
y: minY,
width: maxX-minX,
height: maxY-minY,
coords: coords
}
}
}; // End FloodFill
var fcanvas; // Fabric Canvas
var fillColor = '#f00';
var fillTolerance = 2;
function hexToRgb(hex, opacity) {
opacity = Math.round(opacity * 255) || 255;
hex = hex.replace('#', '');
var rgb = [], re = new RegExp('(.{' + hex.length/3 + '})', 'g');
hex.match(re).map(function(l) {
rgb.push(parseInt(hex.length % 2 ? l+l : l, 16));
});
return rgb.concat(opacity);
}
function floodFill(enable) {
if (!enable) {
fcanvas.off('mouse:down');
fcanvas.selection = true;
fcanvas.forEachObject(function(object){
object.selectable = true;
});
return;
}
fcanvas.deactivateAll().renderAll(); // Hide object handles!
fcanvas.selection = false;
fcanvas.forEachObject(function(object){
object.selectable = false;
});
fcanvas.on({
'mouse:down': function(e) {
var mouse = fcanvas.getPointer(e.e),
mouseX = Math.round(mouse.x), mouseY = Math.round(mouse.y),
canvas = fcanvas.lowerCanvasEl,
context = canvas.getContext('2d'),
parsedColor = hexToRgb(fillColor),
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
getPointOffset = function(x,y) {
return 4 * (y * imageData.width + x)
},
targetOffset = getPointOffset(mouseX, mouseY),
target = imageData.data.slice(targetOffset, targetOffset + 4);
if (FloodFill.withinTolerance(target, 0, parsedColor, fillTolerance)) {
// Trying to fill something which is (essentially) the fill color
console.log('Ignore... same color')
return;
}
// Perform flood fill
var data = FloodFill.fill(
imageData.data,
getPointOffset,
{ x: mouseX, y: mouseY },
parsedColor,
target,
fillTolerance,
imageData.width,
imageData.height
);
if (0 == data.width || 0 == data.height) {
return;
}
var tmpCanvas = document.createElement('canvas'), tmpCtx = tmpCanvas.getContext('2d');
tmpCanvas.width = canvas.width;
tmpCanvas.height = canvas.height;
var palette = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height); // x, y, w, h
palette.data.set(new Uint8ClampedArray(data.coords)); // Assuming values 0..255, RGBA
tmpCtx.putImageData(palette, 0, 0); // Repost the data.
var imgData = tmpCtx.getImageData(data.x, data.y, data.width, data.height); // Get cropped image
tmpCanvas.width = data.width;
tmpCanvas.height = data.height;
tmpCtx.putImageData(imgData,0,0);
fcanvas.add(new fabric.Image(tmpCanvas, {
left: data.x,
top: data.y,
selectable: false
}))
}
});
}
$(function() {
// Init Fabric Canvas:
fcanvas = new fabric.Canvas('c', {
backgroundColor:'#fff',
enableRetinaScaling: false
});
// Add some demo-shapes:
fcanvas.add(new fabric.Circle({
radius: 80,
fill: false,
left: 100,
top: 100,
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Triangle({
width: 120,
height: 160,
left: 50,
top: 50,
stroke: '#000',
fill: '#00f',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 120,
height: 160,
left: 150,
top: 50,
fill: 'red',
stroke: '#000',
strokeWidth: 2
}));
fcanvas.add(new fabric.Rect({
width: 200,
height: 120,
left: 200,
top: 120,
fill: 'green',
stroke: '#000',
strokeWidth: 2
}));
/* Images work very well too. Make sure they're CORS
enabled though! */
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
fcanvas.add(new fabric.Image(img, {
left: 300,
top: 100,
angle: 30,
}));
}
img.src = 'http://misc.avoid.org/chip.png';
});

Understanding state diagram editor

From the blog: http://bl.ocks.org/lgersman/5370827
I want to understand about how connection line between circles are implemented. I tried to go through it but it just went over my head. There's not much documentation about the example I found on blog. I guess other new users like me would be facing the same challenge.
If any one can explain the below sample code, that would be great!
Here's the code I minified for my requirement:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>d3.js selection frame example</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>
<link rel="stylesheet" href="<%=request.getContextPath()%>/app.css" />
<script>
window.onload = function ()
{
var radius = 40;
window.states = [
{x: 43, y: 67, label: "first", transitions: []},
{x: 340, y: 150, label: "second", transitions: []},
{x: 200, y: 250, label: "third", transitions: []}
];
window.svg = d3.select('body')
.append("svg")
.attr("width", "960px")
.attr("height", "500px");
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('class', 'end-arrow')
;
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr({
'class': 'dragline hidden',
'd': 'M0,0L0,0'
})
;
**// NEED EXPLANATION FROM HERE**
var gTransitions = svg.append('g').selectAll("path.transition");
var gStates = svg.append("g").selectAll("g.state");
var transitions = function () {
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
var transformTransitionEndpoints = function (d, i) {
var endPoints = d.endPoints();
var point = [
d.type == 'start' ? endPoints[0].x : endPoints[1].x,
d.type == 'start' ? endPoints[0].y : endPoints[1].y
];
return "translate(" + point + ")";
}
var transformTransitionPoints = function (d, i) {
return "translate(" + [d.x, d.y] + ")";
}
var computeTransitionPath = (function () {
var line = d3.svg.line()
.x(function (d, i) {
return d.x;
})
.y(function (d, i) {
return d.y;
})
.interpolate("cardinal");
return function (d) {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = radius + 4, //d.left ? 17 : 12,
sourceX = source.x + (sourcePadding * normX),
sourceY = source.y + (sourcePadding * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (targetPadding * normX);
targetY = target.y - (targetPadding * normY);
var points =
[{x: sourceX, y: sourceY}].concat(
d.transition.points,
[{x: targetX, y: targetY}]
)
;
var l = line(points);
return l;
};
})();
var dragPoint = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("transitionmidpoint drag");
var gTransitionPoint = d3.select(this);
gTransitionPoint.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
var renderTransitionMidPoints = function (gTransition) {
gTransition.each(function (transition) {
var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
return transition.transition.points.indexOf(d);
});
transitionPoints.enter().append("circle")
.attr({
'class': 'point',
r: 4,
transform: transformTransitionPoints
})
.call(dragPoint);
transitionPoints.exit().remove();
});
};
var renderTransitionPoints = function (gTransition) {
gTransition.each(function (d) {
var endPoints = function () {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourceX = source.x + (radius * normX),
sourceY = source.y + (radius * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (radius * normX);
targetY = target.y - (radius * normY);
return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
};
var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
{endPoints: endPoints, type: 'start'},
{endPoints: endPoints, type: 'end'}
]);
transitionEndpoints.enter().append("circle")
.attr({
'class': function (d) {
return 'endpoint ' + d.type;
},
r: 4,
transform: transformTransitionEndpoints
})
;
transitionEndpoints.exit().remove();
});
};
var renderTransitions = function () {
gTransition = gTransitions.enter().append('g')
.attr({
'class': 'transition'
})
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'background'
})
.on({
dblclick: function (d, i) {
gTransition = d3.select(d3.event.target.parentElement);
if (d3.event.ctrlKey) {
var p = d3.mouse(this);
gTransition.classed('selected', true);
d.transition.points.push({x: p[0], y: p[1]});
renderTransitionMidPoints(gTransition, d);
gTransition.selectAll('path').attr({
d: computeTransitionPath
});
} else {
var gTransition = d3.select(d3.event.target.parentElement),
transition = gTransition.datum(),
index = transition.source.transitions.indexOf(transition.transition);
transition.source.transitions.splice(index, 1)
gTransition.remove();
d3.event.stopPropagation();
}
}
});
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'foreground'
});
renderTransitionPoints(gTransition);
renderTransitionMidPoints(gTransition);
gTransitions.exit().remove();
};
var renderStates = function () {
var gState = gStates.enter()
.append("g")
.attr({
"transform": function (d) {
return "translate(" + [d.x, d.y] + ")";
},
'class': 'state'
})
.call(drag);
gState.append("circle")
.attr({
r: radius + 4,
class: 'outer'
})
.on({
mousedown: function (d) {
console.log("state circle outer mousedown");
startState = d, endState = undefined;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
// force element to be an top
this.parentNode.parentNode.appendChild(this.parentNode);
//d3.event.stopPropagation();
},
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
//$( this).popover( "hide");
}
});
gState.append("circle")
.attr({
r: radius,
class: 'inner'
})
.on({
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
},
});
};
var startState, endState;
var drag = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("drag");
if (startState) {
return;
}
var selection = d3.selectAll('.selected');
// if dragged state is not in current selection
// mark it selected and deselect all others
if (selection[0].indexOf(this) == -1) {
selection.classed("selected", false);
selection = d3.select(this);
selection.classed("selected", true);
}
// move states
selection.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// move transistion points of each transition
// where transition target is also in selection
var selectedStates = d3.selectAll('g.state.selected').data();
var affectedTransitions = selectedStates.reduce(function (array, state) {
return array.concat(state.transitions);
}, [])
.filter(function (transition) {
return selectedStates.indexOf(transition.target) != -1;
});
affectedTransitions.forEach(function (transition) {
for (var i = transition.points.length - 1; i >= 0; i--) {
var point = transition.points[i];
point.x += d3.event.dx;
point.y += d3.event.dy;
}
});
// reappend dragged element as last
// so that its stays on top
selection.each(function () {
this.parentNode.appendChild(this);
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
})
.on("dragend", function (d) {
console.log("dragend");
// needed by FF
drag_line.classed('hidden', true)
.style('marker-end', '');
if (startState && endState) {
startState.transitions.push({label: "transition label 1", points: [], target: endState});
update();
}
startState = undefined;
d3.event.sourceEvent.stopPropagation();
});
svg.on({
mousedown: function () {
console.log("mousedown", d3.event.target);
if (d3.event.target.tagName == 'svg') {
if (!d3.event.ctrlKey) {
d3.selectAll('g.selected').classed("selected", false);
}
var p = d3.mouse(this);
}
},
mousemove: function () {
var p = d3.mouse(this);
// update drag line
drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
var state = d3.select('g.state .inner.hover');
endState = (!state.empty() && state.data()[0]) || undefined;
},
mouseup: function () {
console.log("mouseup");
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
},
mouseout: function ()
{
if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
}
}
});
update();
function update() {
gStates = gStates.data(states, function (d) {
return states.indexOf(d);
});
renderStates();
var _transitions = transitions();
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
renderTransitions();
}
;
};
</script>
</head>
<body>
</body>
</html>
Css file:
rect.selection {
stroke : gray;
stroke-dasharray: 2px;
stroke-opacity : 0.5;
fill : transparent;
}
g.state circle {
stroke : gray;
}
g.state circle.inner {
fill : white;
transition : fill 0.5s;
cursor : move;
}
g.state circle.inner.hover,
g.state circle.outer.hover {
fill : aliceblue;
}
g.state circle.outer.hover {
stroke-width : 1px;
}
g.state circle.outer {
stroke-width : 0px;
stroke-dasharray: 2px;
stroke-color : gray;
fill : transparent;
transition : all 0.5s;
cursor : pointer;
}
g.state.selected circle.outer {
stroke-width : 1px;
}
g.state text {
font : 12px sans-serif;
font-weight : bold;
pointer-events : none;
}
g.transition path,
path.dragline {
fill : none;
stroke : gray;
stroke-width: 1px;
}
g.transition path.foreground {
marker-end : url(#end-arrow);
}
g.transition.hover path.background {
stroke-dasharray: none;
stroke : aliceblue;
stroke-opacity : 1.0;
transition : all 0.5s;
}
g.transition path.background {
stroke-dasharray: none;
stroke-width: 8px;
stroke : transparent;
}
g.transition.selected path.foreground {
stroke-dasharray: 2px;
stroke-color : gray;
}
g.transition path {
cursor : default;
}
.end-arrow {
fill : gray;
stroke-width : 1px;
}
g.transition circle.endpoint {
display : none;
fill : none;
cursor : pointer;
stroke : gray;
stroke-dasharray: 2px;
}
g.transition circle.point {
display : none;
fill : aliceblue;
cursor : move;
stroke : gray;
}
g.transition.selected circle.endpoint,
g.transition.selected circle.point {
display : inline;
transition : all 0.5s;
}
g.transition:not( .selected).hover *,
path.dragline {
display : inline;
}
g.transition:not( .selected).hover {
transition : all 0.5s;
}
path.dragline {
pointer-events: none;
stroke-opacity : 0.5;
stroke-dasharray: 2px;
}
path.dragline.hidden {
stroke-width: 0;
}
/* disable text selection */
svg *::selection {
background : transparent;
}
svg *::-moz-selection {
background:transparent;
}
svg *::-webkit-selection {
background:transparent;
}
I understand the basics about d3 such as appending new circle and drag behaviors, but mainly the transitions part used to draw and connect lines to circle is holding me back.
There is quite a lot in there and actually, and looking at it it's not actually using typical css transitions as you might expect. I'll summarize the interesting parts and expand if you need. The interesting section is the following:
var dragPoint = d3.behavior.drag()
.on("drag", function( d, i) {
console.log( "transitionmidpoint drag");
var gTransitionPoint = d3.select( this);
gTransitionPoint.attr( "transform", function( d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [ d.x,d.y ] + ")"
});
// refresh transition path
gTransitions.selectAll( "path").attr( 'd', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll( "circle.endpoint").attr({
transform : transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll( "circle.point").attr({
transform : transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
This is where all the hard work is taking place. This code basically says, whenever the drag event occurs (e.g. you move a circle) execute the code within this function.
You can see this splits into sections:
Move the point that was clicked by a d.x and d.y which is the amount dragged from the previous event. This is done using the translate transform.
Change the path, this is done by updating the d parameter, which is how you specify the shape of an SVG path. The code that calculates the path is within the comuteTransitionPath function.
Update the circle.endpoint - This is a hidden point on the line
Update the circle.point - These are points on the line that control the curve, they are hidden by default.

Raphael JS rotate group/set not individually

I've been toying around with Raphael JS, but failed to find any solution to rotate a group/set of paths as group, and not individually.
A group/set of path:
Each element is rotated. Not the entire group/set:
How I do:
var bobble = map.paper.set();
bobble.push(map.paper.add([{
"fill":fill,
"stroke":"none",
x: 50,
y: 50,
width: 100,
height: 100,
"type":"rect",
"r": 4
},{
"fill":fill,
"stroke":"none",
x: 100,
y: 25,
width: 200,
height: 50,
"type":"rect",
"r": 4
}]));
bobble.rotate(45);
What am I doing wrong?
Here you go DEMO
var paper = Raphael('arrows');
var r1 = paper.rect(100,100,200,10,5).attr({fill:'white'});
var r2 = paper.rect(50,200,100,15,5).attr({fill:'white'});
var st = paper.set(r1,r2);
var l_coord = st.getBBox().x,
r_coord = st.getBBox().x2,
t_coord = st.getBBox().y,
b_coord = st.getBBox().y2;
var cx = (l_coord + r_coord)/2,
cy = (t_coord + b_coord)/2;
st.rotate(54,cx,cy);
Since you need to get your Raphael set's center coordinates, you can use getBBox() function which returns you:
The set of raphaeljs is a list of element. So, when you use tranform method, it is just a transform of unique element of list in the set.
I had created an plugin which supports g tag for my project. But I haven't yet implement the transform method.
const SVG = 'http://www.w3.org/2000/svg';
function TCRaphael(container, width, height) {
var paper = new Raphael(container, width, height);
paper.node = document.getElementById(container).getElementsByTagName("svg")[0];
console.log(paper.node)
paper.group = function (parent) {
return new Group(parent);
};
function Group(parent) {
var me = this;
this.node = document.createElementNS(SVG, "g");
if (typeof parent !== 'undefined') {
if (typeof parent.node != 'undefined')
parent.node.appendChild(me.node);
else{
parent.appendChild(me.node);
}
}
this.append = function (child) {
me.node.appendChild(child.node);
return child;
};
this.appendNode = function (childNode) {
me.node.appendChild(childNode);
};
this.appendTo = function (parent) {
if (typeof parent !== 'undefined') {
if (typeof parent.node != 'undefined')
parent.node.appendChild(me.node);
else{
parent.appendChild(me.node);
}
}
};
this.remove = function(){
me.node.parentNode.remove();
};
this.circle = function(x, y, r){
return me.append(paper.circle(x, y, r));
};
this.ellipse =function(x, y, rx, ry){
return me.append(paper.ellipse(x, y, rx, ry));
};
this.image = function(src, x, y, width, height){
return me.append(paper.image(src, x, y, width, height));
};
this.path = function(pathString){
return me.append(paper.path(pathString));
};
this.rect = function(x, y, width, height, r){
return me.append(paper.rect(x, y, width, height, r));
};
this.text = function(x, y, text){
return me.append(paper.text(x, y, text));
}
}
return paper;
}
You can add more function which you want to TCRaphael.
I think it's a little easier.
myset.transform("R45,20,20")
does exactly the same as
myset.rotate(45,20,20) // deprecated
the whole myset will rotate around the center at 20,20 (maybe the center of the set)
otherwise
myset.transform("R45")
will rotate each element of the set around it's own center

Resources