233 lines
13 KiB
Python
233 lines
13 KiB
Python
|
import svg
|
||
|
import random
|
||
|
|
||
|
def arrange_in_layer(abin, pwidth, pheight):
|
||
|
# articles are longer than wider
|
||
|
# default rotation: width: x-direction
|
||
|
# height: y-direction
|
||
|
|
||
|
layer = list()
|
||
|
rest = list()
|
||
|
root = {'x': 0, 'y': 0, 'width': pwidth, 'height': pheight, 'article': None, 'down': None, 'right': None}
|
||
|
|
||
|
def find_node(root, width, height):
|
||
|
if root['article']:
|
||
|
return find_node(root['right'], width, height) or find_node(root['down'], width, height)
|
||
|
elif width <= root['width'] and height <= root['height']:
|
||
|
return root
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def split_node(node, width, height, article):
|
||
|
node['article'] = article
|
||
|
node['article']['pos'] = node['x'], node['y']
|
||
|
node['down'] = {'x': node['x'], 'y': node['y']+height, 'width': node['width'], 'height': node['height']-height, 'article': None, 'down': None, 'right': None}
|
||
|
node['right'] = {'x': node['x']+width, 'y': node['y'], 'width': node['width']-width, 'height': height, 'article': None, 'down': None, 'right': None}
|
||
|
return node
|
||
|
|
||
|
for article in abin:
|
||
|
# output format only accepts integer positions, round package sizes up to even numbers
|
||
|
width, height = article['size']
|
||
|
#if width%2 != 0:
|
||
|
# width += 1
|
||
|
#if height%2 != 0:
|
||
|
# height +=1
|
||
|
|
||
|
node = find_node(root, width, height)
|
||
|
if (node):
|
||
|
node = split_node(node, width, height, article)
|
||
|
else:
|
||
|
# rotate article
|
||
|
article['size'] = height, width
|
||
|
width, height = article['size']
|
||
|
node = find_node(root, width, height)
|
||
|
if (node):
|
||
|
node = split_node(node, width, height, article)
|
||
|
else:
|
||
|
rest.append(article)
|
||
|
|
||
|
# traverse tree to find articles
|
||
|
def find_articles(node):
|
||
|
if not node['article']:
|
||
|
return
|
||
|
layer.append(node['article'])
|
||
|
if node['right']:
|
||
|
find_articles(node['right'])
|
||
|
if node['down']:
|
||
|
find_articles(node['down'])
|
||
|
|
||
|
find_articles(root)
|
||
|
|
||
|
return root, layer, rest
|
||
|
|
||
|
def generate_bin():
|
||
|
abin = []
|
||
|
for i in 1,2,3:
|
||
|
w, h = random.randint(20,150), random.randint(20,150)
|
||
|
if h > w:
|
||
|
w, h = h, w
|
||
|
color = random.randint(0,255), random.randint(0,255), random.randint(0,255)
|
||
|
for j in range(200000/(w*h)):
|
||
|
abin.append({'pos':(0,0), 'size':(w,h), 'color':color})
|
||
|
return abin
|
||
|
|
||
|
|
||
|
#print abin
|
||
|
|
||
|
abin = generate_bin()
|
||
|
|
||
|
abin = [{'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (172, 111, 210), 'pos': (0, 0), 'size': (103, 65)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (12, 155, 145), 'pos': (0, 0), 'size': (123, 97)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (253, 124, 189), 'pos': (0, 0), 'size': (118, 29)}, {'color': (
|
||
|
|
||
|
#pwidth, pheight = 400,600
|
||
|
pwidth, pheight = 800, 600
|
||
|
abin = sorted(abin, key=lambda article: article['size'][0]*article['size'][1], reverse=True)
|
||
|
root, layer, rest = arrange_in_layer(abin, pwidth, pheight)
|
||
|
|
||
|
if rest:
|
||
|
print "rest!"
|
||
|
|
||
|
scene = svg.Scene('test1', (pwidth, pheight))
|
||
|
for a in layer:
|
||
|
scene.add(svg.Rectangle(a['pos'],a['size'],a['color']))
|
||
|
scene.write()
|
||
|
|
||
|
# spread vertically
|
||
|
|
||
|
def get_left_children(node):
|
||
|
if not node['article']:
|
||
|
return []
|
||
|
else:
|
||
|
return [node] + get_left_children(node['down'])
|
||
|
|
||
|
def move_tree_down(node, y):
|
||
|
if not node['article']:
|
||
|
return
|
||
|
pos = node['article']['pos']
|
||
|
node['article']['pos'] = pos[0], pos[1]+y
|
||
|
#if node['down']: # only increase height if not the last node
|
||
|
# node['height'] += y
|
||
|
if node['right']:
|
||
|
move_tree_down(node['right'], y)
|
||
|
if node['down']:
|
||
|
move_tree_down(node['down'], y)
|
||
|
|
||
|
# for each child on the very left, spread vertically and adjust subtree y accordingly
|
||
|
|
||
|
# calculate gap size:
|
||
|
|
||
|
def spread_vertically(node):
|
||
|
leftnodes = get_left_children(node)
|
||
|
for n in leftnodes:
|
||
|
if n['right']:
|
||
|
spread_vertically(n['right'])
|
||
|
if len(leftnodes) == 0:
|
||
|
return
|
||
|
elif len(leftnodes) == 1:
|
||
|
# arrange them in the center of parent
|
||
|
gap = (node['height']-leftnodes[0]['article']['size'][1])/2
|
||
|
move_tree_down(node, gap)
|
||
|
else:
|
||
|
sumleftnodes = sum([n['article']['size'][1] for n in leftnodes])
|
||
|
d, m = divmod(node['height']-sumleftnodes, len(leftnodes)-1)
|
||
|
gaps = (m)*[d+1]+((len(leftnodes)-1)-m)*[d]
|
||
|
#vgap = max(0, vgap-1)
|
||
|
|
||
|
#leftnodes[0]['height'] += vgap
|
||
|
# iteratively move trees down by vgap except for first row
|
||
|
for node, gap in zip(leftnodes[1:], gaps):
|
||
|
move_tree_down(node, gap)
|
||
|
|
||
|
spread_vertically(root)
|
||
|
|
||
|
#spread horizontically
|
||
|
|
||
|
def get_right_children(node):
|
||
|
if not node['article']:
|
||
|
return []
|
||
|
else:
|
||
|
return [node] + get_right_children(node['right'])
|
||
|
|
||
|
def move_tree_right(node, x):
|
||
|
if not node['article']:
|
||
|
return
|
||
|
pos = node['article']['pos']
|
||
|
node['article']['pos'] = pos[0]+x, pos[1]
|
||
|
#if node['right']: # only increase width if not the last node
|
||
|
# node['width'] += x
|
||
|
node['width'] -= x
|
||
|
if node['right']:
|
||
|
move_tree_right(node['right'], x)
|
||
|
if node['down']:
|
||
|
move_tree_right(node['down'], x)
|
||
|
|
||
|
# for each child on the very right, spread horizontically and adjust subtree y accordingly
|
||
|
|
||
|
# calculate gap size:
|
||
|
|
||
|
def spread_horizontically(node):
|
||
|
rightnodes = get_right_children(node)
|
||
|
if len(rightnodes) == 0:
|
||
|
return
|
||
|
elif len(rightnodes) == 1:
|
||
|
# arrange them in the center of parent
|
||
|
gap = (node['width']-rightnodes[0]['article']['size'][0])/2
|
||
|
pos = rightnodes[0]['article']['pos']
|
||
|
rightnodes[0]['article']['pos'] = pos[0]+gap, pos[1]
|
||
|
else:
|
||
|
# TODO: the uppermost row might not be the longest
|
||
|
sumrightnodes = sum([n['article']['size'][0] for n in rightnodes])
|
||
|
d, m = divmod(node['width']-sumrightnodes, len(rightnodes)-1)
|
||
|
gaps = (m)*[d+1]+((len(rightnodes)-1)-m)*[d]
|
||
|
#hgap = max(0, hgap-1)
|
||
|
|
||
|
#rightnodes[0]['width'] += hgap
|
||
|
# iteratively move trees right by hgap except for first
|
||
|
for node, gap in zip(rightnodes[1:], gaps):
|
||
|
move_tree_right(node, gap)
|
||
|
|
||
|
# process inner nodes as well
|
||
|
for n in rightnodes:
|
||
|
if n['down']:
|
||
|
spread_horizontically(n['down'])
|
||
|
|
||
|
spread_horizontically(root)
|
||
|
|
||
|
layer = list()
|
||
|
|
||
|
def find_articles(node):
|
||
|
if not node['article']:
|
||
|
return
|
||
|
layer.append(node['article'])
|
||
|
if node['right']:
|
||
|
find_articles(node['right'])
|
||
|
if node['down']:
|
||
|
find_articles(node['down'])
|
||
|
|
||
|
find_articles(root)
|
||
|
|
||
|
def intersects(a1, a2):
|
||
|
return (
|
||
|
a1['pos'][0] <= a2['pos'][0]+a2['size'][0]
|
||
|
and a1['pos'][0]+a1['size'][0] >= a2['pos'][0]
|
||
|
and a1['pos'][1] <= a2['pos'][1]+a2['size'][1]
|
||
|
and a1['pos'][1]+a1['size'][1] >= a2['pos'][1]
|
||
|
)
|
||
|
|
||
|
# sanity checks
|
||
|
for article1 in layer:
|
||
|
for article2 in layer:
|
||
|
if article1 == article2:
|
||
|
continue
|
||
|
if intersects(article1, article2):
|
||
|
print "intersects"
|
||
|
if (article1['pos'][0] < 0
|
||
|
or article1['pos'][1] < 0
|
||
|
or article1['pos'][0]+article1['size'][0] > pwidth
|
||
|
or article1['pos'][1]+article1['size'][1] > pheight):
|
||
|
print "overhang"
|
||
|
|
||
|
scene = svg.Scene('test2', (pwidth, pheight))
|
||
|
for a in layer:
|
||
|
scene.add(svg.Rectangle(a['pos'],a['size'],a['color']))
|
||
|
scene.write()
|