Skip to content
Permalink
Browse files

refactor: changed layout algorithm into a proper component

and removed exposure of intersect
  • Loading branch information...
ColinEberhardt committed Aug 4, 2016
1 parent 2a7558a commit 43cf907a58ae92891fac41c2e59fe53f542e9130
Showing with 93 additions and 32 deletions.
  1. +0 −1 index.js
  2. +19 −1 site/demo.js
  3. +9 −6 src/annealing.js
  4. +8 −5 src/greedy.js
  5. +57 −19 src/util/layout.js
@@ -4,4 +4,3 @@ export { default as layoutGreedy } from './src/greedy';
export { default as layoutAnnealing } from './src/annealing';
export { default as layoutRemoveOverlaps } from './src/removeOverlaps';
export { default as layoutBoundingBox } from './src/boundingBox';
export { default as layoutIntersect } from './src/util/intersect';
@@ -2,9 +2,27 @@ import { range, sum } from 'd3-array';
import { rebind } from 'd3fc-rebind';
import { select, selectAll } from 'd3-selection';
import * as d3 from 'd3-selection';
import { layoutLabel, layoutTextLabel, layoutAnnealing, layoutIntersect, layoutRemoveOverlaps } from '..';
import { layoutLabel, layoutTextLabel, layoutAnnealing, layoutRemoveOverlaps } from '..';
import * as fc from '..';

const isIntersecting = (a, b) =>
!(a.x >= (b.x + b.width) ||
(a.x + a.width) <= b.x ||
a.y >= (b.y + b.height) ||
(a.y + a.height) <= b.y);

const layoutIntersect = (a, b) => {
if (isIntersecting(a, b)) {
const left = Math.max(a.x, b.x);
const right = Math.min(a.x + a.width, b.x + b.width);
const top = Math.max(a.y, b.y);
const bottom = Math.min(a.y + a.height, b.y + b.height);
return (right - left) * (bottom - top);
} else {
return 0;
}
};

const labelPadding = 4;
const label = layoutTextLabel()
.padding(labelPadding)
@@ -28,10 +28,10 @@ export default () => {
const containerPenalty = (rectangle) =>
bounds ? rectangle.width * rectangle.height - intersect(rectangle, bounds) : 0;

const penaltyForRectangle = (rectangles, index) =>
const penaltyForRectangle = (rectangle, index, rectangles) =>
collisionArea(rectangles, index) +
containerPenalty(rectangles[index]) +
orientationPenalty(rectangles[index]);
containerPenalty(rectangle) +
orientationPenalty(rectangle);

const strategy = (data) => {
let currentTemperature = temperature;
@@ -40,15 +40,18 @@ export default () => {
const winningScore = (newScore, oldScore) =>
Math.exp((oldScore - newScore) / currentTemperature) > Math.random();

let rectangles = layout(data, penaltyForRectangle, winningScore);
let rectangles = layout()
.locationScore(penaltyForRectangle)
.winningScore(winningScore)
.rectangles(data);

while (currentTemperature > 0) {
const index = randomIndex(data);
const randomNewPlacement = randomItem(placements(data[index]));
rectangles = rectangles.tryLocation(randomNewPlacement, index);
rectangles = rectangles(randomNewPlacement, index);
currentTemperature -= cooling;
}
return rectangles.data();
return rectangles.rectangles();
};

strategy.temperature = (...args) => {
@@ -11,18 +11,21 @@ export default () => {
const containerPenalty = (rectangle) =>
bounds ? rectangle.width * rectangle.height - intersect(rectangle, bounds) : 0;

const penaltyForRectangle = (rectangles, index) =>
const penaltyForRectangle = (rectangle, index, rectangles) =>
collisionArea(rectangles, index) +
containerPenalty(rectangles[index]);
containerPenalty(rectangle);

const strategy = (data) => {
let rectangles = layout(data, penaltyForRectangle);
let rectangles = layout()
.locationScore(penaltyForRectangle)
.rectangles(data);

data.forEach((rectangle, index) => {
placements(rectangle).forEach((placement, placementIndex) => {
rectangles = rectangles.tryLocation(placement, index);
rectangles = rectangles(placement, index);
});
});
return rectangles.data();
return rectangles.rectangles();
};

strategy.bounds = (...args) => {
@@ -14,32 +14,70 @@ const lessThan = (a, b) => a < b;
// it is constructed using two functions, locationScore, which score the placement of and
// individual rectangle, and winningScore which takes the scores for a rectangle
// at two different locations and assigns a winningScore.
const layout = (rectangles, locationScore, winningScore, score) => {
const layoutComponent = () => {
let score = null;

score = score || sum(
rectangles.map((_, i) => locationScore(rectangles, i))
);
let winningScore = lessThan;

winningScore = winningScore || lessThan;
let locationScore = () => 0;

let rectangles;

const evaluatePlacement = (placement, index) =>
score -
locationScore(rectangles, index) +
locationScore(substitute(rectangles, index, placement), index);

const tryLocation = (rectangle, index) => {
const newScore = evaluatePlacement(rectangle, index);
return winningScore(newScore, score)
? layout(substitute(rectangles, index, rectangle), locationScore, winningScore, newScore)
: self;
locationScore(rectangles[index], index, rectangles) +
locationScore(placement, index, substitute(rectangles, index, placement));

const layout = (placement, index) => {
if (!score) {
score = sum(
rectangles.map((r, i) => locationScore(r, i, rectangles))
);
}

const newScore = evaluatePlacement(placement, index);

if (winningScore(newScore, score)) {
return layoutComponent()
.locationScore(locationScore)
.winningScore(winningScore)
.score(newScore)
.rectangles(substitute(rectangles, index, placement));
} else {
return layout;
}
};

const self = {
data: () => rectangles,
score: () => score,
tryLocation
layout.rectangles = (...args) => {
if (!args.length) {
return rectangles;
}
rectangles = args[0];
return layout;
};
return self;
layout.score = (...args) => {
if (!args.length) {
return score;
}
score = args[0];
return layout;
};
layout.winningScore = (...args) => {
if (!args.length) {
return winningScore;
}
winningScore = args[0];
return layout;
};
layout.locationScore = (...args) => {
if (!args.length) {
return locationScore;
}
locationScore = args[0];
return layout;
};

return layout;
};

export default layout;
export default layoutComponent;

0 comments on commit 43cf907

Please sign in to comment.
You can’t perform that action at this time.