How to tile a rectangular area parent with rectangles of a given aspect ratio.
After a discussion on StackOverflow https://stackoverflow.com/a/6508371/8125472
| class tile(Scene): | |
| def construct(self): | |
| for _ in range(10): | |
| # Size rectangleSize = new Size(); # rectangle size will be used as a default value that is the exact aspect ratio desired. | |
| # | |
| # Aspect Ratio = h / w | |
| # where h is the height of the child and w is the width | |
| # | |
| # the numerator will be the aspect ratio and the denominator will always be one | |
| # if you want it to be square just use an aspect ratio of 1 | |
| parent = Rectangle(width=14, height=8) | |
| rectangleSize = Rectangle(width=np.random.uniform(1,3)) | |
| desiredAspectRatio = rectangleSize.width/rectangleSize.height | |
| numRectangles = np.random.random_integers(10,30) | |
| # estimate of the number of columns useing the formula: | |
| # n * W * h | |
| # columns = SquareRoot( ------------- ) | |
| # H * w | |
| # | |
| # Where n is the number of items, W is the width of the parent, H is the height of the parent, | |
| # h is the height of the child, and w is the width of the child | |
| numColumns = np.sqrt( (numRectangles * rectangleSize.height * parent.width) / (parent.height * rectangleSize.width) ) | |
| lowBoundColumns = np.floor(numColumns) | |
| highBoundColumns = np.ceil(numColumns) | |
| # The number of rows is determined by finding the floor of the number of children divided by the columns | |
| lowNumRows = np.ceil(numRectangles / lowBoundColumns) | |
| highNumRows = np.ceil(numRectangles / highBoundColumns) | |
| # Vertical Scale is what you multiply the vertical size of the child to find the expected area if you were to find | |
| # the size of the rectangle by maximizing by rows | |
| # | |
| # H | |
| # Vertical Scale = ---------- | |
| # R * h | |
| # | |
| # Where H is the height of the parent, R is the number of rows, and h is the height of the child | |
| # | |
| VerticalScale = parent.height / lowNumRows * rectangleSize.height | |
| #Horizontal Scale is what you multiply the horizintale size of the child to find the expected area if you were to find | |
| # the size of the rectangle by maximizing by columns | |
| # | |
| # W | |
| # Vertical Scale = ---------- | |
| # c * w | |
| # | |
| #Where W is the width of the parent, c is the number of columns, and w is the width of the child | |
| HorizontalScale = parent.width / (highBoundColumns * rectangleSize.width) | |
| # The Max areas are what is used to determine if we should maximize over rows or columns | |
| # The areas are found by multiplying the scale by the appropriate height or width and finding the area after the scale | |
| # | |
| # Horizontal Area = Sh * w * ( (Sh * w) / A ) | |
| # | |
| # where Sh is the horizontal scale, w is the width of the child, and A is the aspect ratio of the child | |
| # | |
| MaxHorizontalArea = (HorizontalScale * rectangleSize.width) * ((HorizontalScale * rectangleSize.width) / desiredAspectRatio) | |
| # | |
| # | |
| # Vertical Area = Sv * h * (Sv * h) * A | |
| # Where Sv isthe vertical scale, h is the height of the child, and A is the aspect ratio of the child | |
| # | |
| MaxVerticalArea = (VerticalScale * rectangleSize.height) * ((VerticalScale * rectangleSize.height) * desiredAspectRatio) | |
| if (MaxHorizontalArea >= MaxVerticalArea ): # the horizontal are is greater than the max area then we maximize by columns | |
| # the width is determined by dividing the parent's width by the estimated number of columns | |
| # this calculation will work for NEARLY all of the horizontal cases with only a few exceptions | |
| newSize_Width = parent.width / highBoundColumns # we use highBoundColumns because that's what is used for the Horizontal | |
| newSize_Height = newSize_Width / desiredAspectRatio # A = w/h or h= w/A | |
| # In the cases that is doesnt work it is because the height of the new items is greater than the | |
| # height of the parents. this only happens when transitioning to putting all the objects into | |
| # only one row | |
| if (newSize_Height * np.ceil(numRectangles / highBoundColumns) > parent.height): | |
| #in this case the best solution is usually to maximize by rows instead | |
| newHeight = parent.height / highNumRows | |
| newWidth = newHeight * desiredAspectRatio | |
| # However this doesn't always work because in one specific case the number of rows is more than actually needed | |
| # and the width of the objects end up being smaller than the size of the parent because we don't have enough | |
| # columns | |
| if (newWidth * numRectangles < parent.width): | |
| #When this is the case the best idea is to maximize over columns again but increment the columns by one | |
| #This takes care of it for most cases for when this happens. | |
| numColumns += 1 | |
| newWidth = parent.width / np.ceil(numColumns) | |
| newHeight = newWidth / desiredAspectRatio | |
| # in order to make sure the rectangles don't go over bounds we | |
| # increment the number of columns until it is under bounds again. | |
| while (newWidth * numRectangles > parent.width): | |
| numColumns += 1 | |
| newWidth = parent.width / np.ceil(numColumns) | |
| newHeight = newWidth / desiredAspectRatio; | |
| # however after doing this it is possible to have the height too small. | |
| # this will only happen if there is one row of objects. so the solution is to make the objects' | |
| # height equal to the height of their parent | |
| if (newHeight > parent.height): | |
| newHeight = parent.height | |
| newWidth = newHeight * desiredAspectRatio | |
| # if we have a lot of added items occaisionally the previous checks will come very close to maximizing both columns and rows | |
| # what happens in this case is that neither end up maximized | |
| # because we don't know what set of rows and columns were used to get us to where we are | |
| # we must recalculate them with the current measurements | |
| currentCols = np.floor(parent.width / newWidth) | |
| currentRows = np.ceil(numRectangles/currentCols) | |
| # now we check and see if neither the rows or columns are maximized | |
| if ( (newWidth * currentCols ) < parent.width and ( newHeight * np.ceil(numRectangles/currentCols) ) < parent.height): | |
| # maximize by columns first | |
| newWidth = parent.width / currentCols | |
| newHeight = newSize_Width / desiredAspectRatio | |
| # if the columns are over their bounds, then maximized by the columns instead | |
| if (newHeight * np.ceil(numRectangles / currentCols) > parent.height): | |
| newHeight = parent.height / currentRows | |
| newWidth = newHeight * desiredAspectRatio | |
| # finally we have the height of the objects as maximized using columns | |
| newSize_Height = newHeight | |
| newSize_Width = newWidth | |
| else: | |
| #Here we use the vertical scale. We determine the height of the objects based upong | |
| # the estimated number of rows. | |
| # This work for all known cases | |
| newSize_Height = parent.height / lowNumRows | |
| newSize_Width = newSize_Height * desiredAspectRatio | |
| print(rectangleSize.width,rectangleSize.height) | |
| print(newSize_Width,newSize_Height) | |
| print(numRectangles) | |
| print(lowNumRows,highNumRows) | |
| print(numColumns) | |
| rects = VGroup( | |
| rectangleSize.copy().scale_to_fit_width(newSize_Width) for _ in range(numRectangles) | |
| ) | |
| rows = int(lowNumRows) | |
| cols = int(np.ceil(numRectangles/rows)) | |
| print(cols,rows) | |
| rects.arrange_in_grid(cols = cols, rows = rows, buff=0) | |
| self.play(Create(rects),run_time=0.5) | |
| self.wait() | |
| self.play(Uncreate(rects),run_time=0.5) |
How to tile a rectangular area parent with rectangles of a given aspect ratio.
After a discussion on StackOverflow https://stackoverflow.com/a/6508371/8125472