File: layout.py

package info (click to toggle)
glueviz 0.9.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 17,180 kB
  • ctags: 6,728
  • sloc: python: 37,111; makefile: 134; sh: 60
file content (84 lines) | stat: -rw-r--r-- 2,551 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""
This module provides some routines for performing layout
calculations to organize rectangular windows in a larger canvas
"""

from __future__ import absolute_import, division, print_function

from collections import Counter

from glue.external import six


class Rectangle(object):

    def __init__(self, x, y, w, h):
        """ A rectangle (obviously).

        :param x: Left edge
        :param y: Bottom edge
        :param w: Width
        :param h: Height
        """
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def __eq__(self, other):
        return (self.x == other.x and
                self.y == other.y and
                self.w == other.w and
                self.h == other.h)

    # In Python 3, if __eq__ is defined, then __hash__ has to be re-defined
    if six.PY3:
        __hash__ = object.__hash__

    def __str__(self):
        return repr(self)

    def __repr__(self):
        return "Rectangle(%f, %f, %f, %f)" % (self.x, self.y, self.w, self.h)

    def snap(self, xstep, ystep=None, padding=0.0):
        """
        Snap the rectangle onto a grid, with optional padding.

        :param xstep: The number of intervals to split the x=[0, 1] range into.
        :param ystep: The number of intervals to split the y=[0, 1] range into.
        :param padding: Uniform padding to add around the result. This shrinks
                        the result so that the edges + padding line up with the
                        grid.

        :returns: A new Rectangle, obtained by snapping self onto the grid,
                  and applying padding
        """

        if ystep is None:
            ystep = xstep

        return Rectangle(round(self.x * xstep) / xstep + padding,
                         round(self.y * ystep) / ystep + padding,
                         round(self.w * xstep) / xstep - 2 * padding,
                         round(self.h * ystep) / ystep - 2 * padding)


def _snap_size(rectangles):
    x = Counter([round(1 / r.w) for r in rectangles])
    y = Counter([round(1 / r.h) for r in rectangles])
    return x.most_common()[0][0], y.most_common()[0][0]


def snap_to_grid(rectangles, padding=0.0):
    """
    Snap a collection of rectangles onto a grid, in a sensible fashion

    :param rectangles: List of Rectangle instances
    :returns: A dictionary mapping each input rectangle to a snapped position
    """
    result = {}
    xs, ys = _snap_size(rectangles)
    for r in rectangles:
        result[r] = r.snap(xs, ys, padding=padding)
    return result