suppose svg is the parent element and rect will be the child element in the final rendering.
i want to render it on different changing states
for every 5 rects there will be a single svg parent,so if rect value is 8 ,it would render it as first svg with 5 child elements and second svg with remaining 3 elements
const Svg = ({total}) => {
let rects = total
let totalSvgs = Math.ceil(rects/5)
return(
// i have tried using map and for-loops here
)
}
Summary Divide your Rects into an array of rectChunks ... which would make map straight forward...
const rects = []; /** your rect objs */
const svgs = []; /** your svg objs */
const chunkSize = 5;
const rectLength = rects.length;
const svgLength = Math.ceil(rectLength / chunkSize);
/** 1. Divide your array of rects into an array of small chunks of size 5 */
const rectChunks = [];
for (let i = 0; i < rectLength; i += chunkSize) {
const chunk = rects.slice(i, i + chunkSize);
rectChunks.push(chunk);
}
/** 2. Render */
return svgs.map((svgItem, index) => (
<Svg {...svgItem}>
{rectChunks[index].map(rectItem => (
<Rect {...rectItem} />
))}
</Svg>
));
Related
I'm trying to optimize the layer order of paths in Illustrator so that when sent to a laser cutter, the end of one path is close to the start of the next path reducing the travel time of the laser between each cut.
I've come up with the following code, which works, but could be further optimized considering length of lines, or through an annealing process. I'm posting it here in case anyone else is Googling 'Laser cutting optimization' and doesn't want to write their own code. Also if anyone can suggest improvements to the below code, I'd love to hear them.
// For this script to work, all paths to be optimised need to be on layer 0.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from layer 0 to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.
function test() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
function Optimize(items) {
var lastPath, closest, minDist, delIndex, curItem, tempItems = [];
var topLayer = app.activeDocument.layers[0];
var newLayer = app.activeDocument.layers[1];
for (var x = 1, len = items.length; x < len; x++) {
tempItems.push(items[x]);
}
lastPath = items[0];
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING);
while (tempItems.length) {
closest = tempItems[0];
minDist = endToStartDistance(lastPath, closest);
delIndex = 0;
for (var y = 1, len = tempItems.length; y < len; y++) {
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist) {
closest = curItem;
minDist = endToStartDistance(lastPath, closest);
delIndex = y;
}
}
$.writeln(minDist);
//closest.zOrder(ZOrderMethod.BRINGTOFRONT);
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING);
lastPath = closest;
tempItems.splice(delIndex, 1);
}
}
var allPaths = [];
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
//$.writeln(documents[0].pathItems[i].pathPoints[0].anchor[0])
}
Optimize(allPaths);
}
test();
Version 2 of the above code, some changes include the ability to reverse paths if this results in a reduced distance for the cutting head to move between paths, and added comments to make the code easier to read.
// Create a new empty layer in position 1 in the layer heirarchy.
// Run the script, all paths will move from their current layer to layer 1 in an optimized order.
// Further optimisation possible with 'Annealing', but this will be a good first run optimization.
// Load into Visual Studio Code, follow steps on this website
// https://medium.com/#jtnimoy/illustrator-scripting-in-visual-studio-code-cdcf4b97365d
// to get setup, then run code when linked to Illustrator.aa
function main() {
if (!app.documents.length) {
alert("You must have a document open.");
return;
}
var docRef = app.activeDocument;
// The below function gets the distance between the end of the endPath vector object
// and the start of the startPath vector object.
function endToStartDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[0].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function gets the distance between the end of the endPath vector object
// and the end of the startPath vector object.
function endToEndDistance(endPath, startPath) {
var endPoint = endPath.pathPoints[endPath.pathPoints.length - 1].anchor;
var startPoint = startPath.pathPoints[startPath.pathPoints.length - 1].anchor;
var dx = (endPoint[0] - startPoint[0]);
var dy = (endPoint[1] - startPoint[1]);
var dist = Math.pow((Math.pow(dx, 2) + Math.pow(dy, 2)), 0.5);
return dist;
}
// The below function iterates over the supplied list of tempItems (path objects) and checks the distance between
// the end of path objects and the start/end of all other path objects, ordering the objects in the layer heirarchy
// so that there is the shortest distance between the end of one path and the start of the next.
// The function can reverse the direciton of a path if this results in a smaller distance to the next object.
function Optimize(tempItems) {
var lastPath, closest, minDist, delIndex, curItem;
var newLayer = app.activeDocument.layers[1]; // There needs to be an empty layer in position 2 in the layer heirarchy
// This is where the path objects are moved as they are sorted.
lastPath = tempItems[0]; // Arbitrarily take the first item in the list of supplied items
tempItems.splice(0, 1); // Remove the first item from the list of items to be iterated over
lastPath.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the first item to the first position in the new layer
while (tempItems.length) { // Loop over all supplied items while the length of this array is not 0.
// Items are removed from the list once sorted.
closest = tempItems[0]; // Start by checking the distance to the first item in the list
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// Find the smallest of the distances between the end of the previous path item
// and the start / end of this next item.
delIndex = 0; // The delIndex is the index to be removed from the tempItems list after iterating through
// the entire list.
for (var y = 1, len = tempItems.length; y < len; y++) {
// Iterate over all items in the list, starting at item 1 (item 0 already being used above)
curItem = tempItems[y];
if (endToStartDistance(lastPath, curItem) < minDist || endToEndDistance(lastPath, curItem) < minDist) {
// If either the end / start distance to the current item is smaller than the previously
// measured minDistance, then the current path item becomes the new smallest entry
closest = curItem;
minDist = Math.min(endToStartDistance(lastPath, closest), endToEndDistance(lastPath, closest));
// The new minDistace is set
delIndex = y; // And the item is marked for removal from the list at the end of the loop.
}
}
if (endToEndDistance(lastPath, closest) < endToStartDistance(lastPath, closest)) {
reversePaths(closest); // If the smallest distance is yielded from the end of the previous path
// To the end of the next path, reverse the next path so that the
// end-to-start distance between paths is minimised.
}
closest.move(newLayer, ElementPlacement.PLACEATBEGINNING); // Move the closest path item to the beginning of the new layer
lastPath = closest; // The moved path item becomes the next item in the chain, and is stored as the previous item
// (lastPath) for when the loop iterates again.
tempItems.splice(delIndex, 1); // Remove the item identified as closest in the previous loop from the list of
// items to iterate over. When there are no items left in the list
// The loop ends.
}
}
function reversePaths(theItems) { // This code taken / adapted from https://gist.github.com/Grsmto/bfe1541957a0bb17972d
if (theItems.typename == "PathItem" && !theItems.locked && !theItems.parent.locked && !theItems.layer.locked) {
pathLen = theItems.pathPoints.length;
for (k = 0; k < pathLen / 2; k++) {
h = pathLen - k - 1;
HintenAnchor = theItems.pathPoints[h].anchor;
HintenLeft = theItems.pathPoints[h].leftDirection;
HintenType = theItems.pathPoints[h].pointType;
HintenRight = theItems.pathPoints[h].rightDirection;
theItems.pathPoints[h].anchor = theItems.pathPoints[k].anchor;
theItems.pathPoints[h].leftDirection = theItems.pathPoints[k].rightDirection;
theItems.pathPoints[h].pointType = theItems.pathPoints[k].pointType;
theItems.pathPoints[h].rightDirection = theItems.pathPoints[k].leftDirection;
theItems.pathPoints[k].anchor = HintenAnchor;
theItems.pathPoints[k].leftDirection = HintenRight;
theItems.pathPoints[k].pointType = HintenType;
theItems.pathPoints[k].rightDirection = HintenLeft;
}
}
}
var allPaths = []; // Grab every line in the document
for (var i = 0; i < documents[0].pathItems.length; i++) {
allPaths.push(documents[0].pathItems[i]);
// This could be better changed to the selected objects, or to filter only objects below a certain
// stroke weight so that raster paths are not affected, but cut paths are.
}
Optimize(allPaths); // Feed all paths in the document into the optimize function.
}
main(); // Call the main function, executing the above code.
I have 3 img tag in my html file. I want to change all of the 3 img's src with button click from an array where I stored 9 images src link.
So when the page loads for the very first time it will show first 3 images[0,1,2]. Then on each next-btn click it will change images to the next 3 [3,4,5], [6,7,8]. And when the prev-btn is clicked it will go back to 3 images.
please help me with that. and please suggest me is there any other way(better) around it?
Here is what I've tried so far:
const $img1 = $("#img1")
const $img2 = $("#img2")
const $img3 = $("#img3")
const $nextBtn = $("#next-btn")
const $prevBtn = $("#prev-btn")
$.get("https://someApiLink.com/all.json", function (characters) {
const charactersList = []
for (let i=0; i<characters.length; i++) {
// here casts is another array to match predefined characters with the
// character from the api
if (casts.includes(characters[i].name)) {
charactersList.push(characters[i])
}
}
$img1.attr("src", charactersList[0].image)
$img2.attr("src", charactersList[1].image)
$img3.attr("src", charactersList[2].image)
// it will only switch back and forth 2 images on the first img tag
$nextBtn.on("click", function () {
const src = ($img1.attr("src") === charactersList[0].image)
? charactersList[1].image
: charactersList[0].image;
$img1.attr("src", src)
})
})
You'll need two things:
A function that sets images based on an index.
An index to keep track on where to in charactersList to start counting from.
With the function you can render you images based on a starting point with +1 and +2 index. So let's say you start at 2, then 3 and 4 will also be rendered.
The nextBtn should increment the index with +3 so that the next 3 images will be rendered.
The prevBtn should decrement the index with -3 so that the previous 3 images will be rendered.
const $img1 = $("#img1")
const $img2 = $("#img2")
const $img3 = $("#img3")
const $nextBtn = $("#next-btn")
const $prevBtn = $("#prev-btn")
$.get("https://someApiLink.com/all.json", function (characters) {
const charactersList = []
for (let i=0; i<characters.length; i++) {
if (casts.includes(characters[i].name)) {
charactersList.push(characters[i])
}
}
// Index to start counting from.
let characterIndex = 0;
/**
* Sets three images based in the given index.
* Indexes will be counted upwards starting with the index.
* #param {number} index
*/
const setThreeImageFromIndex = index => {
$img1.attr("src", charactersList[index].image)
$img2.attr("src", charactersList[index + 1].image)
$img3.attr("src", charactersList[index + 2].image)
});
// Set images for the first time.
setThreeImageFromIndex(characterIndex);
// Go to next 3.
$nextBtn.on("click", function () {
// Don't go over the length of the characterList.
if (characterIndex + 3 < charactersList.length) {
characterIndex += 3;
setThreeImageFromIndex(characterIndex);
}
});
// Go to previous 3.
$prevBtn.on("click", function () {
// Don't go below 0.
if (characterIndex - 3 >= 0) {
characterIndex -= 3;
setThreeImageFromIndex(characterIndex);
}
});
})
You can store a sort of "page number" for example page to keep track of what page are they on and increase/decrease it on each "press".
let page = 0;
const imgInSet = 3;
const reloadImages = ()=>{
const imgSet = Math.abs(page); // we get abs if someone do more previous than nexts
$img1.attr("src", characterList[imgSet*imgInSet].image); // get first image
$img2.attr("src", characterList[imgSet*imgInSet+1].image); // get second image
$img3.attr("src", characterList[imgSet*imgInSet+2].image); // get third image
}
$nextBtn.on("click", ()=>reloadImages(++page)); // do reloadImages after adding 1 to roll
$prevBtn.on("click", ()=>reloadImages(--page);) // do reloadImages after removing 1 from roll
You could add a numberOfPages value to restrict the number of available pages like that:
let page = 0;
const imgInSet = 3;
const numberOfPages = Math.floor(characterList.length/3);
const reloadImages = ()=>{
const imgSet = Math.abs(page)%numberOfPages; // we get abs if someone do more previous than nexts
$img1.attr("src", characterList[imgSet*imgInSet].image); // get first image
$img2.attr("src", characterList[imgSet*imgInSet+1].image); // get second image
$img3.attr("src", characterList[imgSet*imgInSet+2].image); // get third image
}
Those two swap previous/next buttons when they get to the negative numbers by doing it like that:
let page = 0;
const imgInSet = 3;
const numberOfPages = Math.floor(characterList.length/3);
const reloadImages = ()=>{
const imgSet = page<0?numberOfPages+page%imgInSet:page)%numOfPages;
$img1.attr("src", characterList[imgSet*imgInSet].image);
$img2.attr("src", characterList[imgSet*imgInSet+1].image);
$img3.attr("src", characterList[imgSet*imgInSet+2].image);
}
$nextBtn.on("click", ()=>reloadImages(++page));
$prevBtn.on("click", ()=>reloadImages(--page);)
This answer is for more "universal solution" where you won't need to change too much with each new img that you would need to be cycled through.
I am having an issue with my merge sort visualizer.
My program has no issues visualizing bubble sort or quick sort, as I can do the swapping operation of css property values in-place, but I am having major issues trying to get merge sort to work properly. The issue arises when I try to update a css property on the dom, it causes the sort to not function.
I have tried passing in copies of the data I wish to sort, and all sorts of weird things I could think of to make it work. I am currently trying to sort by the css property 'maxWidth'. I use that to display how large a div element is in the html file and then visualize the sort from there.
My latest thought has been to set all the div elements to have another css property equal to the maxWidth (I am using fontSize as it does not affect my program) and then sorting based on fontSize, allowing me in theory to change the maxWidth properties of the divs without affecting merge sorts algorithm.
I am including my entire js file as I hope reading my correctly working bubble sort or quick sort functions can help you see what I am trying to achieve. Thank you so much for taking the time to read this and offer any help!
Important Note: I am not trying to visualize the individual steps of merge sort yet because I am unable to update the final result to the html page without affecting the merge sort algorithm. According to console logs, my merge sort algorithm does indeed work, I just can't update the DOM without messing it up. Once I can do that, I will turn it into an asynchronous function using async and await like I previously did with bubble and quick sort.
/********* Generate and Store Divs to be Sorted *************/
const generateSortingDivs = (numOfDivs) => {
const divContainer = document.querySelector('.div-container');
let html = '';
for (let i = 0; i < numOfDivs; i++) {
let r = Math.floor(Math.random() * 100);
html += `<div class='sorting-div' id='id-${i}' style='max-width: ${r}%'> </div>`;
}
divContainer.innerHTML = html;
for(let i = 0; i < numOfDivs; i++) {
let x = document.getElementById('id-' + i);
x.style.fontSize = x.style.maxWidth;
}
}
const storeSortingDivs = () => {
const divContainer = document.querySelector('.div-container');
let divCollection = [];
const numOfDivs = divContainer.childElementCount;
for(let i=0; i<numOfDivs; i++) {
let div = document.getElementById('id-' + i);
divCollection.push(div);
}
return divCollection;
}
/********** SLEEP FUNCTION ************/
//Used to allow asynchronous visualizations of synchronous tasks
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/******* SWAP FUNCTIONS *********/
//Used for Testing Algorithm before Animating Visualization
const syncSwap = (div1, div2) => {
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
async function asyncSwap(div1, div2) {
await sleep(50);
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
const swapDivs = (smallerDiv, biggerDiv) => {
return new Promise(resolve => {
setTimeout(() => {
let tmp = smallerDiv.style.maxWidth;
smallerDiv.style.maxWidth = biggerDiv.style.maxWidth;
biggerDiv.style.maxWidth = tmp;
resolve();
}, 50);
});
}
/****************************************/
/*********** SORTING ALGO'S *************/
/****************************************/
/******* BUBBLE SORT ***********/
async function bubbleSort(divCollection) {
displayBubbleSortInfo();
const len = divCollection.length;
for(let i=0; i<len; i++) {
for(let j=0; j<len-i-1; j++) {
divCollection[j].style.backgroundColor = "#FF4949";
divCollection[j+1].style.backgroundColor = "#FF4949";
let numDiv1 = parseInt(divCollection[j].style.maxWidth);
let numDiv2 = parseInt(divCollection[j+1].style.maxWidth);
let div1 = divCollection[j];
let div2 = divCollection[j+1];
if(numDiv1 > numDiv2) {
await swapDivs(div2, div1);
}
divCollection[j].style.backgroundColor = "darkcyan";
divCollection[j+1].style.backgroundColor = "darkcyan";
}
divCollection[len - i - 1].style.backgroundColor = 'black';
}
}
function displayBubbleSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Bubble Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n^2)</h2>`;
html += `<h3>Space Complexity: O(1)</h3>`;
html += `<p>This sorting algorithm loops through the array and continues to push the
largest found element into the last position, also pushing the last available
position down by one on each iteration. It is guaranteed to run in exactly
O(n^2) time because it is a nested loop that runs completely through.</p>`;
infoDiv.innerHTML = html;
}
/****** QUICK SORT ********/
async function quickSort(divCollection, start, end) {
if(start >= end) return;
let partitionIndex = await partition(divCollection, start, end);
await Promise.all([quickSort(divCollection, start, partitionIndex - 1), quickSort(divCollection, partitionIndex + 1, end)]);
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
async function partition(divCollection, start, end) {
let pivotIndex = start;
let pivotValue = parseInt(divCollection[end].style.maxWidth);
for(let i = start; i < end; i++) {
if(parseInt(divCollection[i].style.maxWidth) < pivotValue) {
await asyncSwap(divCollection[i], divCollection[pivotIndex]);
pivotIndex++;
}
}
await asyncSwap(divCollection[pivotIndex], divCollection[end]);
return pivotIndex;
}
function displayQuickSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Quick Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n log n)</h2>`;
html += `<h3>Space Complexity: O(log n)</h3>`;
html += `<p>This sorting algorithm uses the idea of a partition to sort
each iteration recursively. You can implement quick sort
in a variety of manners based on the method in which you
pick your "pivot" value to partition the array. In this
visualization, I implemented the method that chooses the
last element of the array as the pivot value. You could
also choose the first value, the middle value, or the median
value based on the first, middle, and last values.</p>`;
infoDiv.innerHTML = html;
}
/* Merge Sort does not sort in place, and thus we have to be
* clever when implementing it and also editing the css style
* of our divs to show the visualization of how the algorithm
* works. My method is to store a copy of the divs, that way
* I can use one to be sorted by merge sort, and the other to
* change the css style property to show the visualization.
* Unlike Quick Sort and Bubble Sort, we are not swapping
* elements when sorting, instead we are merging entire
* arrays together as the name implies. */
function mergeSort(divCollection) {
if(divCollection.length < 2) return divCollection;
let middleIndex = Math.floor(divCollection.length / 2);
let left = divCollection.slice(0, middleIndex);
let right = divCollection.slice(middleIndex);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let mergedCollection = [];
while(left.length && right.length) {
if(parseInt(left[0].style.fontSize) < parseInt(right[0].style.fontSize || right.length === 0)) {
let el = left.shift();
mergedCollection.push(el);
} else {
let el = right.shift();
mergedCollection.push(el);
}
}
let res = mergedCollection.concat(left.slice().concat(right.slice()));
return res;
}
/***** INITIALIZATION FUNCTION *******/
generateSortingDivs(10);
let divs = storeSortingDivs();
let copyDivs = [...divs];
console.log('Original State: ')
console.log(divs);
//bubbleSort(divs);
//displayQuickSortInfo();
//quickSort(divs, 0, divs.length-1);
let x = mergeSort(copyDivs);
console.log('Sorted: ');
console.log(x);
I want to know how to properly reuse controllers, modules and functions in AngularJS to make a slightly different offset for each of the posts (placeholders for now just showing where the posts will be) in this Plunker.
When later ngRepeat will output, for example, 6 posts, I want each of them to use the same code as in the mousePosition() function, but for each element calculate the x and y coordinates with a new variable to increase the gap accordingly.
I thought the radian offset between posts will work but it is very narrow, so you may ignore that. The question is about how to have this code applied on each repeated element with a different variable, like a counter.
angular.module('mouseMovement', [])
.controller('MouseMovementController', ['$scope', function MouseMovementController($scope) {
numberOfPosts = 1
radiansBetweenPosts = (90 / numberOfPosts) * Math.PI / 180
$scope.mousePosition = function(event) {
mouseXpercent = event.pageX / window.innerWidth * 2
x = Math.cos(mouseXpercent * Math.PI + radiansBetweenPosts) * 50 + 50
y = Math.sin(mouseXpercent * Math.PI + radiansBetweenPosts) * -50 + 50
$scope.position = {
left: x + '%',
top: y + '%'
}
}
}])
EDIT: I just had an idea that perhaps the better idea is to loop through the child elements within the div that contains all the posts?
You are right, a loop is the best way to do it.
Angular doesn't need to be involved in this basic process either.
I wrote up a little generator you are free to use or reference if you like:
// generate element function
function el(n, type) {
let children = []
while (n) {
children.push(document.createElement(type || 'div'))
n--
}
return children
}
function genNodes() {
// for loop generates each child node
for (let child of children) {
let nInContainer = container.childElementCount
// do whatever you need to do to each element
// i.e. apply id, class, or style
child.id = `child_${nInContainer}`
child.style.background = `rgba(100,${100*nInContainer%255},100,1)`
child.style.color = `rgba(${88*nInContainer%255},255,255,1)`
child.style.fontSize = `2px`
child.style.textAlign = `center`
child.innerHTML = `hello I am child ${nInContainer}`
//child.style.paddingLeft = `${(nInContainer/children.length)*window.innerWidth}px`
child.onmouseenter = () => {
child.style.fontSize = `${50+nInContainer}px`
console.log(child.id)
}
child.onmouseleave = () => {
child.style.height = ``
child.style.fontSize = `2px`
}
container.appendChild(child)
}
}
//
//
//
let container = el(1, 'div')[0]
let children = el(101, 'div')
container.style.overflow = `hidden`
//////////
genNodes()
//////////
document.body.appendChild(container)
//grab any element you want after adding to dom
container.children.child_100.style.fontSize = `150px`
you can play with the live code here:
http://codepen.io/blrzzzt/pen/ozGXKK
I need to 'slice' a big path into two smaller paths using a line. For example, you may have the following configuration:
After the operation, I should have two distinct closed paths. I probably should find two intersections and use Path.split function to split rectangle path, but I don't fully understand paper.js API and I am not sure about the best way to do that using exactly paper.js API.
For example, I split the original rectangle by doing the following commands:
var starting_shape = new paper.Path.Rectangle([paper.view.center.x - 200, paper.view.center.y - 200], 400);
starting_shape.strokeColor = "#aaa";
starting_shape.strokeWidth = 2;
starting_shape.fullySelected = true;
var p1 = starting_shape.split(starting_shape.getNearestLocation([paper.view.center.x - 40, paper.view.center.y - 250]));
var p2 = starting_shape.split(starting_shape.getNearestLocation([paper.view.center.x + 50, paper.view.center.y + 250]));
And I get the following:
I tried to do the following:
p1.closed = true;
p2.closed = true;
p1.position.x += 10;
I got the necessary result:
But is there a way to make it more clever?
Yes, you can use path.divide(path2) to perform a division boolean operation. If you clone the project from github, there's a test for all boolean functions in Examples > Scripts > BooleanOperations.html
I don't believe this currently works as you would like with just a line. It seems to be more stable with closed paths.
The splitUsingPath function here can split in two a complex shape using path, even one with a curve.
const rectangle = new Shape.Rectangle(new Point(200, 200), new Size(300, 300)).toPath();
const path = new Path([
new Point(300, 150),
new Segment(new Point(325, 350), new Point(-90, -90), new Point(90, 90)),
new Point(400, 550)
])
rectangle.strokeColor = 'black'
path.strokeColor = 'black'
const splitUsingPath = (target, path) => {
const paths = [path];
const targets = [target];
const originalTarget = target.clone({ insert: false })
const intersections = target.getIntersections(path)
intersections.forEach(location => {
const newTarget = target.splitAt(location)
const isNew = newTarget !== target
if (isNew) targets.push(newTarget)
paths.forEach(path => {
const offset = path.getOffsetOf(location.point)
const pathLocation = path.getLocationAt(offset)
if (pathLocation) {
paths.push(path.splitAt(pathLocation))
}
})
})
const innerPath = paths.find(p =>
originalTarget.contains(p.bounds.center))
paths
.filter(path => path !== innerPath)
.forEach(item => item.remove())
targets.forEach((target, i) => {
const isFirst = i === 0
const innerPathCopy = isFirst ? innerPath : innerPath.clone()
target.join(innerPathCopy, innerPathCopy.length)
target.closed = true
})
return targets
}
const splitPaths = splitUsingPath(rectangle, path)
splitPaths.forEach((path, i) => {
path.position.x += i * -10
})
This is a great answer that I've used many times. I noticed a little room for improvement though. Sometimes the resulting sliced path has segments (those originated from the slicing path) in wrong order. That causes segment handles pointing to the opposite directions than intended and results in path deformation.
I added a check and fix:
...
targets.forEach((target, i) => {
const isFirst = i === 0
const innerPathCopy = isFirst ? innerPath : innerPath.clone()
// THE FIX -------------------------------
// Check if the starting point of the slicing path and the ending point of the target path are at the same point (or very near).
// If so, reverse the slicing path direction and fix the segment handle directions.
if (innerPathCopy.getPointAt(0).isClose(target.getPointAt(target.length), 0.1)) innerPathCopy.reverse()
// THE FIX -------------------------------
target.join(innerPathCopy, innerPathCopy.length)
target.closed = true
...