How to get bounding box that contains all items in Paper.js? - paperjs

How can we get the bounding Rectangle that contains all items in the project?
Currently I'm calculating them one by one:
calc-bounds = (scope) ->
fit = {}
for layer in scope.project.layers
for item in layer.children
for <[ top left ]>
if item.bounds[..] < fit[..] or not fit[..]?
fit[..] = item.bounds[..]
for <[ bottom right ]>
if item.bounds[..] > fit[..] or not fit[..]?
fit[..] = item.bounds[..]
#console.log "fit bounds: ", fit
top-left = new scope.Point fit.left, fit.top
bottom-right = new scope.Point fit.right, fit.bottom
new scope.Rectangle top-left, bottom-right
Rationale
A "Fit all" function that sets project.center and project.zoom will need this calculation.

You can just unite (merge) all the bounds from all elements.
This will return a Rectangle that tightly fits all the elements, a.k.a a Bounding Box.
Here's a Sketch.
And here's the code:
var items = [
new Path.Circle({
position: new Point(100, 200),
radius: 50,
fillColor: 'blue'
}),
new Path.Circle({
position: new Point(200, 70),
radius: 50,
fillColor: 'purple'
}),
new Path.Circle({
position: new Point(400, 100),
radius: 50,
fillColor: 'magenta'
})
]
// Unite all bounds from all items.
var unitedBounds = items.reduce((bbox, item) => {
return !bbox ? item.bounds : bbox.unite(item.bounds)
}, null)
// Draw the united bounds.
var bbox = new Path.Rectangle(unitedBounds)
bbox.strokeColor = 'black'

You can use the layer bounds or group the element you want to bound instead of doing the reduce trick from #nicholaswmin
Here is a sketch with both solutions
const items = [
new Path.Circle({
position: new Point(100, 200),
radius: 50,
fillColor: 'blue'
}),
new Path.Circle({
position: new Point(200, 70),
radius: 50,
fillColor: 'purple'
}),
new Path.Circle({
position: new Point(400, 100),
radius: 50,
fillColor: 'magenta'
})
]
// Use the layer bounds
const layerBounds = project.activeLayer.bounds
// Group all needed items and use the bounds
const groupBounds = (new Group(items)).bounds
// Draw the bounds.
const bbox = new Path.Rectangle(layerBounds)
const bbox2 = new Path.Rectangle(groupBounds)
bbox.strokeWidth = 3
bbox.strokeColor = 'blue'
bbox2.strokeWidth = 6
bbox2.strokeColor = 'red'
bbox2.dashArray = [10, 10]

Related

Evenly distribute a given number of segments along an existing path

without seeing the codepen it is tricky to explain my situation, but here goes. I'm creating some paths by getting pathData using opentype.js. I am then placing random shapes at the position of the path's segment's points. Because of the nature of a font's paths some paths have far more segments than others for example '1' has way fewer segments thant '0'. I would like to average out the number of segments along each path so that when I add the shapes they look a consistent number of segments. Thanks in advance.
Is it possible to evenly distribute a given number of segments along an existing path?
Here is a link to the Codepen
paper.install(window);
const minMax = (min, max) => {
return Math.floor(Math.random() * max + min);
};
window.onload = () => {
paper.setup("canvas");
let pathData;
const font = opentype.load(
"https://assets.codepen.io/1070/pphatton-ultralight-webfont.woff",
(err, font) => {
if (err) {
console.log(err);
} else {
class Doughnut {
constructor(x, y) {
this.x = x;
this.y = y;
this.shape = new paper.Path.RegularPolygon({
position: [this.x, this.y],
sides: minMax(3, 8),
radius: minMax(6, 12),
fillColor: "black"
});
}
// makeShape(){
// this.shape
// }
}
pathData = font.getPath("100", 0, 600, 600).toSVG();
// const rect = new paper.Path.Rectangle({
// point: [80, 25],
// size: [300, 200],
// fillColor: "black"
// });
const number = new paper.Path(pathData);
number.selected = true;
// number.flatten(10);
const amount = 50;
const length = number.length
const points = [];
const segments = number.segments;
number.fitBounds(paper.view.bounds);
for(let i = 0; i < amount; i++){
const offset = i / amount * length
const point = number.getPointAt(offset)
new Doughnut(point.x, point.y);
}
segments.forEach((seg) => {
points.push(number.getPointAt(seg));
});
points.forEach((point) => {
console.log(point);
new Doughnut(point.x, point.y);
});
number.reduce();
}
}
);
const shapes = [];
class Doughnut {
constructor(x, y) {
this.x = x;
this.y = y;
this.shape = new paper.Path.RegularPolygon({
position: [this.x, this.y],
sides: minMax(3, 8),
radius: minMax(6, 12),
fillColor: "black"
});
}
// makeShape(){
// this.shape
// }
}
// for (let i = 0; i < 10; i++) {
// shapes.push(new Doughnut(minMax(100, 500), minMax(100, 500)));
// }
// console.log(shapes)
// shapes.makeShape()
};
The path.divideAt() method can help you greatly.
What is tricky in your case is that, in order to preserve the path appearance, you can't move the existing segments. So you'll have to find a way to only add segment where it is needed.
Otherwise, here's a simple sketch demonstrating a possible solution. It should get you on the track to find a solution more specific to your use case.
const circle = new Path.Circle({
center: [0, 0],
radius: 75,
selected: true
});
const rectangle = new Path.Rectangle({
from: [0, 0],
to: [200, 100],
selected: true
});
rectangle.position = circle.position + [circle.bounds.width + rectangle.bounds.width, 0];
const cloneAndAddSegments = (item) => {
const clone = item.clone().translate(0, 200);
const length = clone.length;
const step = 20;
const iterations = Math.floor(length / step);
for (let i = 1; i <= iterations; i++) {
const offset = i * step;
clone.divideAt(offset);
}
return clone;
};
const circleClone = cloneAndAddSegments(circle);
const rectangleClone = cloneAndAddSegments(rectangle);
const showSegments = (item) => {
item.segments.forEach(({ point }) => new Path.Circle({
center: point,
radius: 5,
fillColor: 'orange'
}))
}
showSegments(circle);
showSegments(rectangle);
showSegments(circleClone);
showSegments(rectangleClone);
project.activeLayer.fitBounds(view.bounds.scale(0.8));

In LightningChartJS, is there a way to do a drag zoom where the zoom for X and Y is always equal?

My problem is when I drag rectangle zoom for example (gyazo gif), the X axis is wider than the Y. In effect, the X axis is zoomed in more details than the Y and the visual of the graph looks different from the original graph.
https://gyazo.com/749db917465a8037b7c5f21792f572ce
I am looking for a way where if i zoom rectangle drag, the function is similar to zooming via mousewheel where x and y zoom feels equal.
here you will find an example how you can perform custom zoom rect instead of default and keep the ratio of the axes.
// Extract required parts from LightningChartJS.
const {
ColorRGBA,
ColorHEX,
emptyFill,
SolidFill,
SolidLine,
translatePoint,
_point,
lightningChart
} = lcjs;
// Import data-generator from 'xydata'-library.
const {
createProgressiveTraceGenerator
} = xydata
const chart = lightningChart()
.ChartXY()
// Disable default chart interactions with left mouse button.
// .setMouseInteractionRectangleFit(false)
.setMouseInteractionRectangleZoom(false)
.setTitleFillStyle(emptyFill)
// generate data and creating the series
const series = chart.addLineSeries().setStrokeStyle(
new SolidLine({
fillStyle: new SolidFill({ color: ColorHEX('#fff') }),
thickness: 2,
}),
)
// generate data and create series
createProgressiveTraceGenerator()
.setNumberOfPoints(200)
.generate()
.toPromise()
.then((data) => {
return series.add(data)
})
// create zooming rect and dispose it
const rect = chart
.addRectangleSeries()
.add({
x: 0,
y: 0,
width: 0,
height: 0,
})
.setFillStyle(new SolidFill({ color: ColorRGBA(255, 255, 255, 30) }))
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255, 255) }),
}),
)
.dispose()
// om Mouse drag restor rectange and set position and coordinates
chart.onSeriesBackgroundMouseDrag((obj, event, button, startLocation, delta) => {
if (button !== 0) return
const startLocationOnScale = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
series.scale,
)
const curLocationOnScale = translatePoint(chart.engine.clientLocation2Engine(event.x, event.y), chart.engine.scale, series.scale)
const x = Math.abs(series.getBoundaries().min.x) + Math.abs(series.getBoundaries().max.x)
const y = Math.abs(series.getBoundaries().min.y) + Math.abs(series.getBoundaries().max.y)
const ratio = x / y
const width = Math.abs(curLocationOnScale.x - startLocationOnScale.x)
const height = Math.abs(curLocationOnScale.y - startLocationOnScale.y)
const heightDirection = curLocationOnScale.y - startLocationOnScale.y // check direction of rect
// check for mouse direction to prevet fit and zoom conflict
if (curLocationOnScale.x > startLocationOnScale.x) {
rect.setDimensions({
x: startLocationOnScale.x,
y: startLocationOnScale.y,
width: width > height * ratio ? width : height * ratio,
height: width > height * ratio ? (heightDirection > 0 ? width : -width) / ratio : heightDirection,
}).restore()
} else {
// prevent phantom rectangle if you change zoom to fit during the dragging
rect.setDimensions({
x: 0,
y: 0,
width: 0,
height: 0,
}).dispose()
}
})
// on mouse drag stop dispose rect and zoom relative to its dimensions
chart.onSeriesBackgroundMouseDragStop((_, event, button, startLocation) => {
if (button !== 0) return
const rectZooom = rect.getDimensionsPositionAndSize()
if (rectZooom.width !== 0) {
chart.getDefaultAxisX().setInterval(rectZooom.x, (rectZooom.x + rectZooom.width), true, true)
if(rectZooom.height > 0){
chart.getDefaultAxisY().setInterval(rectZooom.y, (rectZooom.y + Math.abs(rectZooom.height)), true, true)
}else{
chart.getDefaultAxisY().setInterval((rectZooom.y - Math.abs(rectZooom.height)),rectZooom.y, true, true)
}
}
rect.setDimensions({
x: 0,
y: 0,
width: 0,
height: 0,
}).dispose()
})
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#3.0.0/dist/lcjs.iife.js"></script>

How to define a heavy object in Matter JS

To explain better what I need, think about a tank and a can of soda.
No matter what the speed of that can of soda would be, the tank wouldn't move if it's hit.
But the tank should be able to move when force is applied to it.
Already opened an issue here with more details:
https://github.com/liabru/matter-js/issues/841
And I also made an example here for what I need.
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body;
var engine = Engine.create();
engine.positionIterations = 10;
engine.velocityIterations = 8;
var render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 400,
wireframes: false
}
});
engine.world.gravity.y = 0;
var topWall = Bodies.rectangle(400, 50, 720, 20, { isStatic: true });
var leftWall = Bodies.rectangle(50, 210, 20, 300, { isStatic: true });
var rightWall = Bodies.rectangle(750, 210, 20, 300, { isStatic: true });
var bottomWall = Bodies.rectangle(400, 350, 720, 20, { isStatic: true });
var ball = Bodies.circle(100, 200, 5, {
friction: 0.05,
frictionAir: 0.05,
density: 0.001,
restitution: 0.2
});
var bigBox = Matter.Bodies.rectangle(500, 200, 120, 120, {
inertia: Infinity,
frictionAir: 1,
friction: 1,
density: 0.1
});
World.add(engine.world, [topWall, leftWall, rightWall, bottomWall, ball, bigBox]);
Engine.run(engine);
Render.run(render);
// CMD + SHIFT + 7 to reload
$('.shoot').on('click', function () {
var speed = 0.02;
var angle = 0;
let vector = Matter.Vector.create(Math.cos(angle) * speed, Math.sin(angle) * speed);
Body.applyForce(ball, ball.position, vector);
});
https://codepen.io/dmerlea/pen/BaoQJYK

How do I add a class to a Rect in bonsaijs?

I need to add a class to a Rect. I can't seem to figure out how to do it.
bar = (new Rect(x, ySegment * 10 + 30 + margin, w, 0)
.attr('opacity', 0.8)
.attr('class', data[i].segments[j].color)
.addTo(stage));
the class attr is ignored.
A DisplayObject like Rect isn't the representation of an HTMLElement. That's why custom attributes like "class" don't work. If your intention is to re-use attributes for different DisplayObjects, then try the following:
var myAttrs = {
fillColor: 'red',
opacity: 0.5
};
new Rect(20, 20, 100, 100).attr(myAttrs).addTo(stage);
new Rect(20, 130, 100, 100).attr(myAttrs).addTo(stage);
Play with it here: Orbit

famo.us lightbox demo transition

I'm trying to get a transition that is similar to the lightbox demo that famous has put out. I have a grid layout, when I click a surface in the grid, Id like to have the surface transition, grow in size and be centered in the browser window.
--edit
Here is the demo, what I would like to nail is the flyout of the clicked image from its location to the center of the screen. http://demo.famo.us/lightbox/
I have the following code that I've been using as a basis. http://codepen.io/heyimlance/pen/JooQMX
var Engine = famous.core.Engine;
var Surface = famous.core.Surface;
var GridLayout = famous.views.GridLayout;
var StateModifier = famous.modifiers.StateModifier;
var Transform = famous.core.Transform;
var RenderNode = famous.core.RenderNode;
var Easing = famous.transitions.Easing;
var mainContext = Engine.createContext();
var grid = new GridLayout({
dimensions: [8, 8],
});
var surfaces = [];
grid.sequenceFrom(surfaces);
function newSurface(id) {
var surface = new Surface({
content: id + 1,
properties: {
backgroundColor: "hsl(" + (id * 70 / 64) + ", 60%, 70%)",
lineHeight: '50px',
textAlign: 'center'
}
});
var smod = new StateModifier({
size: [50,50],
transform: Transform.translate(0,0,1),
origin: [.5,.5]
});
var rnode = new RenderNode();
rnode.add(smod).add(surface);
surfaces.push(rnode);
surface.on('click', function() {
console.log(smod)
var zpos = (this.up || this.up == undefined) ? 0 : -180;
if (!zpos) {
this.up = false;
smod.setTransform(Transform.translate(0,0,2000), { curve:Easing.outElastic, duration: 1000 })
gridModifier.setTransform(Transform.translate(0,0,-2000), { curve:Easing.outElastic, duration: 500 })
} else {
this.up = true;
gridModifier.setTransform(Transform.translate(0,0,0), { curve:Easing.outElastic, duration: 400 })
smod.setTransform(Transform.translate(0,0,0), { curve:Easing.outElastic, duration: 1000 })
}
});
}
for(var i = 0; i < 64; i++) {
newSurface(i);
}
var gridModifier = new StateModifier({
size: [400, 400],
align: [.5, .5],
origin: [.5, .5],
transform : Transform.translate(0,0,0),
});
var gridRotate = new StateModifier({
transform : Transform.rotate(0,0,0),
});
mainContext.add(gridModifier).add(grid);
mainContext.setPerspective(1000);
Using your code, I made a few changes to use the Lightbox render contoller at the time of the click. Not sure what transition you would like for the grid and surface, this should give you options to transition as you like.
Here is a codepen of the example
The code:
var Engine = famous.core.Engine;
var Surface = famous.core.Surface;
var GridLayout = famous.views.GridLayout;
var StateModifier = famous.modifiers.StateModifier;
var Transform = famous.core.Transform;
var RenderNode = famous.core.RenderNode;
var RenderController = famous.views.RenderController;
var Lightbox = famous.views.Lightbox;
var Easing = famous.transitions.Easing;
var mainContext = Engine.createContext();
var grid = new GridLayout({
dimensions: [8, 8],
});
var surfaces = [];
var showing;
grid.sequenceFrom(surfaces);
var cmod = new StateModifier({
origin: [0.5, 0.5],
align: [0.5, 0.5]
});
var controller = new Lightbox({
inTransition: true,
outTransition: false,
overlap: true
});
controller.hide();
function newSurface(id) {
var surface = new Surface({
size: [undefined, undefined],
content: id + 1,
properties: {
backgroundColor: "hsl(" + (id * 70 / 64) + ", 60%, 70%)",
lineHeight: '50px',
textAlign: 'center',
cursor: 'pointer'
}
});
surface._smod = new StateModifier({
size: [420,420],
origin: [0.5, 0.5],
align: [0.5, 0.5]
});
surface._rnode = new RenderNode();
surface._rnode.add(surface._smod).add(surface);
surfaces.push(surface);
surface.on('click', function(context, e) {
if (this === showing) {
controller.hide({ curve:Easing.inElastic, duration: 1000 }, function(){
gridModifier.setTransform(Transform.scale(1,1,1),
{ curve:Easing.outElastic, duration: 1000 });
});
showing = null;
} else {
showing = this;
gridModifier.setTransform(Transform.scale(0.001, 0.001, 0.001),
{ curve:Easing.outCurve, duration: 300 });
cmod.setTransform(Transform.translate(0, 0, 0.0001));
controller.show(this._rnode, { curve:Easing.outElastic, duration: 2400 });
}
}.bind(surface, mainContext));
}
for(var i = 0; i < 64; i++) {
newSurface(i);
}
var gridModifier = new StateModifier({
size: [400, 400],
align: [0.5, 0.5],
origin: [0.5, 0.5]
});
mainContext.add(gridModifier).add(grid);
mainContext.add(cmod).add(controller);
mainContext.setPerspective(1000);
I think the best way is to follow "StateModifier" example that you can find in famo.us university : http://famo.us/university/lessons/#/famous-102/transitionables/2
Do a scale :
// animates x- and y-scale to 1
stateModifier.setTransform(
Transform.scale(1, 1, 1),
{ duration : 2000, curve: Easing.outBack }
);
and then a align [0.5, 0.5] :
var alignModifier = new Modifier({
align: [0.5, 0.5]
});
and if you want background to be minified, you have to apply 'scale' modifier too to make all other surfaces smaller.

Resources