Related
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);
}
});
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.
I've subclassed the Image class so that I can add a label to it. However, the text seems to get rendered before the image such that the image is hiding the label. Below is the class declaration. Note that I tried calling callSuper() at the end of the _render() method, which didn't make sense to me anyway, and it didn't work. Seems to me if super is called first and then text is added after it should be on top right?
var Cabinet = fabric.util.createClass(fabric.Image, {
type: 'cabinet',
initialize: function(element, options) {
options || (options = { });
this.callSuper('initialize', element, options);
this.set('label', options.label || '');
this.set('height', options.height || 48);
this.set('width', options.width || 24);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
var txtOffset = this.label.length * 6;
ctx.fillStyle = '#000';
if (this.get('angle') !== 0) {
ctx.font = '11px Arial Narrow';
//ctx.save();
ctx.rotate(90 * Math.PI/180);
ctx.fillText(this.label, (this.width / 2) - (txtOffset / 2) - 8, this.height - 10);
//ctx.restore();
} else {
ctx.font = "9px Arial Narrow";
ctx.fillText(this.label, (this.width / 2) - (txtOffset / 2) - 8, this.height - 10);
}
}
});
I would appreciate any help you can provide.
Thanks,
Rob
i am new in fabric js want to set the drag limit
i have also try with https://github.com/kangax/fabric.js/wiki/Working-with-events
not able to get the solution.
please check the attached image, object can move anyware but it should be display in red area only.i want this. help me...thanks in advance !!
While Orangepill's answer is correct, it produces a "stuttering" when your object hits the object bounds. If you have a rectangular bounding box (and not a complex bounding object) an alternative is to allow the object to be dragged along the bounds and "slide" along the bounding box. You do this by capping the coordinates values and letting the other dimension move as usual. An example snippet would look like so:
var canvas = new fabric.Canvas("bounded");
var boundingBox = new fabric.Rect({
fill: "none",
width: 600,
height: 400,
hasBorders: false,
hasControls: false,
lockMovementX: true,
lockMovementY: true,
evented: false,
stroke: "red"
});
var movingBox = new fabric.Rect({
width: 100,
height: 100,
hasBorders: false,
hasControls: false
});
canvas.on("object:moving", function() {
var top = movingBox.top;
var bottom = top + movingBox.height;
var left = movingBox.left;
var right = left + movingBox.width;
var topBound = boundingBox.top;
var bottomBound = topBound + boundingBox.height;
var leftBound = boundingBox.left;
var rightBound = leftBound + boundingBox.width;
// capping logic here
movingBox.setLeft(Math.min(Math.max(left, leftBound), rightBound - movingBox.width));
movingBox.setTop(Math.min(Math.max(top, topBound), bottomBound - movingBox.height));
});
canvas.add(boundingBox);
canvas.add(movingBox);
See this example in JSFiddle here
Felix Fung's answer was a starting point, but there are many things to consider. Here is a version that accounts for some of them.
It handles the canvas having a viewport transform (ie, zoomed/panned) and objects that are center-origined instead of left/top-origined. It also constrains objects wider/taller than the viewport to the top/left instead of the bottom/right.
canvas.on("object:moving", function(e) {
var obj = e.target;
var canvas = obj.canvas;
var top = obj.top;
var left = obj.left;
var zoom = canvas.getZoom();
var pan_x = canvas.viewportTransform[4];
var pan_y = canvas.viewportTransform[5];
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
var c_width = canvas.width / zoom;
var c_height = canvas.height / zoom;
var w = obj.width * obj.scaleX
var left_adjust, right_adjust
if(obj.originX == "center") {
left_adjust = right_adjust = w / 2;
} else {
left_adjust = 0;
right_adjust = w;
}
var h = obj.height * obj.scaleY;
var top_adjust, bottom_adjust;
if(obj.originY == "center") {
top_adjust = bottom_adjust = h / 2;
} else {
top_adjust = 0;
bottom_adjust = h;
}
// if you need margins set them here
var top_margin = 0;
var bottom_margin = 0;
var left_margin = 0;
var right_margin = 0;
var top_bound = top_margin + top_adjust - pan_y;
var bottom_bound = c_height - bottom_adjust - bottom_margin - pan_y;
var left_bound = left_margin + left_adjust - pan_x;
var right_bound = c_width - right_adjust - right_margin - pan_x;
if( w > c_width ) {
obj.setLeft(left_bound);
} else {
obj.setLeft(Math.min(Math.max(left, left_bound), right_bound));
}
if( h > c_height ) {
obj.setTop(top_bound);
} else {
obj.setTop(Math.min(Math.max(top, top_bound), bottom_bound));
}
});
What had worked for me is to create an event listener for the object:moving event. When the move is happening you update the goodtop and goodleft variables and once you are out of bounds to reposition the object to the last good points.
var goodtop, goodleft, boundingObject;
canvas.on("object:moving", function(){
var obj = this.relatedTarget;
var bounds = boundingObject;
obj.setCoords();
if(!obj.isContainedWithinObject(bounds)){
obj.setTop(goodtop);
obj.setLeft(goodleft);
canvas.refresh();
} else {
goodtop = obj.top;
goodleft = obj.left;
}
});
I used Michael Johnston's snipped as a starting point to add bounding control for rotated elements. This snipped only covers the cases when either (obj.centerX && obj.centerY == "center") || (obj.centerX && obj.centerY != "center")
canvasRef.current.on("object:moving", function (e) {
var obj = e.target;
var canvas = obj.canvas;
var zoom = canvas.getZoom();
var pan_x = canvas.viewportTransform[4];
var pan_y = canvas.viewportTransform[5];
// get left, top, width, and height of object
var left = obj.left;
var top = obj.top;
var width = obj.width * obj.scaleX;
var height = obj.height * obj.scaleY;
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
var c_width = canvas.width / zoom;
var c_height = canvas.height / zoom;
// calculate values that define the origin of the object, when it is centered in the center or not
var left_adjust, right_adjust;
if (obj.originX == "center") {
left_adjust = right_adjust = width / 2;
} else {
left_adjust = 0;
right_adjust = width;
}
var top_adjust, bottom_adjust;
if (obj.originY == "center") {
top_adjust = bottom_adjust = height / 2;
} else {
top_adjust = 0;
bottom_adjust = height;
}
// support for rotated objects
if (obj.angle) {
var angle = obj.angle;
if (angle > 270) {
angle -= 270;
} else if (angle > 180) {
angle -= 180;
} else if (angle > 90) {
angle -= 90;
}
const radians = angle * (Math.PI / 180);
const w_opposite = width * Math.sin(radians);
const w_adjacent = width * Math.cos(radians);
const h_opposite = height * Math.sin(radians);
const h_adjacent = height * Math.cos(radians);
if (obj.originX != "center" && obj.originY != "center") {
if (obj.angle <= 90) {
left_adjust = h_opposite;
top_adjust = 0;
right_adjust = w_adjacent;
bottom_adjust = h_adjacent + w_opposite;
} else if (obj.angle > 90 && obj.angle <= 180) {
left_adjust = h_adjacent + w_opposite;
top_adjust = h_opposite;
right_adjust = 0;
bottom_adjust = w_adjacent;
} else if (obj.angle > 180 && obj.angle <= 270) {
left_adjust = w_adjacent;
top_adjust = w_opposite + h_adjacent;
right_adjust = h_opposite;
bottom_adjust = 0;
} else {
left_adjust = 0;
top_adjust = w_adjacent;
right_adjust = w_opposite + h_adjacent;
bottom_adjust = h_opposite;
}
}
if (obj.originX == "center" && obj.originY == "center") {
if (obj.angle <= 90 || (obj.angle > 180 && obj.angle <= 270)) {
left_adjust = (w_adjacent + h_opposite) / 2;
right_adjust = (w_adjacent + h_opposite) / 2;
top_adjust = (h_adjacent + w_opposite) / 2;
bottom_adjust = (h_adjacent + w_opposite) / 2;
} else {
left_adjust = (h_adjacent + w_opposite) / 2;
right_adjust = (h_adjacent + w_opposite) / 2;
top_adjust = (w_adjacent + h_opposite) / 2;
bottom_adjust = (w_adjacent + h_opposite) / 2;
}
}
}
// if you need margins set them here
var top_margin = 0;
var bottom_margin = 0;
var left_margin = 0;
var right_margin = 0;
var top_bound = top_margin + top_adjust - pan_y;
var bottom_bound = c_height - bottom_adjust - bottom_margin - pan_y;
var left_bound = left_margin + left_adjust - pan_x;
var right_bound = c_width - right_adjust - right_margin - pan_x;
if (width > c_width) {
obj.set("left", left_bound);
} else {
obj.set("left", Math.min(Math.max(left, left_bound), right_bound));
}
if (height > c_height) {
obj.set("top", top_bound);
} else {
obj.set("top", Math.min(Math.max(top, top_bound), bottom_bound));
}
});
SEE THE WORKING EXAMPLE
It's as simple as a water try this
just use this js
<script type="text/javascript">
//use global variable for canvas object
var canvas;
var ctx;
function onLoad() {
//get fabric canvas with id mycanvas
canvas = new fabric.Canvas('mycanvas');
canvas.on("mouse : down",function{
//get canvas 2d context
ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(115,60,221,390);//specify bounded rectangle
ctx.closePath();
ctx.clip();
ctx.save();
});
//now restore the context on mouse up
canvas.on("mouse : up",function(){
ctx.restore();//restore the context
});
}
</script>
Hope this will help you.
Njoy coding :)
now u can have the same clipping region for every object supported by Fabric.js for transformation and moving.
try it :).
I am fairly new to using d3, but what I am trying to do is make a chord diagram of some site traffic, and I am trying to make it interactive by changing the color of the paths when a user clicks on the group for a certain site.here is the style and script section of my code:
<style type="text/css">
.group text {
font: 11px sans-serif;
pointer-events: none;
}
#circle circle {
fill: none;
pointer-events: all;
}
.group path {
stroke: #000;
fill-opacity: .5;
}
path.chord {
stroke-width: .75;
fill-opacity: .75;
}
#circle:hover path.fade {
display: none;
}
</style>
</head>
<body>
<script type="text/javascript">
// Chart dimensions.
var w = 600,
h = 700,
r1 = Math.min(w, h) / 2 - 4,
r0 = r1 - 20,
format = d3.format(",.3r");
// Square matrices, asynchronously loaded; credits is the transpose of sitename.
var sitename = [];
// The chord layout, for computing the angles of chords and groups.
var layout = d3.layout.chord()
.sortGroups(d3.descending)
.sortSubgroups(d3.descending)
.sortChords(d3.descending)
.padding(.04);
// The color scale, for different categories of "worrisome" risk.
var fill = d3.scale.ordinal();
// The arc generator, for the groups.
var arc = d3.svg.arc()
.innerRadius(r0)
.outerRadius(r1);
// The chord generator (quadratic Bézier), for the chords.
var chord = d3.svg.chord()
.radius(r0);
// Add an SVG element for each diagram, and translate the origin to the center.
var svg = d3.select("body").selectAll("div")
.data([sitename])
.enter().append("div")
.style("display", "inline-block")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
// Load our data file…
d3.csv("data2.csv", function(data) {
var uniqueids = {},
array = [],
n = 0;
// Compute a unique id for each site.
data.forEach(function(d) {
d.siteid1 = uniqueIDMaker(d.siteid1);
d.siteid2 = uniqueIDMaker(d.siteid2);
d.valueOf = value; // convert object to number implicitly
});
// Initialize a square matrix of sitename and users
for (var i = 0; i < n; i++) {
sitename[i] = [];
for (var j = 0; j < n; j++) {
sitename[i][j] = 0;
}
}
// Populate the matrices, and stash a map from id to site.
data.forEach(function(d) {
sitename[d.siteid1.id][d.siteid2.id] = d;
array[d.siteid1.id] = d.siteid1;
array[d.siteid2.id] = d.siteid2;
});
// For each diagram…
svg.each(function(matrix, j) {
var svg = d3.select(this);
// Compute the chord layout.
layout.matrix(matrix);
// Add chords.
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", "chord")
.style("fill", function(d) { return fill(d.source.value); })
.style("stroke", function(d) { return d3.rgb(fill(d.source.value)).darker(); })
.attr("d", chord)
.on("dblclick",function(){
d3.select(this)
.style("fill","red")
.style("stroke","yellow")
})
.append("svg:title")
.text(function(d) { return "site " + d.source.value.siteid2.name + " and site " + d.source.value.siteid1.name + " have " + format(d.source.value) + " common users"; })
;
// Add groups.
var g = svg.selectAll("g.group")
.data(layout.groups)
.enter().append("svg:g")
.attr("class", "group");
// Add the group arc.
g.append("svg:path")
.style("fill", function(d) { return fill(array[d.index]); })
.attr("id", function(d, i) { return "group" + d.index + "-" + j; })
.attr("d", arc)
.append("svg:title")
.text(function(d) { return "site " + array[d.index].name + " has " + format(d.value) + "users"; });
g.append("svg:text")
.attr("x", 6)
.attr("dy", 15)
.filter(function(d) { return d.value > 110; })
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group" + d.index + "-" + j; })
.text(function(d) { return array[d.index].name; });
});
function uniqueIDMaker(d) {
return uniqueids[d] || (uniqueids[d] = {
name: d,
id: n++
});
}
function value() {
return +this.count;
}});
</script>
any help would be greatly appreciated
http://jsfiddle.net/Rw3aK/2/ is a jsFiddle of the script, not sure how to make it read from a file, so here is the contents of data2.csv:
siteid1,siteid2,count,pubid1,pubid2
8,94,11132,57141,57141
201,94,10035,57141,57141
201,8,9873,57141,57141
0,94,8488,45020,57141
0,8,8258,45020,57141
0,201,7644,45020,57141
0,1,6973,45020,45020
94,1,5719,57141,45020
8,1,5670,57141,45020
1,201,5410,57141,45020
I forked your jsfiddle and converted your CSV data to JSON, now in a variable data: http://jsfiddle.net/mdml/K6FHW/.
I also modified your code slightly so that when you click on a group, all outgoing chords are highlighted red. When you click on a group again, the chords change back to their original color. Here're the relevant snippets:
When adding the chords, label each chord with a class according to the chord's source
svg.selectAll(".chord")
.data(layout.chords)
.enter().append("svg:path")
.attr("class", function(d){ return "chord chord-" + d.source.index; })
...
When clicking a group, check if that group's chords are highlighted.
If so, fill the chords with their default color
If not, fill the chords red
Then record whether or not the group's chords are highlighted in a variable d.chordHighlighted
g.append("svg:path")
...
.attr("id", function (d, i) {
return "group" + d.index + "-" + j;
})
...
.on("click", function(d){
if (d.chordHighlighted)
d3.selectAll(".chord-" + d.index)
.style("fill", fill(d.value));
else{
d3.selectAll(".chord-" + d.index)
.style("fill", "red");
}
d.chordHighlighted = d.chordHighlighted ? false : true;
})