def buildAtlas(self):
        # get rendered array for all symbols, keep track of avg/max width
        rendered = {}
        avgWidth = 0.0
        maxWidth = 0
        images = []
        for key, sourceRect in self.symbolMap.items():
            if sourceRect.width() == 0:
                img = renderSymbol(key[0], key[1], sourceRect.pen, sourceRect.brush)
                images.append(img)  ## we only need this to prevent the images being garbage collected immediately
                arr = fn.imageToArray(img, copy=False, transpose=False)
            else:
                (y,x,h,w) = sourceRect.getRect()
                arr = self.atlasData[int(x):int(x+w), int(y):int(y+w)]
            rendered[key] = arr
            w = arr.shape[0]
            avgWidth += w
            maxWidth = max(maxWidth, w)

        nSymbols = len(rendered)
        if nSymbols > 0:
            avgWidth /= nSymbols
            width = max(maxWidth, avgWidth * (nSymbols**0.5))
        else:
            avgWidth = 0
            width = 0

        # sort symbols by height
        symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True)

        self.atlasRows = []

        x = width
        y = 0
        rowheight = 0
        for key in symbols:
            arr = rendered[key]
            w,h = arr.shape[:2]
            if x+w > width:
                y += rowheight
                x = 0
                rowheight = h
                self.atlasRows.append([y, rowheight, 0])
            self.symbolMap[key].setRect(y, x, h, w)
            x += w
            self.atlasRows[-1][2] = x
        height = y + rowheight

        self.atlasData = np.zeros((int(width), int(height), 4), dtype=np.ubyte)
        for key in symbols:
            y, x, h, w = self.symbolMap[key].getRect()
            self.atlasData[int(x):int(x+w), int(y):int(y+h)] = rendered[key]
        self.atlas = None
        self.atlasValid = True
        self.max_width = maxWidth