I'm trying to render a box with the rotating point in the bottom. Normaly, we can render a box with the property hasRotatingPoint: true and a handle appear in the top side of the box to the user rotate it.
What I want is to draw it in the bottom side of the box.
This could be achieved by set angle: 180 and the rotating point will appear in the bottom side (as we rotate the box 180°) but I got a draw problem with this approach: the drawing process also rotated 180° and now my box goes in the opposite direction when I'm drawing.
Thanks in advance for any help!
Edit 1
There is a very similar question about this problem here How to change Rotating point position to bottom in FabricJS? but the solution accepted creates another bug in my context as described in the third paragraph.
You need to rewrite 3 functions. fabric.Object.prototype.calcCoords, fabric.Object.prototype.drawControls, fabric.Object.prototype.drawBorders
Those 3 are almost the same, the only difference is that the mtr point is calculated different.
fabric.Object.prototype.calcCoords= function(absolute) {
var rotateMatrix = this._calcRotateMatrix(),
translateMatrix = this._calcTranslateMatrix(),
startMatrix = fabric.util.multiplyTransformMatrices(translateMatrix, rotateMatrix),
vpt = this.getViewportTransform(),
finalMatrix = absolute ? startMatrix : fabric.util.multiplyTransformMatrices(vpt, startMatrix),
dim = this._getTransformedDimensions(),
w = dim.x / 2, h = dim.y / 2,
tl = fabric.util.transformPoint({ x: -w, y: -h }, finalMatrix),
tr = fabric.util.transformPoint({ x: w, y: -h }, finalMatrix),
bl = fabric.util.transformPoint({ x: -w, y: h }, finalMatrix),
br = fabric.util.transformPoint({ x: w, y: h }, finalMatrix);
if (!absolute) {
var padding = this.padding, angle = fabric.util.degreesToRadians(this.angle),
cos = fabric.util.cos(angle), sin = fabric.util.sin(angle),
cosP = cos * padding, sinP = sin * padding, cosPSinP = cosP + sinP,
cosPMinusSinP = cosP - sinP;
if (padding) {
tl.x -= cosPMinusSinP;
tl.y -= cosPSinP;
tr.x += cosPSinP;
tr.y -= cosPMinusSinP;
bl.x -= cosPSinP;
bl.y += cosPMinusSinP;
br.x += cosPMinusSinP;
br.y += cosPSinP;
}
var ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2),
mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2),
mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2),
mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2),
mtr = new fabric.Point(mb.x - sin * this.rotatingPointOffset, mb.y + cos * this.rotatingPointOffset);
}
// if (!absolute) {
// var canvas = this.canvas;
// setTimeout(function() {
// canvas.contextTop.clearRect(0, 0, 700, 700);
// canvas.contextTop.fillStyle = 'green';
// canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
// canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
// canvas.contextTop.fillRect(br.x, br.y, 3, 3);
// canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
// canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
// canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
// canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
// canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
// canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
// }, 50);
// }
var coords = {
// corners
tl: tl, tr: tr, br: br, bl: bl,
};
if (!absolute) {
// middle
coords.ml = ml;
coords.mt = mt;
coords.mr = mr;
coords.mb = mb;
// rotating point
coords.mtr = mtr;
}
return coords;
}
fabric.Object.prototype.drawControls=function(ctx, styleOverride) {
styleOverride = styleOverride || {};
var wh = this._calculateCurrentDimensions(),
width = wh.x,
height = wh.y,
scaleOffset = styleOverride.cornerSize || this.cornerSize,
left = -(width + scaleOffset) / 2,
top = -(height + scaleOffset) / 2,
transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ?
styleOverride.transparentCorners : this.transparentCorners,
hasRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ?
styleOverride.hasRotatingPoint : this.hasRotatingPoint,
methodName = transparentCorners ? 'stroke' : 'fill';
ctx.save();
ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor;
if (!this.transparentCorners) {
ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor;
}
this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray, null);
// top-left
this._drawControl('tl', ctx, methodName,
left,
top, styleOverride);
// top-right
this._drawControl('tr', ctx, methodName,
left + width,
top, styleOverride);
// bottom-left
this._drawControl('bl', ctx, methodName,
left,
top + height, styleOverride);
// bottom-right
this._drawControl('br', ctx, methodName,
left + width,
top + height, styleOverride);
if (!this.get('lockUniScaling')) {
// middle-top
this._drawControl('mt', ctx, methodName,
left + width / 2,
top, styleOverride);
// middle-bottom
this._drawControl('mb', ctx, methodName,
left + width / 2,
top + height, styleOverride);
// middle-right
this._drawControl('mr', ctx, methodName,
left + width,
top + height / 2, styleOverride);
// middle-left
this._drawControl('ml', ctx, methodName,
left,
top + height / 2, styleOverride);
}
// middle-top-rotate
if (hasRotatingPoint) {
this._drawControl('mtr', ctx, methodName,
left + width / 2,
top + height + this.rotatingPointOffset, styleOverride);
}
ctx.restore();
return this;
}
fabric.Object.prototype.drawBorders=function(ctx, styleOverride) {
styleOverride = styleOverride || {};
var wh = this._calculateCurrentDimensions(),
strokeWidth = 1 / this.borderScaleFactor,
width = wh.x + strokeWidth,
height = wh.y + strokeWidth,
drawRotatingPoint = typeof styleOverride.hasRotatingPoint !== 'undefined' ?
styleOverride.hasRotatingPoint : this.hasRotatingPoint,
hasControls = typeof styleOverride.hasControls !== 'undefined' ?
styleOverride.hasControls : this.hasControls,
rotatingPointOffset = typeof styleOverride.rotatingPointOffset !== 'undefined' ?
styleOverride.rotatingPointOffset : this.rotatingPointOffset;
ctx.save();
ctx.strokeStyle = styleOverride.borderColor || this.borderColor;
this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray, null);
ctx.strokeRect(
-width / 2,
-height / 2,
width,
height
);
if (drawRotatingPoint && this.isControlVisible('mtr') && hasControls) {
var rotateHeight = height / 2;
ctx.beginPath();
ctx.moveTo(0, rotateHeight);
ctx.lineTo(0, rotateHeight + rotatingPointOffset);
ctx.stroke();
}
ctx.restore();
return this;
}
body {
background-color: ivory;
padding:20px;
}
#canvas {
border:1px solid red;
}
<canvas id="canvas" width="666" height="444"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.js"></script>
<script>
var obj = new fabric.Triangle({
fill: 'lime',
top: 110,
left: 110,
borderColor: 'red',
cornerColor: 'cyan',
cornerSize: 9,
transparentCorners: false
});
var canvas = new fabric.Canvas('canvas');
canvas.add(obj);
</script>
Related
I want to create multiple line graphs with their own datasets into one p5.js sketch.
I created one line graph and do not know how to make another line graph to work with a different dataset.
https://editor.p5js.org/ariel.koh/sketches/VsRk3KxYp
I tried to create another preload table for my second graph but it did not work so I am still confused.
This was what I tried but I think it is not right, I never tried working with multiple datasets before so I am confused :(
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canonattacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv",
"header");
}
You're on the right track loading the data.
The p5.js code you posted is a bit convoluted, here only a few examples:
the year labels a manually drawn, for each chart (even though the data is contained in the csv files)
if the text is simply drawn for the years but separate style, coordinate transformations, etc. are used then the push()/pop() call don't add anything.(e.g push();let sbm = 'Saul Bass Movies';...)
the drawLineGraph() function is defined twice (which might get you into variable shadowing bugs) and might be accidentally called within itself
No wonder you're confused.
I'd simplify as much as possible:
get back to loading and displaying one graph
load the second dataset (as you already correctly do above)
display the second graph
For example:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Burns", 30, 20, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: burnsdataset,
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Canon Attacks", 30, 250, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// we will calcualte the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
With that working you can focus on making the code nicer.
Taking the idea of "don't repeat yourself(DRY)" you can notice a few patterns that could be improved:
you try to not just draw the graph, but also also display the graph's
label: perhaps the drawLineGraph() could include that
you seem to manually be drawing every single year label, even though the data is already in the dataset and the x position is already calculated to display the points of the graph
Tackling point 1 is fairly straight foward: simply move the label drawing instrunctions inside the function, handle the extra argument to get the label text and adjust coordinates so they're relative to the graph.
Here's a modified version of the above integrating label as part of the drawLineGraph() function:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y + h + marginTopBottom * 0.5, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
And here's a modified version of the above that renders the labels on the x axis (year):
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canon attacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
drawPlots();
}
function drawPlots() {
background(184, 37, 0);
// burnsdataset linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
xColumn: 0,
yColumn: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
// canonaattacksdataset linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
xColumn: 0,
yColumn: 1,
x: 30,
y: 300,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.yColumn);
let minValue = min(values);
let maxValue = max(values);
let labels = theProps.dataset.getColumn(theProps.xColumn);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// y position for labels (e.g. years)
let labelsY = marginTopBottom * 0.5;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y - marginTopBottom, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
// label per data point
push();
noStroke();
fill(255);
textSize(9);
textAlign(CENTER);
text(labels[i], xx[i], labelsY);
pop();
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
// if you want to use the SVG export
// option, go to setup and enable SVG mode
function keyPressed() {
if (key === "s") {
saveSVG("line-graph.svg");
}
}
There's one slight detail snuck in there: I've renamed draw() to drawPlots() and called it once from setup(). That is to render the plots a single time since they don't change and save CPU resources. If you do plan to have some sort interation changing these graphs then you should use draw().
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'm using THREE.js and Aframe ( in Exokit ) together and I have a component for a "selfie camera". I have a weird issue that when i enter VR the camera rotation is taken over by the head rotation. I understand how the camera rotation works has changed in recent versions of THREE.js ( ArrayCamera ) but I assumed that only affected the main camera and not all cameras in the scene.
Below is my hacky component that works fine in 2D mode but in VR it messes up. The worst thing about it is im fine with it being linked to the head, the camera itself is a child object of the main camera anyway so it appears in front of the users face when opened and is moved with the head rotation - but its off angle when in VR like its pointing down and to the left a bit.
Here are some screenshots that hopefully demonstrate the issue:
Edit: need 10 rep to post images so here are urls instead
2D Mode
VR Mode
Any help much appreciated!!
AFRAME.registerComponent('selfie-camera', {
schema:{
resolution:{type:'int',default:512},
fov:{type:'int',default:100},
aspect:{type:'number',default:1.5},
near:{type:'number',default:0.001},
far:{type:'number',default:1000}
},
init() {
this.el.addEventListener('loaded',()=>{
this.renderTarget = new THREE.WebGLRenderTarget(this.data.resolution*1.5, this.data.resolution,{ antialias: true });
this.el.getObject3D('mesh').material.map = this.renderTarget.texture;
this.cameraContainer = new THREE.Object3D();
this.el.object3D.add( this.cameraContainer );
this.el.takePicture = this.takePicture.bind(this);
this.el.setSide = this.setSide.bind(this);
this.wider = 1.5;
this.photoMultiplier = 2;
this.canvas = document.createElement('canvas');
});
this.testQuat = new THREE.Quaternion();
this.el.open = this.open.bind(this);
this.el.close = this.close.bind(this);
},
open(){
this.camera = new THREE.PerspectiveCamera( this.data.fov, this.data.aspect, this.data.near, this.data.far );
this.cameraContainer.add(this.camera);
new TWEEN.Tween(this.el.getAttribute('scale'))
.to(new THREE.Vector3(1,1,1), 650)
.easing(TWEEN.Easing.Exponential.Out).start();
},
close(){
new TWEEN.Tween(this.el.getAttribute('scale'))
.to(new THREE.Vector3(0.0000001,0.0000001,0.0000001), 200)
.onComplete(()=>{
this.cameraContainer.remove(this.camera);
delete this.camera;
})
.easing(TWEEN.Easing.Exponential.Out).start();
},
tick(){
if(this.camera){
this.camera.getWorldQuaternion(this.testQuat);
console.log(this.camera.quaternion);
}
this.el.getObject3D('mesh').material.visible = false;
if(this.isTakingPicture) {
this.renderTarget.setSize(this.data.resolution * this.wider * this.photoMultiplier, this.data.resolution * this.photoMultiplier);
}
this.el.sceneEl.renderer.render( this.el.sceneEl.object3D, this.camera, this.renderTarget );
if(this.isTakingPicture){
this.isTakingPicture = false;
this.pictureResolve(this.createImageFromTexture());
this.renderTarget.setSize(this.data.resolution * this.wider, this.data.resolution);
}
this.el.getObject3D('mesh').material.visible = true;
},
setSide(isFront){
let _this = this;
new TWEEN.Tween({y:this.cameraContainer.rotation.y})
.to({y:isFront?Math.PI:0}, 350)
.onUpdate(function(){
_this.cameraContainer.rotation.y = this.y;
})
.easing(TWEEN.Easing.Exponential.Out).start();
},
takePicture(){
return new Promise(resolve=>{
this.isTakingPicture = true;
this.pictureResolve = resolve;
})
},
createImageFromTexture() {
let width = this.data.resolution*this.wider*this.photoMultiplier,
height = this.data.resolution*this.photoMultiplier;
let pixels = new Uint8Array(4 * width * height);
this.el.sceneEl.renderer.readRenderTargetPixels(this.renderTarget, 0, 0, width, height, pixels);
pixels = this.flipPixelsVertically(pixels, width, height);
let imageData = new ImageData(new Uint8ClampedArray(pixels), width, height);
this.canvas.width = width;
this.canvas.height = height;
let context = this.canvas.getContext('2d');
context.putImageData(imageData, 0, 0);
return this.canvas.toDataURL('image/jpeg',100);
},
flipPixelsVertically: function (pixels, width, height) {
let flippedPixels = pixels.slice(0);
for (let x = 0; x < width; ++x) {
for (let y = 0; y < height; ++y) {
flippedPixels[x * 4 + y * width * 4] = pixels[x * 4 + (height - y) * width * 4];
flippedPixels[x * 4 + 1 + y * width * 4] = pixels[x * 4 + 1 + (height - y) * width * 4];
flippedPixels[x * 4 + 2 + y * width * 4] = pixels[x * 4 + 2 + (height - y) * width * 4];
flippedPixels[x * 4 + 3 + y * width * 4] = pixels[x * 4 + 3 + (height - y) * width * 4];
}
}
return flippedPixels;
}
});
You have to disable VR before rendering:
var renderer = this.el.sceneEl.renderer;
var vrEnabled = renderer.vr.enabled;
renderer.vr.enabled = false;
renderer.render(this.el.sceneEl.object3D, this.camera, this.renderTarget);
renderer.vr.enabled = vrEnabled;
With the help of TextGeometry, I am drawing text around meshes. PerspectiveCamera is in steady mode, My cube is moving itself in the scene. How can text take a look to the object rotation?
Check this I want to do like this same.
var storeCamera = new THREE.PerspectiveCamera(75, $domElement.innerWidth() / $domElement.innerHeight(), 1, 10000);
dist = (largestSide - 0) / 2 + (largestSide - 0) / (2 * Math.tan(storeCamera.fov * Math.PI / 360));
storeCamera.position.z = dist;
var axisHelperScene = new THREE.Scene();
var axisHelperCamera = new THREE.PerspectiveCamera(75, $domElement.find('.axis-helper').innerWidth() / $domElement.find('.axis-helper').innerHeight(), 1, 10000);
axisHelperCamera.position.z = 350;
var axisHelperRenderer = new THREE.WebGLRenderer({
antialias: true, alpha: true
});
axisHelperRenderer.setSize($domElement.find('.axis-helper').innerWidth(), $domElement.find('.axis-helper').innerHeight());
$domElement.find('.axis-helper').append(axisHelperRenderer.domElement);
var directionX = new THREE.Vector3(1, 0, 0);
var directionY = new THREE.Vector3(0, 0, -1);
var directionZ = new THREE.Vector3(0, 1, 0);
var origin = new THREE.Vector3(0, 0, 0);
var length = 150;
var color = 0x4a4f65;
var headLength = 45;
var headWidth = 30;
// creating axes
var arrowHelperX = new THREE.ArrowHelper(directionX, origin, length, color, headLength, headWidth);
arrowHelperX.line.material.linewidth = 4;
var arrowHelperY = new THREE.ArrowHelper(directionY, origin, length, color, headLength, headWidth);
arrowHelperX.line.material.linewidth = 4;
var arrowHelperZ = new THREE.ArrowHelper(directionZ, origin, length, color, headLength, headWidth);
arrowHelperX.line.material.linewidth = 4;
// origin sphere
var axisSphere = new THREE.Mesh(new THREE.SphereGeometry(15, 40, 40, 40),
new THREE.MeshLambertMaterial({ color: 0x4a4f65 }));
var axisGroup = new THREE.Group();
axisGroup.add(arrowHelperX);
axisGroup.add(arrowHelperY);
axisGroup.add(arrowHelperZ);
axisGroup.add(axisSphere);
axisGroup.rotation.x = 0.60;
axisGroup.rotation.y = -0.50;
// #region Adding axis labels
var fontLoader = new THREE.FontLoader();
var jsonFont = String.format("{0}Scripts/threejs/json/helvetiker_regular.typeface.json", GetResourceFromClient("RootUrl"));
fontLoader.load(jsonFont, function (theFont) {
var xLabel = new THREE.TextGeometry("X", {
font: theFont,
size: 35,
height: 10,
curveSegments: 12,
bevelThickness: 1,
bevelSize: 1,
bevelEnabled: true
});
var yLabel = new THREE.TextGeometry("Y", {
font: theFont,
size: 35,
height: 10,
curveSegments: 12,
bevelThickness: 1,
bevelSize: 1,
bevelEnabled: true
});
var zLabel = new THREE.TextGeometry("Z", {
font: theFont,
size: 35,
height: 10,
curveSegments: 12,
bevelThickness: 1,
bevelSize: 1,
bevelEnabled: true
});
var textMaterial = new THREE.MeshLambertMaterial({
color: 0x4a4f65
});
var xMesh = new THREE.Mesh(xLabel, textMaterial);
xMesh.position.x = 160;
xMesh.position.y = -10;
axisGroup.add(xMesh);
var yMesh = new THREE.Mesh(yLabel, textMaterial);
yMesh.position.x = -10;
yMesh.position.z = -160;
axisGroup.add(yMesh);
var zMesh = new THREE.Mesh(zLabel, textMaterial);
zMesh.position.x = -10;
zMesh.position.y = 160;
axisGroup.add(zMesh);
});
// #endregion Adding axis labels
function render() {
storeRenderer.render(storeScene, storeCamera);
axisHelperRenderer.render(axisHelperScene, axisHelperCamera);
requestAnimFrame(render);
}
Instead of using a TextGeometry for the axis labels a sprite could be an option
var xLabel = makeTextSprite(" X ", { fontsize: 100, borderColor: { r: 0, g: 0, b: 0, a: 1.0 }, backgroundColor: { r: 255, g: 255, b: 255, a: 0.8 } , borderThickness: 5});
xLabel.position.set(150, 0, 0);
axisGroup.add(xLabel);
// creates a THREE.Sprite object with the given text and parameters
function makeTextSprite(theMessage, theParameters) {
if (theParameters === undefined) theParameters = {
};
var fontface = theParameters.hasOwnProperty("fontface") ?
theParameters["fontface"] : "Arial";
var fontsize = theParameters.hasOwnProperty("fontsize") ?
theParameters["fontsize"] : 18;
var borderThickness = theParameters.hasOwnProperty("borderThickness") ?
theParameters["borderThickness"] : 4;
var borderColor = theParameters.hasOwnProperty("borderColor") ?
theParameters["borderColor"] : {
r: 0, g: 0, b: 0, a: 1.0
};
var backgroundColor = theParameters.hasOwnProperty("backgroundColor") ?
theParameters["backgroundColor"] : {
r: 255, g: 255, b: 255, a: 1.0
};
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = "Bold " + fontsize + "px " + fontface;
// get size data (height depends only on font size)
var metrics = context.measureText(theMessage);
var textWidth = metrics.width;
// background color
context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + ","
+ backgroundColor.b + "," + backgroundColor.a + ")";
// border color
context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + ","
+ borderColor.b + "," + borderColor.a + ")";
context.lineWidth = borderThickness;
roundRect(context, borderThickness / 2, borderThickness / 2, textWidth + borderThickness, fontsize * 1.4 + borderThickness, 6);
// 1.4 is extra height factor for text below baseline: g,j,p,q.
// text color
context.fillStyle = "rgba(0, 0, 0, 1.0)";
context.fillText(theMessage, borderThickness, fontsize + borderThickness);
// canvas contents will be used for a texture
var texture = new THREE.Texture(canvas)
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial(
{
map: texture, useScreenCoordinates: false
});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(100, 50, 1.0);
return sprite;
}
// function for drawing rounded rectangles
function roundRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}
I wanted to display the labels just like how it is done here, within chartjs documentation samples.
However, along with one label, I want to display another label beside the first label. Both the labels have different font colors. Something like this:
Is that possible? If yes, kindly let me know how can that be achieved.
Currently I am able to display one label using following code:
Chart.plugins.register({
afterDatasetsDraw: function(chart, easing) {
// To only draw at the end of animation, check for easing === 1
var ctx = chart.ctx;
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function(element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = 'rgb(0, 0, 0)';
var fontSize = 16;
var fontStyle = 'normal';
var fontFamily = 'Helvetica Neue';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
var dataString = dataset.data[index].toString();
// Make sure alignment settings are correct
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var padding = 5;
var position = element.tooltipPosition();
ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
});
}
});
}
});
Yes, that is possible. You can achieve that using the following chart plugin :
Chart.plugins.register({
afterDatasetsDraw: function(chart, ease) {
var barLabels = chart.options.barLabels;
if (!barLabels) return;
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, index) {
var meta = chart.getDatasetMeta(index);
if (!meta.hidden) {
meta.data.forEach(function(segment, index) {
var model = segment._model,
position = segment.tooltipPosition(),
x = position.x,
y = position.y,
height = model.height,
padding = height / 4;
ctx.save();
ctx.textBaseline = 'middle';
ctx.font = 'bold ' + height / 2 + 'px Arial';
ctx.fillStyle = '#777'; //first label's font color
var text1 = barLabels.first[index],
text2 = barLabels.second[index],
textWidth = ctx.measureText(text1).width + padding;
ctx.fillText(text1, x + padding, y);
ctx.fillStyle = '#000'; //second label's font color
ctx.fillText(text2, x + padding + textWidth, y);
ctx.restore();
});
}
});
}
});
To utilize the plugin, define the following properties in your chart options :
barLabels: {
first: ['0.4M', '1.6M', '0.6M', '0.7M', '1.5M'],
second: ['19.3%', '19.1%', '14.1%', '9.0%', '8.9%']
}
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
Chart.plugins.register({
afterDatasetsDraw: function(chart, ease) {
var barLabels = chart.options.barLabels;
if (!barLabels) return;
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, index) {
var meta = chart.getDatasetMeta(index);
if (!meta.hidden) {
meta.data.forEach(function(segment, index) {
var model = segment._model,
position = segment.tooltipPosition(),
x = position.x,
y = position.y,
height = model.height,
padding = height / 4;
ctx.save();
ctx.textBaseline = 'middle';
ctx.font = 'bold ' + height / 2 + 'px Arial';
ctx.fillStyle = '#777'; //first label's font color
var text1 = barLabels.first[index],
text2 = barLabels.second[index],
textWidth = ctx.measureText(text1).width + padding;
ctx.fillText(text1, x + padding, y);
ctx.fillStyle = '#000'; //second label's font color
ctx.fillText(text2, x + padding + textWidth, y);
ctx.restore();
});
}
});
}
});
var chart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
datasets: [{
label: 'Statistics',
data: [1, 4, 2, 3, 4],
backgroundColor: ['#fd625e', '#01b8aa', '#01b8aa', '#01b8aa', '#fd625e'],
}]
},
options: {
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1,
max: 6
}
}]
},
barLabels: {
first: ['0.4M', '1.6M', '0.6M', '0.7M', '1.5M'],
second: ['19.3%', '19.1%', '14.1%', '9.0%', '8.9%']
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>