Based on my original Problem ( C3/D3 pie legend format / label overlap ) I have attempted to apply a bugfix originally created for flot to the C3 piechart.
In principal it seems to work, a collsion is detected and the label is being moved but the positioning seems to be incorrect.
Here ist some sample code to show the problem
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1 && Q4
if ((pos[0] > 0 && pos[1] >= 0) || (pos[0] > 0 && pos[1] > 0)) {
return Math.atan(pos[0] / pos[1]);
}
//Q2
else if (pos[0] < 0 && pos[1] >= 0) {
return Math.atan(pos[0] / pos[1]) + Math.PI;
}
//Q3
else if (pos[0] < 0 && pos[1] <= 0) {
return Math.atan(pos[0] / pos[1]) - Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//then check and modify labels
labelSelection.each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.text("");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0)
.attr("text-anchor", "middle");
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var newRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [
[labelLeft, labelLeft + bbox.width],
[labelBottom, labelBottom + bbox.height]
];
var yix = 10; //max label reiterations with collisions
do {
for (var i = allLabels.length - 1; i >= 0; i--) {
if (!labelNode.isEqualNode(allLabels[i].node())) {
var checkLabelBBox = allLabels[i].node().getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2), (checkLabelPos[0] - checkLabelBBox.width / 2) + checkLabelBBox.width],
[(checkLabelPos[1] - checkLabelBBox.height / 2), (checkLabelPos[1] - checkLabelBBox.height / 2) + checkLabelBBox.height]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
bCollision = true;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (labelPos[0] !== (labelLeft + bbox.width / 2) || labelpos[1] !== (labelBottom + bbox.height / 2)) {
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - padding) +
"," + (labelPos[1] - labelTextHeight / 2 - padding) + ")")
.attr("width", bbox.width + 2 * padding)
.attr("height", bbox.height + 2 * padding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<div id="chart">
</div>
Does anyone have an idea what is going wrong here?
Basically it was a combination of several things:
- calculating the angle (messed up arctan ;))
- checking the overlav of the rects (not only text boxes)
- wrong loops
This does work now:
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1
if ((pos[0] > 0 && pos[1] >= 0)) {
return Math.atan(pos[1] / pos[0]);
}
//Q2 & Q3
else if (pos[0] < 0) {
return Math.atan(pos[1] / pos[0]) + Math.PI;
} //Q4
else if (pos[0] > 0 && pos[1] < 0) {
return Math.atan(pos[1] / pos[0]) + 2 * Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//.filter(function(d){return d.style("display") !== 'none'});
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//padding of the surrounding rect
var rectPadding = 1;
//then check and modify labels
labelSelection.each(function(v, labelIndex) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
if (data.length > 1) {
label.html('')
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0);
}, label);
}
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
labelPos[0] = parseFloat(labelPos[0]);
labelPos[1] = parseFloat(labelPos[1]);
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var oldRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var newRadius = oldRadius;
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [ [ labelLeft - rectPadding, labelLeft + bbox.width + rectPadding ], [ labelBottom-rectPadding, labelBottom + bbox.height + rectPadding ] ];
var yix = 10; //max label reiterations with collisions
do {
for (var i = labelIndex - 1; i >= 0; i--) {
var checkLabelNode = allLabels[i].node();
var checkLabelBBox = checkLabelNode.getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
if (checkLabelBBox && checkLabelPos) { //element visible
checkLabelPos[0] = parseFloat(checkLabelPos[0]);
checkLabelPos[1] = parseFloat(checkLabelPos[1]);
//box is text bbox + padding from rect
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2) - rectPadding, (checkLabelPos[0] + checkLabelBBox.width / 2) + rectPadding],
[(checkLabelPos[1] - checkLabelBBox.height / 2) - rectPadding, (checkLabelPos[1] + checkLabelBBox.height / 2) + rectPadding]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
bCollision = true;
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (Math.round(labelPos[0]) !== Math.round(labelLeft + bbox.width / 2) || Math.round(labelPos[1]) !== Math.round(labelBottom + bbox.height / 2)) {
/*console.log("moving label[" + labelIndex + "][" + labelPos[0] + "," + labelPos[1] + "] to [" + (labelLeft + bbox.width / 2) + "," + (labelBottom + bbox.height / 2) + "] Radius " + oldRadius + "=>" + newRadius + " #" + yix);*/
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - rectPadding) +
"," + (labelPos[1] - labelTextHeight / 2 - rectPadding) + ")")
.attr("width", bbox.width + 2 * rectPadding)
.attr("height", bbox.height + 2 * rectPadding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.js"></script>
<div id="chart">
</div>
optional todo: hide labels still overlapping in the end
Related
For my fabric.js project: I'm trying to setup object snapping and alignment guidelines. For snapping, this means when a user drags an object around, if any edge of the object comes close to alignment with another object edge, it will snap into place. During this time, guidelines appear as visual helpers for the user.
So far I'm implementing existing work, done by various fabric.js contributors, found here:
centering_guidelines.js & aligning_guidelines.js.
WORKS: At default zoom (1), object snapping and alignment guidelines work great!
FAILS: When zooming (in or out), the visual guidelines appear in the wrong position, however snapping maintains correct functionality.
CODE SAMPLES: Move objects around. At default zoom, snapping and guidelines work great. Change zoom level (with mousewheel) and notice guidelines are not positioned correctly, however snapping works fine.
Sample 1: Simple
Original libraries loaded as-is; simple demo.
https://codepen.io/MarsAndBack/pen/ZEQMXoM
Sample 2: Detailed
Original libraries copy-pasted inline, with modifications to help investigation.
https://codepen.io/MarsAndBack/pen/LYGJGoq
Note: Codepen has full code.
// ==========================================
// SETUP
// ==========================================
const canvas = new fabric.Canvas("myCanvas")
canvas.backgroundColor = "#222222";
var lastClientX = 0
var lastClientY = 0
var state = "default"
const outer = null
const box1 = null
const box2 = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
this.alignmentLines_horizontal = ""
this.alignmentLines_vertical = ""
fabric.Object.prototype.set({
cornerSize: 15,
cornerStyle: 'circle',
cornerColor: '#ffffff',
transparentCorners: false
})
setupObjects()
updateInfo(canvas)
function setupObjects() {
this.outer = new fabric.Rect({
width: canvas.getWidth(),
height: canvas.getHeight(),
top: 20,
left: 20,
stroke: '#ffffff',
evented: false,
selectable: false
})
this.box1 = new fabric.Rect({
width: 240,
height: 100,
top: 20,
left: 20,
fill: '#fff28a',
myType: "box"
})
this.box2 = new fabric.Rect({
width: 240,
height: 100,
top: 140,
left: 20,
fill: '#ff8a8a',
myType: "box"
})
this.box3 = new fabric.Rect({
width: 100,
height: 160,
top: 20,
left: 280,
fill: '#cf8aff',
myType: "box"
})
canvas.add(this.outer)
this.outer.center()
canvas.add(this.box1)
canvas.add(this.box2)
canvas.add(this.box3)
let allBoxes = new fabric.ActiveSelection(canvas.getObjects().filter(obj => obj.myType == "box"), {
canvas: canvas
})
allBoxes.center()
allBoxes.destroy()
}
function updateInfo() {
let info_zoom = document.getElementById('info_zoom')
let info_vptTop = document.getElementById('info_vptTop')
let info_vptLeft = document.getElementById('info_vptLeft')
let info_centerLine_horizontal = document.getElementById('info_centerLine_horizontal')
let info_centerLine_vertical = document.getElementById('info_centerLine_vertical')
let info_alignmentLines_horizontal = document.getElementById('info_alignmentLines_horizontal')
let info_alignmentLines_vertical = document.getElementById('info_alignmentLines_vertical')
info_zoom.innerHTML = canvas.getZoom().toFixed(2)
info_vptTop.innerHTML = Math.round(canvas.viewportTransform[5])
info_vptLeft.innerHTML = Math.round(canvas.viewportTransform[4])
info_centerLine_horizontal.innerHTML = this.centerLine_horizontal
info_centerLine_vertical.innerHTML = this.centerLine_vertical
info_alignmentLines_horizontal.innerHTML = this.alignmentLines_horizontal
info_alignmentLines_vertical.innerHTML = this.alignmentLines_vertical
}
// ------------------------------------
// Reset
// ------------------------------------
let resetButton = document.getElementById('reset')
resetButton.addEventListener('click', function() {
reset()
}, false)
function reset() {
canvas.remove(...canvas.getObjects())
setupObjects()
canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
updateInfo()
}
// ------------------------------------
// ==========================================
// MOUSE INTERACTIONS
// ==========================================
// MOUSEWHEEL ZOOM
canvas.on('mouse:wheel', (opt) => {
let delta = 0
// -------------------------------
// WHEEL RESOLUTION
let wheelDelta = opt.e.wheelDelta
let deltaY = opt.e.deltaY
// CHROME WIN/MAC | SAFARI 7 MAC | OPERA WIN/MAC | EDGE
if (wheelDelta) {
delta = -wheelDelta / 120
}
// FIREFOX WIN / MAC | IE
if (deltaY) {
deltaY > 0 ? delta = 1 : delta = -1
}
// -------------------------------
let pointer = canvas.getPointer(opt.e)
let zoom = canvas.getZoom()
zoom = zoom - delta / 10
// limit zoom in
if (zoom > 4) zoom = 4
// limit zoom out
if (zoom < 0.2) {
zoom = 0.2
}
//canvas.zoomToPoint({
// x: opt.e.offsetX,
// y: opt.e.offsetY
//}, zoom)
canvas.zoomToPoint(
new fabric.Point(canvas.width / 2, canvas.height / 2),
zoom);
opt.e.preventDefault()
opt.e.stopPropagation()
canvas.renderAll()
canvas.calcOffset()
updateInfo(canvas)
})
initCenteringGuidelines(canvas)
initAligningGuidelines(canvas)
// ==========================================
// CANVAS CENTER SNAPPING & ALIGNMENT GUIDELINES
// ==========================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/centering_guidelines.js
/**
* Augments canvas by assigning to `onObjectMove` and `onAfterRender`.
* This kind of sucks because other code using those methods will stop functioning.
* Need to fix it by replacing callbacks with pub/sub kind of subscription model.
* (or maybe use existing fabric.util.fire/observe (if it won't be too slow))
*/
function initCenteringGuidelines(canvas) {
let canvasWidth = canvas.getWidth(),
canvasHeight = canvas.getHeight(),
canvasWidthCenter = canvasWidth / 2,
canvasHeightCenter = canvasHeight / 2,
canvasWidthCenterMap = {},
canvasHeightCenterMap = {},
centerLineMargin = 4,
centerLineColor = 'purple',
centerLineWidth = 2,
ctx = canvas.getSelectionContext(),
viewportTransform
for (let i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin; i <= len; i++) {
canvasWidthCenterMap[Math.round(i)] = true
}
for (let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin; i <= len; i++) {
canvasHeightCenterMap[Math.round(i)] = true
}
function showVerticalCenterLine() {
showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight)
}
function showHorizontalCenterLine() {
showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5)
}
function showCenterLine(x1, y1, x2, y2) {
ctx.save()
ctx.strokeStyle = centerLineColor
ctx.lineWidth = centerLineWidth
ctx.beginPath()
ctx.moveTo(x1 * viewportTransform[0], y1 * viewportTransform[3])
ctx.lineTo(x2 * viewportTransform[0], y2 * viewportTransform[3])
ctx.stroke()
ctx.restore()
}
let afterRenderActions = [],
isInVerticalCenter,
isInHorizontalCenter
canvas.on('mouse:down', () => {
isInVerticalCenter = isInHorizontalCenter = null
this.centerLine_horizontal = ""
this.centerLine_vertical = ""
updateInfo()
viewportTransform = canvas.viewportTransform
})
canvas.on('object:moving', function(e) {
let object = e.target,
objectCenter = object.getCenterPoint(),
transform = canvas._currentTransform
if (!transform) return
isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap,
isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap
if (isInHorizontalCenter || isInVerticalCenter) {
object.setPositionByOrigin(new fabric.Point((isInVerticalCenter ? canvasWidthCenter : objectCenter.x), (isInHorizontalCenter ? canvasHeightCenter : objectCenter.y)), 'center', 'center')
}
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
if (isInVerticalCenter) {
showVerticalCenterLine()
this.centerLine_horizontal = ""
this.centerLine_vertical = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
}
if (isInHorizontalCenter) {
showHorizontalCenterLine()
this.centerLine_horizontal = (canvasWidthCenter + 0.5) + ", " + 0 + ", " + (canvasWidthCenter + 0.5) + ", " + canvasHeight
this.centerLine_vertical = ""
}
updateInfo()
})
canvas.on('mouse:up', function() {
// clear these values, to stop drawing guidelines once mouse is up
canvas.renderAll()
})
}
// ===============================================
// OBJECT SNAPPING & ALIGNMENT GUIDELINES
// ===============================================
// ORIGINAL:
// https://github.com/fabricjs/fabric.js/blob/master/lib/aligning_guidelines.js
// Original author:
/**
* Should objects be aligned by a bounding box?
* [Bug] Scaled objects sometimes can not be aligned by edges
*
*/
function initAligningGuidelines(canvas) {
let ctx = canvas.getSelectionContext(),
aligningLineOffset = 5,
aligningLineMargin = 4,
aligningLineWidth = 2,
aligningLineColor = 'lime',
viewportTransform,
zoom = null,
verticalLines = [],
horizontalLines = [],
canvasContainer = document.getElementById("myCanvas"),
containerWidth = canvasContainer.offsetWidth,
containerHeight = canvasContainer.offsetHeight
function drawVerticalLine(coords) {
drawLine(
coords.x + 0.5, coords.y1 > coords.y2 ? coords.y2 : coords.y1,
coords.x + 0.5, coords.y2 > coords.y1 ? coords.y2 : coords.y1
)
}
function drawHorizontalLine(coords) {
drawLine(
coords.x1 > coords.x2 ? coords.x2 : coords.x1, coords.y + 0.5,
coords.x2 > coords.x1 ? coords.x2 : coords.x1, coords.y + 0.5
)
}
function drawLine(x1, y1, x2, y2) {
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
ctx.moveTo(
((x1 + viewportTransform[4]) * zoom),
((y1 + viewportTransform[5]) * zoom)
)
//console.log("-------")
//console.log("x1 :" + x1)
//console.log("viewportTransform[4] :" + viewportTransform[4])
//console.log("zoom :" + zoom)
//console.log("x :" + (x1 + canvas.viewportTransform[4]) * zoom)
ctx.lineTo(
((x2 + viewportTransform[4]) * zoom),
((y2 + viewportTransform[5]) * zoom)
)
ctx.stroke()
ctx.restore()
}
function isInRange(value1, value2) {
value1 = Math.round(value1)
value2 = Math.round(value2)
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
if (i === value2) {
return true
}
}
return false;
}
canvas.on('mouse:down', function() {
verticalLines.length = horizontalLines.length = 0
viewportTransform = canvas.viewportTransform
zoom = canvas.getZoom()
})
canvas.on('object:moving', (e) => {
verticalLines.length = horizontalLines.length = 0
let activeObject = e.target,
canvasObjects = canvas.getObjects().filter(obj => obj.myType == "box"),
activeObjectCenter = activeObject.getCenterPoint(),
activeObjectLeft = activeObjectCenter.x,
activeObjectTop = activeObjectCenter.y,
activeObjectBoundingRect = activeObject.getBoundingRect(),
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
horizontalInTheRange = false,
verticalInTheRange = false,
transform = canvas._currentTransform;
//console.log("|||||||||")
//console.log("active acoords is: " + JSON.stringify(activeObject.aCoords, null, 4))
//console.log("active acoords is: " + JSON.stringify(activeObject.oCoords, null, 4))
//console.log("active left offset is: " + JSON.stringify(activeObject.aCoords, null, 4))
//containerWidth = canvasContainer.offsetWidth
//containerHeight = canvasContainer.offsetHeight
//console.log("active left from container is: " + (containerWidth - this.outer.width) / 2 + activeObject.aCoords.tl.x )
if (!transform) return;
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
for (let i = canvasObjects.length; i--;) {
if (canvasObjects[i] === activeObject) continue
let objectCenter = canvasObjects[i].getCenterPoint(),
objectLeft = objectCenter.x,
objectTop = objectCenter.y,
objectBoundingRect = canvasObjects[i].getBoundingRect(),
objectHeight = objectBoundingRect.height / viewportTransform[3],
objectWidth = objectBoundingRect.width / viewportTransform[0]
// snap by the horizontal center line
if (isInRange(objectLeft, activeObjectLeft)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center');
}
// snap by the left edge
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft - objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the right edge
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
verticalInTheRange = true
verticalLines.push({
x: objectLeft + objectWidth / 2,
y1: (objectTop < activeObjectTop) ?
(objectTop - objectHeight / 2 - aligningLineOffset) :
(objectTop + objectHeight / 2 + aligningLineOffset),
y2: (activeObjectTop > objectTop) ?
(activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) :
(activeObjectTop - activeObjectHeight / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center')
}
// snap by the vertical center line
if (isInRange(objectTop, activeObjectTop)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center')
}
// snap by the top edge
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop - objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center');
}
// snap by the bottom edge
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
horizontalInTheRange = true
horizontalLines.push({
y: objectTop + objectHeight / 2,
x1: (objectLeft < activeObjectLeft) ?
(objectLeft - objectWidth / 2 - aligningLineOffset) :
(objectLeft + objectWidth / 2 + aligningLineOffset),
x2: (activeObjectLeft > objectLeft) ?
(activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) :
(activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset)
})
activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center')
}
}
if (!horizontalInTheRange) {
horizontalLines.length = 0
}
if (!verticalInTheRange) {
verticalLines.length = 0
}
})
canvas.on('mouse:wheel', (opt) => {
verticalLines.length = horizontalLines.length = 0
})
canvas.on('before:render', function() {
canvas.clearContext(canvas.contextTop)
})
canvas.on('after:render', () => {
for (let i = verticalLines.length; i--;) {
drawVerticalLine(verticalLines[i])
}
for (let i = horizontalLines.length; i--;) {
drawHorizontalLine(horizontalLines[i])
}
this.alignmentLines_horizontal = JSON.stringify(horizontalLines, null, 4)
this.alignmentLines_vertical = JSON.stringify(verticalLines, null, 4)
updateInfo()
// console.log("activeObject left edge x is: " + canvas.getActiveObject().left)
//verticalLines.length = horizontalLines.length = 0
canvas.calcOffset()
})
canvas.on('mouse:up', () => {
//verticalLines.length = horizontalLines.length = 0
canvas.renderAll()
//this.alignmentLines_horizontal = horizontalLines
//this.alignmentLines_vertical = verticalLines
//updateInfo()
})
}
#container {
display: flex;
font-family: sans-serif;
}
#header {
display: flex;
}
#reset {
background-color: #333333;
color: #ffffff;
padding: 1em;
border: none;
margin: 0.5em;
margin-top: 6em;
cursor: pointer;
}
#reset:hover {
background-color: #666666;
}
#reset:active {
background-color: #333333;
}
#info {
/* display: flex; */
display: none;
flex-direction: column;
}
#info>div {
display: flex;
flex-direction: column;
}
#info>div>div {
display: flex;
margin: 0.5em;
}
canvas {
display: block;
}
hr {
width: 100%;
}
<script src="https://pagecdn.io/lib/fabric/3.6.3/fabric.min.js"></script>
<div id="container">
<canvas id="myCanvas" width="500" height="300"></canvas>
<div id="sidebar">
<button id="reset">RESET</button>
<div id="info">
<div>
<div><b>zoom:</b>
<div id="info_zoom"></div>
</div>
<div><b>viewport top:</b>
<div id="info_vptTop"></div>
</div>
<div><b>viewport left:</b>
<div id="info_vptLeft"></div>
</div>
</div>
<hr />
<div>
<div><b>Alignment lines (green)</b></div>
<div><b>Horizontal:</b>
<div id="info_alignmentLines_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_alignmentLines_vertical"></div>
</div>
</div>
<hr />
<div>
<div><b>Canvas-center lines (purple)</b></div>
<div><b>Horizontal:</b>
<div id="info_centerLine_horizontal"></div>
</div>
<div><b>Vertical:</b>
<div id="info_centerLine_vertical"></div>
</div>
</div>
</div>
</div>
</div>
I changed drawLine function.Should work
https://jsfiddle.net/3mtcsy6p/1/
function drawLine(x1, y1, x2, y2) {
var originXY = fabric.util.transformPoint(new fabric.Point(x1, y1), canvas.viewportTransform),
dimensions = fabric.util.transformPoint(new fabric.Point(x2, y2),canvas.viewportTransform);
ctx.save()
ctx.lineWidth = aligningLineWidth
ctx.strokeStyle = aligningLineColor
ctx.beginPath()
ctx.moveTo(
( (originXY.x ) ),
( (originXY.y ) )
)
ctx.lineTo(
( (dimensions.x ) ),
( (dimensions.y ) )
)
ctx.stroke()
ctx.restore()
}
A more performant way would be to do the calculation directly in drawLine function insted of calling another one. As the function is called on every mouse movement, performance is important.
The new function:
function drawLine(x1, y1, x2, y2) {
ctx.save();
ctx.lineWidth = aligningLineWidth;
ctx.strokeStyle = aligningLineColor;
ctx.beginPath();
ctx.moveTo(((x1*zoom+viewportTransform[4])), ((y1*zoom+viewportTransform[5])));
ctx.lineTo(((x2*zoom+viewportTransform[4])), ((y2*zoom+viewportTransform[5])));
ctx.stroke();
ctx.restore();
}
The code pen: https://codepen.io/Theluiz-eduardo/pen/abqMVGV
Just out of curiosity, a performance test calling the function vs calculating directly:
const ctx = {}, canvas = {}, fabric = { util: {} }
ctx.moveTo = () => true;
ctx.lineTo = () => true;
canvas.viewportTransform = [0.5, 0, 0, 0.5, 200, 100]
canvas.getZoom = () => 0.5;
fabric.util.transformPoint = function(p, t, ignoreOffset) {
if (ignoreOffset) {
return fabric.Point(
t[0] * p.x + t[2] * p.y,
t[1] * p.x + t[3] * p.y
);
}
return fabric.Point(
t[0] * p.x + t[2] * p.y + t[4],
t[1] * p.x + t[3] * p.y + t[5]
);
}
fabric.Point = (x, y) => ({x: x, y: y})
const x1 = 10, x2 = 15, y1 = 20, y2 = 25, zoom = canvas.getZoom()
function performTest(fun, name, repeat) {
console.time(name)
while (repeat--) {
fun()
}
console.timeEnd(name)
}
const callingFun = () => {
var originXY = fabric.util.transformPoint(fabric.Point(x1, y1), canvas.viewportTransform),
dimensions = fabric.util.transformPoint(fabric.Point(x2, y2),canvas.viewportTransform);
ctx.moveTo(originXY.x, originXY.y)
ctx.lineTo(dimensions.x, dimensions.y)
}
const directCalc = () => {
ctx.moveTo(((x1*zoom+canvas.viewportTransform[4])), ((y1*zoom+canvas.viewportTransform[5])));
ctx.lineTo(((x2*zoom+canvas.viewportTransform[4])), ((y2*zoom+canvas.viewportTransform[5])));
}
performTest(directCalc, 'directCalc', 10**8)
performTest(callingFun, 'callingFun', 10**8)
I can't seem to get collision detection to work with the floor array? I seem to be having the issue in the ballMovement function.
The ball falls straight through the rectangles made by the array.
Floor Array (issue)
Collision Detection
<html lang=en>
<head>
<meta charset=utf-8>
<title>Javascript gravity</title>
<link rel="stylesheet" href="game.css">
</head>
<body onload="init()">
<script>
var canvas, ctx, container;
canvas = document.createElement( 'canvas' );
ctx = canvas.getContext("2d");
var ball;
var message = "Helloworld";
// Velocity x
var vx = 2.0;
// Velocity y - randomly set
var vy;
var gravity = 0.5;
var bounce = 0.7;
var xFriction = 0.1;
// Floor Array
var floor = new Array();
//Rectagles
Rectangle = function(x, y, w, h, color){
if (x == null || y == null || w == null || h == null){
alert("You must pass in all the veriables for a rectange: (x, y, width, height)");
var errorMsg = "The following are not provided:";
if (x == null)
errorMsg += " 'x' ";
if (y == null)
errorMsg += " 'y' ";
if (w == null)
errorMsg += " 'width' ";
if (h == null)
errorMsg += " 'height'";
alert(errorMsg);
throw new Error(errorMsg);
}
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.color = new Color();
this.Contains = function(x, y){
if (x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.height)
return true;
else
return false;
};
this.Draw = function(ctx){
ctx.fillStyle = this.color.ToStandard();
ctx.fillRect(this.x, this.y, this.width, this.height);
}
};
//Rectangle Colors
Color = function(r, g, b, a){
this.r = 255;
this.g = 255;
this.b = 255;
this.a = 1;
if (r != null)
this.r = r;
if (g != null)
this.g = g;
if (b != null)
this.b = b;
if (a != null)
this.a = a;
this.ToStandard = function(noAlpha){
if (noAlpha == null || !noAlpha)
return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
else
return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
};
};
function init(){
setupCanvas();
vy = (Math.random() * -5) + -5;
ball = {x:canvas.width / 2, y:100, radius:10, status: 0, color:"red"};
floor.push(new Rectangle(0, 480, 500, 20));
floor.push(new Rectangle(250, 350, 200, 20));
//floor.push(new Rectangle(150, 300, 20, 20));
//floor.push(new Rectangle(200, 250, 20, 20));
//floor.push(new Rectangle(250, 200, 20, 20));
//floor.push(new Rectangle(300, 150, 20, 20));
//floor.push(new Rectangle(350, 100, 20, 20));
//floor.push(new Rectangle(400, 50, 20, 20));
for (var i = 0; i < floor.length; i++)floor[i].color = new Color(0, 0, 0, 1);
}//end init method
function draw() {
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i = 0; i < floor.length; i++)
floor[i].Draw(ctx);
//display some text
ctx.fillStyle = "red";
ctx.font = "20px Arial";
ctx.fillText(message, 20,20);
//draw cirlce
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, false);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
ballMovement();
}
setInterval(draw, 1000/35);
function ballMovement(){
ball.x += vx;
ball.y += vy;
vy += gravity;
//If either wall is hit, change direction on x axis
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0){
vx *= -1;
}
// Ball hits the canvas floor
if (ball.y + ball.radius > canvas.height){// ||
// Re-positioning on the base
ball.y = canvas.height - ball.radius;
//bounce the ball
vy *= -bounce;
//do this otherwise, ball never stops bouncing
if(vy<0 && vy>-2.1)
vy=0;
//do this otherwise ball never stops on xaxis
if(Math.abs(vx)<1.1)
vx=0;
xF();
}
// Ball hits the rectangles
if (ball.y + ball.radius > floor.width){// ||
// Re-positioning on the base
ball.y = floor.height - ball.radius;
//bounce the ball
vy *= -bounce;
//do this otherwise, ball never stops bouncing
if(vy<0 && vy>-2.1)
vy=0;
//do this otherwise ball never stops on xaxis
if(Math.abs(vx)<1.1)
vx=0;
xF();
}
}
function xF(){
if(vx>0)
vx = vx - xFriction;
if(vx<0)
vx = vx + xFriction;
}
function setupCanvas() {//setup canvas
container = document.createElement( 'div' );
container.className = "container";
canvas.width = 500;
canvas.height = 500;
document.body.appendChild( container );
container.appendChild(canvas);
ctx.strokeStyle = "red";
ctx.lineWidth =1;
}
</script>
</body>
</html>
There are a couple of mistakes:
You compare the ball position on Y axis ball.y to the bar width floor.width, it may be a typing/editing mistake.
You should replace floor.width by floor.y to check whether the ball hits the bar when it falls.
But floor is an Array of Rectangle, it includes the bar and potential bricks to break, if I guess it right. So you need to loop through floor before checking, otherwise floor.height equals undefined.
for (let i = 0; i < floor.length; i += 1) {
const rect = floor[i];
// Ball hits the rectangle
if (ball.y + ball.radius > rect.y) {
// ...
}
}
Then, floor isn't an appropriate name for an Array which doesn't contain any 'floor'.
Finally, add a condition to handle the ball X position (collisions with the bar, the bricks, etc.).
Working example:
https://jsfiddle.net/nmerinian/3sokr512/22/
Have a good day
I want to practice pixel manipulation with matrix for extract an image from another.
This is what I have done with css transformation matrix :
https://www.noelshack.com/2017-18-1493893008-capture-2.png
With the Left image 'L' I have place 4 points around the image and in the right image 'R' I find the content of the transformation.
For that i use the property transform of the css but i want to do the manipulation manually.
CSS version :
matrix3d(1.5456325781948308,1.6561987730956724,0,0.0012239101773909712,-0.4663849104791486,2.218793881308064,0,0.0009095626603861196,0,0,1,0,12.247969030166722,-17.754955132517754,0,0.9951722722714726)
Matrix 'M':
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
I want to know for each pixel in the image R what are their pixel related position in the image L.
For example (0,0) in R is (52,203) in R.
For that i do this calculation.
M * P = P'
P is the pixel position in R image
P' is the pixel position in L image
P matrix is define like that:
[[x],
[y],
[0],
[1]]
So for the 0,0 position, I do this :
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
X
[[0],
[0],
[0],
[1]]
=
[[0.0012239101773909712],
[0.0009095626603861196],
[0],
[0.9951722722714726]]
This is the result, but the 2 first component :
(0.0012239101773909712, 0.0009095626603861196)
is too smaller than expected. can you help me to find the problem.
scincerly,
MatrixCuriosity.
These are homogeneous coordinates. So given some [x1, y1, z1, 1] as input you obtain some [x2, y2, z2, w2] but the actual position they describe is [x2/w2, y2/w2, z2/w2], i.e. you have to divide by the last coordinate.
But this doesn't lead to the result you expected. Nor does replacing the matrix with its adjunct (or equivalently inverse), nor its transpose. Both of these are conventions that are easy to get wrong, so without spending too much thought about which version you actually have and should have, trying all four alternatives (with and without adjunct, with and without transpose) solves a huge number of trivial problems.
But not yours. So my next best bet would be that the coordinates you expect are measured from some corner of the image, while the CSS property transform-origin is at it's initial value of 50% 50% 0 so the origin of the coordinate system is in fact in the center of the object.
Actually sharing the HTML and CSS for this might have allowed me to verify this assumption. Now you have to check whether this applies to you. I remember that when I last created a projective image transformation demo to answer a question about finding the transform, I deliberately set transform-origin: 0 0; (and the various vendor-prefixed versions of this) to avoid such problems.
Thanks a lot MvG.
I follow your link and I find what I want [https://math.stackexchange.com/a/339033]
Just one thing, I have to invert the C matrix to find the pixel related L<-R
I share my code for give an idea of what you have to do
You can find my implementation in the function computeMat()
<style>
body {
touch-action: none;
overflow-y: hidden;
}
#canvas_toeic
{
position:absolute;
top:0;
left:0;
}
</style>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/3.12.2/math.min.js"></script>
</head>
<body>
<canvas id="canvas_toeic" width="600" height="400">
</canvas>
<script type="text/javascript">
var image = new Image();
image.src = 'image.jpg';
image.onload = function() {
var c = document.getElementById("canvas_toeic");
var ratio = image.width / image.height;
var canvasWidth = document.body.clientWidth;
var canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
var canvasLargeur = canvasWidth;
var canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
var canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
var ctx = c.getContext("2d");
var idPoint = -1;
var points = [];
for(var i = 0; i < 4; i++)
points[i] = {x:0, y:0};
var marginImage = Math.round(40 * canvasPixelRatio);
points[0].x = marginImage;
points[0].y = marginImage;
points[1].x = marginImage;
points[1].y = canvasHeight - marginImage;
points[2].x = canvasWidth - marginImage;
points[2].y = canvasHeight - marginImage;
points[3].x = canvasWidth - marginImage;
points[3].y = marginImage;
function draw(points) {
console.log("draw");
// Fond
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.drawImage(image, marginImage, marginImage, canvasWidth - marginImage * 2, canvasHeight - marginImage * 2); // this fait référence à l'objet courant (=image)
if(idPoint == -1)
ctx.lineWidth = 3 * canvasPixelRatio;
else
ctx.lineWidth = 5 * canvasPixelRatio;
ctx.beginPath(); // Début du chemin
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(64, 128, 255, 0.5)";
ctx.moveTo(points[0].x, points[0].y); // Le tracé part du point 50,50
for(var i = 0; i < 4; i++)
ctx.lineTo(points[i].x, points[i].y); // Un segment est ajouté vers 200,200
ctx.closePath(); // Fermeture du chemin (facultative)
ctx.stroke();
for(var i = 0; i < 4; i++)
{
var radius = 30 * canvasPixelRatio;
if(idPoint == i)
radius = 60 * canvasPixelRatio;
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI*2, true);
ctx.strokeStyle = "#FF8800";
ctx.fillStyle = "rgba(255, 128, 0, 0.5)";
ctx.fill();
ctx.stroke();
}
if(idPoint != -1)
{
var zoomWidth = canvasWidth / 3;
var zoomHeight = canvasHeight / 3;
var zoomMargin = 5;
var zoomAroundWidth = 50;
var zoomAroundHeight = zoomAroundWidth / ratio;
var positionMouse = points[idPoint];
var imagePositionX = (positionMouse.x - marginImage) / (canvasWidth - marginImage * 2) * image.width;
var imagePositionY = (positionMouse.y - marginImage) / (canvasHeight - marginImage * 2) * image.height;
var zoomX = 0;
var zoomY = 0;
if(imagePositionX < image.width / 2)
zoomX = canvasWidth - zoomWidth;
if(imagePositionY < image.height / 2)
zoomY = canvasHeight - zoomHeight;
ctx.fillStyle = "#F08";
ctx.fillRect(zoomX, zoomY, zoomWidth, zoomHeight);
ctx.drawImage(image, imagePositionX - zoomAroundWidth, imagePositionY - zoomAroundHeight, zoomAroundWidth * 2, zoomAroundHeight * 2, zoomX + zoomMargin, zoomY + zoomMargin, zoomWidth - zoomMargin * 2, zoomHeight - zoomMargin * 2);
ctx.lineWidth = 3 * canvasPixelRatio;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
ctx.moveTo(zoomX, zoomY + zoomHeight / 2);
ctx.lineTo(zoomX + zoomWidth, zoomY + zoomHeight / 2);
ctx.moveTo(zoomX + zoomWidth / 2, zoomY);
ctx.lineTo(zoomX + zoomWidth / 2, zoomY + zoomHeight);
ctx.closePath();
ctx.stroke();
}
}
function nearPoint(points, x, y)
{
var radiusDetection = 60 * canvasPixelRatio;
var distances = [];
for(i = 0; i < 4; i++) {
var mx = x - points[i].x;
var my = y - points[i].y;
distances[i] = Math.sqrt(mx * mx + my * my);
}
minI = 0;
minD = distances[0];
for(i = 1; i < 4; i++)
{
if(minD > distances[i])
{
minD = distances[i];
minI = i;
}
}
if(minD <= radiusDetection)
return minI;
return -1;
}
function getTouchPosition(e)
{
var target = null;
var mouse = null;
if(e.changedTouches != undefined)
{
var touches = e.changedTouches;
mouse = touches[0];
target = touches[0].target;
}
else if(e.originalTarget != undefined)
{
mouse = e;
target = e.originalTarget;
}
var coordX = 0;
var coordY = 0;
if(mouse.layerX != undefined)
{
coordX = mouse.layerX;
coordY = mouse.layerY;
}
else
{
coordX = mouse.pageX;
coordY = mouse.pageY;
}
var x = coordX - target.offsetLeft;
var y = coordY - target.offsetTop;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x >= canvasWidth) x = canvasWidth - 1;
if(y >= canvasHeight) y = canvasHeight - 1;
return {'x':x, 'y':y};
}
function mouseDown(e)
{
var position = getTouchPosition(e);
idPoint = nearPoint(points, position.x, position.y);
if(idPoint == -1)
{
if(position.x < marginImage * 3 && position.y < marginImage * 3)
{
computeMat();
}
}
}
function mouseUp(e)
{
if(idPoint != -1)
{
idPoint = -1;
draw(points);
}
}
function mouseMove(e)
{
if(idPoint != -1)
{
var position = getTouchPosition(e);
points[idPoint].x = position.x;
points[idPoint].y = position.y;
draw(points);
}
}
function cancelDefault(e)
{
e.preventDefault();
}
function matStep12(pts)
{
var matP = [
[pts[0].x, pts[1].x, pts[2].x],
[pts[0].y, pts[1].y, pts[2].y],
[1, 1, 1]
];
var vecP = [[pts[3].x], [pts[3].y], [1]];
var matPi = math.inv(matP);
var vecPi = math.multiply(matPi, vecP);
var result = [
[pts[0].x * vecPi[0][0], pts[1].x * vecPi[1][0], pts[2].x * vecPi[2][0]],
[pts[0].y * vecPi[0][0], pts[1].y * vecPi[1][0], pts[2].y * vecPi[2][0]],
[vecPi[0][0], vecPi[1][0], vecPi[2][0]]
];
return result;
}
function distance(a, b)
{
var mx = b.x - a.x;
var my = b.y - a.y;
return Math.sqrt(mx * mx + my * my);
}
function computeMat()
{
var pts = getPointRelativePosition();
var widthT = distance(pts[0], pts[3]);
var widthB = distance(pts[1], pts[2]);
var heightL = distance(pts[0], pts[1]);
var heightR = distance(pts[2], pts[3]);
var maxWidth = (widthT > widthB) ? widthT : widthB;
var maxHeight = (heightL > heightR) ? heightL : heightR;
var imgWidth = Math.round(maxWidth);
var imgHeight = Math.round(maxHeight);
var matA = matStep12(pts);
var matB = matStep12([{x:0,y:0}, {x:0,y:maxHeight}, {x:maxWidth,y:maxHeight}, {x:maxWidth,y:0}]);
var matC = math.multiply(matB, math.inv(matA));
var matCi = math.inv(matC);
console.log('width:' + imgWidth + ', height:' + imgHeight);
printMat(matC);
// construct image with transformation matrice
imageData = ctx.createImageData(imgWidth, imgHeight);
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
tempCtx.drawImage(image, 0, 0, image.width, image.height);
var imageDataSrc = tempCtx.getImageData(0, 0, image.width, image.height);
var mz = [matCi[0][2], matCi[1][2], matCi[2][2]];
for(var y = 0; y < imgHeight; y++)
{
var my = [matCi[0][1] * y, matCi[1][1] * y, matCi[2][1] * y];
var offsetY = y * imgWidth;
for(var x = 0; x < imgWidth; x++)
{
var mx = [matCi[0][0] * x, matCi[1][0] * x, matCi[2][0] * x];
var cx = mx[0] + my[0] + mz[0];
var cy = mx[1] + my[1] + mz[1];
var cz = mx[2] + my[2] + mz[2];
var px = Math.round(cx / cz);
var py = Math.round(cy / cz);
if(px < 0.0 || py < 0.0 || px >= image.width || py >= image.height)
{
imageData.data[pixelIndex] = 0;
imageData.data[pixelIndex + 1] = 255;
imageData.data[pixelIndex + 2] = 0;
imageData.data[pixelIndex + 3] = 255;
}
else
{
var pixelIndex = (offsetY + x) * 4;
var pixelIndexSrc = (py * image.width + px) * 4;
imageData.data[pixelIndex] = imageDataSrc.data[pixelIndexSrc];
imageData.data[pixelIndex + 1] = imageDataSrc.data[pixelIndexSrc + 1];
imageData.data[pixelIndex + 2] = imageDataSrc.data[pixelIndexSrc + 2];
imageData.data[pixelIndex + 3] = 255;
}
}
}
// here to do, image analysis
}
function getPointRelativePosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) * image.width / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) * image.height / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function getPointPosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function printPoint(pts)
{
var result = '';
for(var i = 0; i < 4; i++)
{
result += "{x:" + pts[i].x + ", y:" + pts[i].y + "},\n";
}
console.log(result);
}
function printMat(mat)
{
var result = '';
for(var i = 0; i < mat.length; i++)
{
result += "[";
for(var j = 0; j < mat[i].length; j++)
{
result += mat[i][j] + ", ";
}
result += "],\n";
}
console.log(result);
}
function canvasResize()
{
if(canvasWidth != document.body.clientWidth && canvasHeight != document.body.clientHeight)
{
var transformPoint = getPointPosition();
ratio = image.width / image.height;
canvasWidth = document.body.clientWidth;
canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
canvasLargeur = canvasWidth;
canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
marginImage = Math.round(40 * canvasPixelRatio);
for(i = 0; i < 4; i++)
{
points[i].x = transformPoint[i].x * (canvasWidth - marginImage * 2) + marginImage;
points[i].y = transformPoint[i].y * (canvasHeight - marginImage * 2) + marginImage;
}
draw(points);
}
}
c.addEventListener("mousedown", mouseDown, false);
c.addEventListener("mouseup", mouseUp, false);
c.addEventListener("mousemove", mouseMove, false);
c.addEventListener("touchstart", mouseDown, false);
c.addEventListener("touchend", mouseUp, false);
c.addEventListener("touchmove", mouseMove, false);
document.addEventListener("touchstart", cancelDefault, true);
document.addEventListener("touchend", cancelDefault, true);
document.addEventListener("touchmove", cancelDefault, true);
setInterval(canvasResize, 30);
draw(points);
};
</script>
So I'm trying to have the down arrow make the background of my canvas change. I'm having trouble just getting the button press to work itself.
Also I was told that I would have to have a function redraw all the shapes that are already there at the beginning as well, which I am also stuck on what to change.
Here is a JSFiddle of what I have going so far, any suggestions are appreciated!
https://jsfiddle.net/u8avnky2/
var mainCanvas = document.getElementById("canvas");
var mainContext = mainCanvas.getContext('2d');
//rotate canvas
function buttonClick() {
mainContext.rotate(20*Math.PI/180);
}
//key down event
window.addEventListener('keydown', function(event) {
if (event.keyCode === 40) {
fillBackgroundColor();
}
});
function fillBackgroundColor() {
var colors = ["red", "green", "blue", "orange", "purple", "yellow"];
var color = colors[Math.floor(Math.random()*colors.length)];
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
mainContext.fillStyle = color;
mainContext.fillRect(0, 0, canvas.width, canvas.height);
}
function check() {
mainContext.clearRect(square.x,square.y,square.w,square.h);
}
var circles = new Array();
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
function Circle(radius, speed, width, xPos, yPos) {
this.radius = radius;
this.speed = speed;
this.width = width;
this.xPos = xPos;
this.yPos = yPos;
this.opacity = .1 + Math.random() * .5;
this.counter = 0;
var signHelper = Math.floor(Math.random() * 2);
if (signHelper == 1) {
this.sign = -1;
} else {
this.sign = 1;
}
}
//drawing circle
Circle.prototype.update = function () {
this.counter += this.sign * this.speed;
mainContext.beginPath();
mainContext.arc(this.xPos + Math.cos(this.counter / 100) * this.radius,
this.yPos + Math.sin(this.counter / 100) * this.radius,
this.width,
0,
Math.PI * 2,
false);
mainContext.closePath();
mainContext.fillStyle = 'rgba(255, 255, 51,' + this.opacity + ')';
mainContext.fill();
};
function setupCircles() {
for (var i = 0; i < 25; i++) {
var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2 + Math.random() * 3;
var size = 5 + Math.random() * 100;
var radius = 5 + Math.random() * 100;
var circle = new Circle(radius, speed, size, randomX, randomY);
circles.push(circle);
}
drawAndUpdate();
}
setupCircles();
function drawAndUpdate() {
mainContext.clearRect(0, 0, 1000, 1000);
for (var i = 0; i < circles.length; i++) {
var myCircle = circles[i];
myCircle.update();
}
requestAnimationFrame(drawAndUpdate);
}
jsFiddle : https://jsfiddle.net/CanvasCode/u8avnky2/1/
I added a variable known as color globally, so the fillBackgroundColor can access that instead.
var color = "white";
Then in your drawAndUpdate function we just do a fillRect with the color variable using the canvas width and height and it works.
function drawAndUpdate() {
mainContext.fillStyle = color;
mainContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
for (var i = 0; i < circles.length; i++) {
var myCircle = circles[i];
myCircle.update();
}
requestAnimationFrame(drawAndUpdate);
}
I have a canvas and a load an image on it. I want to make this image draggable. My code so far is:
$(document).ready(function() {
loadImagetoEdit();
});
function loadImagetoEdit() {
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
imageObj = new Image();
imageObj.onload = function(){
ctx.drawImage(this, 0, 0, 100, 100);
};
imageObj.src = 'myImage.png';
}
i found this tutorial http://html5.litten.com/how-to-drag-and-drop-on-an-html5-canvas/
and also this one http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-an-image-tutorial/ but I couldn't successfully apply them in my case. You someone provide me with the easiest solution?
Thanks is advance
Use rescaling, mouse event handlers, and offsets. For example:
var canvas;
var ctx;
var status;
var isMouseDown = false;
// Canvas Dimensions
var cw = 0;
var ch = 0;
// Puck Dimensions (and Half thereof)
var pw = 30;
var ph = 30;
var pw2 = 0;
var ph2 = 0;
// Puck Position
var px = 50;
var py = 100;
// cursor offset relative to the puck
var cx = 0;
var cy = 0;
// Attr:Actual dimensions Scale
var sx = 1.0;
var sy = 1.0;
function Init()
{
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
status = document.getElementById("status");
cw = canvas.width;
ch = canvas.height;
// get the scale based on actual width;
sx = cw / canvas.offsetWidth;
sy = ch / canvas.offsetHeight;
// Rescale the puck
pw = pw * sx;
ph = ph * sy;
pw2 = pw/2;
ph2 = ph/2;
// Rescale the puck position
px = px * sx;
py = py * sy;
status.innerHTML = "Attr:" + canvas.width + "," + canvas.height
+ "; Actual:" + canvas.offsetWidth + "," + canvas.offsetHeight
+ "; Scale:" + sx + "," + sy
+ "; Puck:" + pw + "," + ph;
canvas.onmouseup = MouseUp;
canvas.onmousedown = MouseDown;
canvas.onmousemove = MouseMoved;
return setInterval(Repaint, 10); // repaint the canvas at intervals
}
function Repaint()
{
// Clear the canvas
ctx.clearRect(0, 0, cw, ch);
// Draw the background
DrawRect(0, 0, cw, ch, "rgb(220,220,190)");
// Draw the puck
DrawRect(px, py, pw, ph, "blue");
}
function DrawRect(x, y, w, h, colour)
{
ctx.fillStyle = colour;
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.closePath();
ctx.fill();
}
function MouseMoved(e)
{
status.innerHTML = "Cursor[" + e.pageX + ", " + e.pageY + "], Offset["
+ (e.pageX - canvas.offsetLeft) + ", " + (e.pageY - canvas.offsetTop) + "]";
if ( IsCursorOverPuck(e.pageX, e.pageY) )
{
document.body.style.cursor = 'pointer';
}
else
{
document.body.style.cursor = 'default';
}
if (isMouseDown)
{
px = (e.pageX - canvas.offsetLeft)*sx - cx*sx;
py = (e.pageY - canvas.offsetTop)*sy - cy*sy;
status.innerHTML = "mouse down. Offset[" + cx + ", " + cy + "], Puck[" + px + ", " + py + "]";
}
}
function MouseUp()
{
isMouseDown = false;
}
function MouseDown(e)
{
if ( IsCursorOverPuck(e.pageX, e.pageY) )
{
cx = (e.pageX - canvas.offsetLeft)*sx - px;
cy = (e.pageY - canvas.offsetTop)*sy - py;
isMouseDown = true;
}
}
function IsCursorOverPuck(x, y)
{
status.innerHTML = "Cursor[" + x + ", " + y + "], CanvasOffset["
+ (x - canvas.offsetLeft) + ", " + (y - canvas.offsetTop) + "], Puck["
+ px + ", " + py + "]";
return (x - canvas.offsetLeft) * sx > px && (x - canvas.offsetLeft) * sx < px + pw
&& (y - canvas.offsetTop) * sy > py && (y - canvas.offsetTop) * sy < py + ph;
}
Init();
<canvas id="canvas" width="400" height="300"></canvas>
<div id="status"></div>
References
Drag-n-Drop with HTML Canvas – GkSpk