def dateTimeRect( self, dateTime ): """ Returns the rect that is defined by the inputed date time. :return <QRectF> """ data = self._dateTimeGrid.get(dateTime.toTime_t()) if ( data ): return QRectF(data[1]) return QRectF()
def dateRect( self, date ): """ Returns the rect that is defined by the inputed date. :return <QRectF> """ data = self._dateGrid.get(date.toJulianDay()) if ( data ): return QRectF(data[1]) return QRectF()
def setSceneRect(self, *args): """ Overloads the set rect method to signal that the scene needs to be rebuilt. :param args | <arg variant> """ curr = self.sceneRect() super(XGanttScene, self).setSceneRect(*args) if curr != QRectF(*args): self._dirty = True
def viewportRect(self): """ Returns the QRectF that represents the visible viewport rect for the current view. :return <QRectF> """ w = self.width() h = self.height() vbar = self.verticalScrollBar() hbar = self.horizontalScrollBar() if vbar.isVisible(): w -= vbar.width() if hbar.isVisible(): h -= hbar.height() top_l = self.mapToScene(QPoint(0, 0)) bot_r = self.mapToScene(QPoint(w, h)) return QRectF(top_l.x(), top_l.y(), bot_r.x() - top_l.x(), bot_r.y() - top_l.y())
def rebuildGrid(self): """ Rebuilds the ruler data. """ vruler = self.verticalRuler() hruler = self.horizontalRuler() rect = self._buildData['grid_rect'] # process the vertical ruler h_lines = [] h_alt = [] h_notches = [] vpstart = vruler.padStart() vnotches = vruler.notches() vpend = vruler.padEnd() vcount = len(vnotches) + vpstart + vpend deltay = rect.height() / max((vcount - 1), 1) y = rect.bottom() alt = False for i in range(vcount): h_lines.append(QLineF(rect.left(), y, rect.right(), y)) # store alternate color if (alt): alt_rect = QRectF(rect.left(), y, rect.width(), deltay) h_alt.append(alt_rect) # store notch information nidx = i - vpstart if (0 <= nidx and nidx < len(vnotches)): notch = vnotches[nidx] notch_rect = QRectF(0, y - 3, rect.left() - 3, deltay) h_notches.append((notch_rect, notch)) y -= deltay alt = not alt self._buildData['grid_h_lines'] = h_lines self._buildData['grid_h_alt'] = h_alt self._buildData['grid_h_notches'] = h_notches # process the horizontal ruler v_lines = [] v_alt = [] v_notches = [] hpstart = hruler.padStart() hnotches = hruler.notches() hpend = hruler.padEnd() hcount = len(hnotches) + hpstart + hpend deltax = rect.width() / max((hcount - 1), 1) x = rect.left() alt = False for i in range(hcount): v_lines.append(QLineF(x, rect.top(), x, rect.bottom())) # store alternate info if (alt): alt_rect = QRectF(x - deltax, rect.top(), deltax, rect.height()) v_alt.append(alt_rect) # store notch information nidx = i - hpstart if (0 <= nidx and nidx < len(hnotches)): notch = hnotches[nidx] notch_rect = QRectF(x - (deltax / 2.0), rect.bottom() + 3, deltax, 13) v_notches.append((notch_rect, notch)) x += deltax alt = not alt self._buildData['grid_v_lines'] = v_lines self._buildData['grid_v_alt'] = v_alt self._buildData['grid_v_notches'] = v_notches # draw the axis lines axis_lines = [] axis_lines.append( QLineF(rect.left(), rect.top(), rect.left(), rect.bottom())) axis_lines.append( QLineF(rect.left(), rect.bottom(), rect.right(), rect.bottom())) self._buildData['axis_lines'] = axis_lines
def rebuild(self): """ Rebuilds the data for this scene to draw with. """ global XChartWidgetItem if (XChartWidgetItem is None): from projexui.widgets.xchartwidget.xchartwidgetitem \ import XChartWidgetItem self._buildData = {} # build the grid location x = 8 y = 8 w = self.sceneRect().width() h = self.sceneRect().height() hpad = self.horizontalPadding() vpad = self.verticalPadding() hmax = self.horizontalRuler().maxNotchSize(Qt.Horizontal) left_offset = hpad + self.verticalRuler().maxNotchSize(Qt.Vertical) right_offset = left_offset + hpad top_offset = vpad bottom_offset = top_offset + vpad + hmax left = x + left_offset right = w - right_offset top = y + top_offset bottom = h - bottom_offset rect = QRectF() rect.setLeft(left) rect.setRight(right) rect.setBottom(bottom) rect.setTop(top) self._buildData['grid_rect'] = rect # rebuild the ruler data self.rebuildGrid() self._dirty = False # rebuild all the items padding = self.horizontalPadding() + self.verticalPadding() grid = self.sceneRect() filt = lambda x: isinstance(x, XChartWidgetItem) items = filter(filt, self.items()) height = float(grid.height()) if height == 0: ratio = 1 else: ratio = grid.width() / height count = len(items) if (not count): return if (ratio >= 1): radius = (grid.height() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = radius * 2.5 dy = 0 else: radius = (grid.width() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = 0 dy = radius * 2.5 for item in items: item.setPieCenter(QPointF(x, y)) item.setRadius(radius) item.rebuild() x += dx y += dy if (self._trackerItem and self._trackerItem()): self._trackerItem().rebuild(self._buildData['grid_rect'])
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_) / 2.0 - node.rect().width() / 2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_) / 2.0 - node.rect().height() / 2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes
def rebuildTiles(self): # create the foreground pixmap gantt = self.ganttWidget() header = gantt.treeWidget().header() width = self.sceneRect().width() height = header.height() # create the main color palette = gantt.palette() color = palette.color(palette.Button) textColor = palette.color(palette.ButtonText) borderColor = color.darker(140) text_align = Qt.AlignBottom | Qt.AlignHCenter # create the gradient gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, height) gradient.setColorAt(0, color) gradient.setColorAt(1, color.darker(120)) # generate the tiles tiles = [] painters = [] for rect, label in self._topLabels: tile_rect = QRectF(rect.x(), 0, rect.width(), height) pixmap = QPixmap(rect.width(), height) with XPainter(pixmap) as painter: painter.setBrush(QBrush(gradient)) painter.drawRect(tile_rect) rx = 0 ry = 0 rw = rect.width() rh = rect.height() painter.setPen(borderColor) painter.drawRect(rx, ry, rw, rh) painter.setPen(textColor) painter.drawText(rx, ry, rw, rh - 2, text_align, label) tiles.append((tile_rect, pixmap)) painters.append((tile_rect, pixmap, painter)) # add bottom labels for rect, label in self._labels: for tile_rect, tile, painter in painters: if tile_rect.x() <= rect.x() and \ rect.right() <= tile_rect.right(): rx = rect.x() - tile_rect.x() ry = rect.y() rw = rect.width() rh = rect.height() with painter: painter.setPen(borderColor) painter.drawRect(rx, ry, rw, rh) painter.setPen(textColor) painter.drawText(rx, ry, rw, rh - 2, text_align, label) self._tiles = tiles
def rebuild(self): """ Rebuilds the item based on the current points. """ scene = self.scene() if not scene: return self._subpaths = [] grid = scene.gridRect() typ = self.chartType() hruler = scene.horizontalRuler() vruler = scene.verticalRuler() path = QPainterPath() area = QPainterPath() self._buildData.clear() self._buildData['path_area'] = area self.setPos(0, 0) # draw a line item if typ == XChartScene.Type.Line: first = True pos = None home = None self._ellipses = [] points = self.points() if (self.orientation() == Qt.Horizontal): points.sort(hruler.compareValues, key=lambda x: x[0]) else: points.sort(vruler.compareValues, key=lambda y: y[1]) points.reverse() for x, y in self.points(): pos = scene.mapFromChart(x, y) if first: home = QPointF(pos.x(), grid.bottom()) area.moveTo(home) area.lineTo(pos) path.moveTo(pos) self._ellipses.append(pos) first = False else: path.lineTo(pos) area.lineTo(pos) self._ellipses.append(pos) if pos and home: area.lineTo(pos.x(), grid.bottom()) area.lineTo(home) # draw a bar item elif typ == XChartScene.Type.Bar: barsize = self.barSize() horiz = self.orientation() == Qt.Horizontal for x, y in self.points(): pos = scene.mapFromChart(x, y) subpath = QPainterPath() if horiz: r = min(grid.bottom() - pos.y(), 8) subpath.moveTo(pos.x() - barsize / 2.0, grid.bottom()) subpath.lineTo(pos.x() - barsize / 2.0, pos.y() + r) subpath.quadTo(pos.x() - barsize / 2.0, pos.y(), pos.x() - barsize / 2.0 + r, pos.y()) subpath.lineTo(pos.x() + barsize / 2.0 - r, pos.y()) subpath.quadTo(pos.x() + barsize / 2.0, pos.y(), pos.x() + barsize / 2.0, pos.y() + r) subpath.lineTo(pos.x() + barsize / 2.0, grid.bottom()) subpath.lineTo(pos.x() - barsize / 2.0, grid.bottom()) else: subpath.moveTo(grid.left(), pos.y() - barsize / 2.0) subpath.lineTo(pos.x(), pos.y() - barsize / 2.0) subpath.lineTo(pos.x(), pos.y() + barsize / 2.0) subpath.lineTo(grid.left(), pos.y() + barsize / 2.0) subpath.lineTo(grid.left(), pos.y() - barsize / 2.0) path.addPath(subpath) self._subpaths.append((x, y, subpath)) # draw a pie chart elif typ == XChartScene.Type.Pie: if self.orientation() == Qt.Horizontal: key_index = 0 value_index = 1 value_ruler = self.verticalRuler() else: key_index = 1 value_index = 0 value_ruler = self.horizontalRuler() pie_values = {} for point in self.points(): key = point[key_index] value = point[value_index] pie_values.setdefault(key, []) pie_values[key].append(value) for key, values in pie_values.items(): pie_values[key] = value_ruler.calcTotal(values) total = max(1, value_ruler.calcTotal(pie_values.values())) # calculate drawing parameters center = self.pieCenter() radius = self.radius() diameter = radius * 2 angle = 0 bound = QRectF(-radius, -radius, diameter, diameter) for key, value in sorted(pie_values.items(), key=lambda x: x[1]): # calculate the percentage perc = float(value) / total # calculate the angle as the perc * 360 item_angle = perc * 360 self.setPos(center) sub_path = QPainterPath() sub_path.arcTo(bound, angle, item_angle) sub_path.lineTo(0, 0) path.addPath(sub_path) self._subpaths.append((key, value, sub_path)) angle += item_angle self.setPath(path) self._dirty = False
def rebuild( self ): """ Rebuilds the data for this scene to draw with. """ global XChartWidgetItem if ( XChartWidgetItem is None ): from projexui.widgets.xchartwidget.xchartwidgetitem \ import XChartWidgetItem self._buildData = {} # build the grid location x = 8 y = 8 w = self.sceneRect().width() h = self.sceneRect().height() hpad = self.horizontalPadding() vpad = self.verticalPadding() hmax = self.horizontalRuler().maxNotchSize(Qt.Horizontal) left_offset = hpad + self.verticalRuler().maxNotchSize(Qt.Vertical) right_offset = left_offset + hpad top_offset = vpad bottom_offset = top_offset + vpad + hmax left = x + left_offset right = w - right_offset top = y + top_offset bottom = h - bottom_offset rect = QRectF() rect.setLeft(left) rect.setRight(right) rect.setBottom(bottom) rect.setTop(top) self._buildData['grid_rect'] = rect # rebuild the ruler data self.rebuildGrid() self._dirty = False # rebuild all the items padding = self.horizontalPadding() + self.verticalPadding() grid = self.sceneRect() filt = lambda x: isinstance(x, XChartWidgetItem) items = filter(filt, self.items()) height = float(grid.height()) if height == 0: ratio = 1 else: ratio = grid.width() / height count = len(items) if ( not count ): return if ( ratio >= 1 ): radius = (grid.height() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = radius * 2.5 dy = 0 else: radius = (grid.width() - padding * 2) / 2.0 x = rect.center().x() y = rect.center().y() dx = 0 dy = radius * 2.5 for item in items: item.setPieCenter(QPointF(x, y)) item.setRadius(radius) item.rebuild() x += dx y += dy if ( self._trackerItem and self._trackerItem() ): self._trackerItem().rebuild(self._buildData['grid_rect'])
def calculateDatasets(self, scene, axes, datasets): """ Builds the datasets for this renderer. Each renderer will need to subclass and implemenent this method, otherwise, no data will be shown in the chart. :param scene | <XChartScene> axes | [< datasets | [<XChartDataset>, ..] """ items = self.calculateDatasetItems(scene, datasets) if not items: scene.clear() return xaxis = scene.chart().horizontalAxis() yaxis = scene.chart().verticalAxis() data_axis = None all_values = [] # determine if we're mapping data aginst the sets themselves, in # which case, create a pie chart of the dataset vs. its if isinstance(xaxis, XDatasetAxis): per_dataset = False data_axis = yaxis total = 1 elif isinstance(yaxis, XDatasetAxis): per_dataset = False total = 1 data_axis = xaxis else: per_dataset = True total = len(items) if not per_dataset: all_values = [dataset.sum(data_axis) \ for dataset in datasets] # generate the build information rect = self.buildData('grid_rect') rect.setX(rect.x() + 10) rect.setY(rect.y() + 10) rect.setWidth(rect.width() - 20) rect.setHeight(rect.height() - 20) if rect.width() > rect.height(): radius = min(rect.width() / 2.0, rect.height() / 2.0) x = rect.left() y = rect.top() + radius deltax = min(radius * 2, rect.width() / float(total + 1)) deltay = 0 else: radius = min(rect.height() / 2.0, rect.width() / 2.0) x = rect.left() + radius y = rect.top() deltax = 0 deltay = min(radius * 2, rect.height() / float(total + 1)) # initialize the first pie chart angle = 0 cx = x + deltax cy = y + deltay x += deltax y += deltay self.setBuildData('center', QPointF(cx, cy)) self.setBuildData('radius', radius) for dataset in datasets: item = items.get(dataset) if not item: continue item.setBuildData('center', QPointF(cx, cy)) item.setBuildData('radius', radius) subpaths = [] bound = QRectF(cx - radius, cy - radius, radius * 2, radius * 2) path = QPainterPath() if per_dataset: data_values = dataset.values(yaxis.name()) andle = 0 for value in dataset.values(): perc = yaxis.percentOfTotal(value.get(yaxis.name()), data_values) # calculate the angle as the perc item_angle = perc * 360 subpath = QPainterPath() subpath.moveTo(cx, cy) subpath.arcTo(bound, angle, item_angle) subpath.lineTo(cx, cy) path.addPath(subpath) subpaths.append((value.get(xaxis.name()), subpath)) angle += item_angle cx = x + deltax cy = y + deltay x += deltax y += deltay else: value = dataset.sum(data_axis) perc = data_axis.percentOfTotal(value, all_values) # calculate the angle as the perc item_angle = perc * 360 subpath = QPainterPath() subpath.moveTo(cx, cy) subpath.arcTo(bound, angle, item_angle) subpath.lineTo(cx, cy) path.addPath(subpath) subpaths.append((value, subpath)) angle += item_angle item.setPath(path) item.setBuildData('subpaths', subpaths)
def rebuildDays( self ): """ Rebuilds the interface as a week display. """ time = QTime(0, 0, 0) hour = True x = 6 y = 6 + 24 w = self.width() - 12 - 25 dh = 48 indent = 58 text_data = [] vlines = [] hlines = [QLine(x, y, w, y)] time_grids = [] for i in range(48): if ( hour ): hlines.append(QLine(x, y, w, y)) text_data.append((x, y + 6, indent - 6, dh, Qt.AlignRight | Qt.AlignTop, time.toString('hap'))) else: hlines.append(QLine(x + indent, y, w, y)) time_grids.append((time, y, dh / 2)) # move onto the next line hour = not hour time = time.addSecs(30 * 60) y += dh / 2 hlines.append(QLine(x, y, w, y)) h = y y = 6 + 24 # load the grid vlines.append(QLine(x, y, x, h)) vlines.append(QLine(x + indent, y, x + indent, h)) vlines.append(QLine(w, y, w, h)) today = QDate.currentDate() curr_date = self.currentDate() # load the days if ( self.currentMode() == XCalendarScene.Mode.Week ): date = self.currentDate() day_of_week = date.dayOfWeek() if ( day_of_week == 7 ): day_of_week = 0 min_date = date.addDays(-day_of_week) max_date = date.addDays(6-day_of_week) self._minimumDate = min_date self._maximumDate = max_date dw = (w - (x + indent)) / 7.0 vx = x + indent date = min_date for i in range(7): vlines.append(QLine(vx, y, vx, h)) text_data.append((vx + 6, 6, dw, 24, Qt.AlignCenter, date.toString('ddd MM/dd'))) self._dateGrid[date.toJulianDay()] = ((0, i), QRectF(vx, y, dw, h - y)) # create the date grid for date time options for r, data in enumerate(time_grids): time, ty, th = data dtime = QDateTime(date, time) key = dtime.toTime_t() self._dateTimeGrid[key] = ((r, i), QRectF(vx, ty, dw, th)) if ( date == curr_date ): self._buildData['curr_date'] = QRectF(vx, y, dw, h - 29) elif ( date == today ): self._buildData['today'] = QRectF(vx, y, dw, h - 29) date = date.addDays(1) vx += dw # load a single day else: date = self.currentDate() self._maximumDate = date self._minimumDate = date text_data.append((x + indent, 6, w, 24, Qt.AlignCenter, date.toString('ddd MM/dd'))) self._dateGrid[date.toJulianDay()] = ((0, 0), QRectF(x, y, w - x, h - y)) # create the date grid for date time options for r, data in enumerate(time_grids): time, ty, th = data dtime = QDateTime(date, time) key = dtime.toTime_t() rect = QRectF(x + indent, ty, w - (x + indent), th) self._dateTimeGrid[key] = ((r, 0), rect) self._buildData['grid'] = hlines + vlines self._buildData['regular_text'] = text_data rect = self.sceneRect() rect.setHeight(h + 6) super(XCalendarScene, self).setSceneRect(rect)
def rebuildMonth( self ): """ Rebuilds the month for this scene. """ # make sure we start at 0 for sunday vs. 7 for sunday day_map = dict([(i+1, i+1) for i in range(7)]) day_map[7] = 0 today = QDate.currentDate() curr = self.currentDate() first = QDate(curr.year(), curr.month(), 1) last = QDate(curr.year(), curr.month(), curr.daysInMonth()) first = first.addDays(-day_map[first.dayOfWeek()]) last = last.addDays(6-day_map[last.dayOfWeek()]) cols = 7 rows = (first.daysTo(last) + 1) / cols hlines = [] vlines = [] padx = 6 pady = 6 header = 24 w = self.width() - (2 * padx) h = self.height() - (2 * pady) dw = (w / cols) - 1 dh = ((h - header) / rows) - 1 x0 = padx y0 = pady + header x = x0 y = y0 for row in range(rows + 1): hlines.append(QLine(x0, y, w, y)) y += dh for col in range(cols + 1): vlines.append(QLine(x, y0, x, h)) x += dw self._buildData['grid'] = hlines + vlines # draw the date fields date = first row = 0 col = 0 # draw the headers x = x0 y = pady regular_text = [] mid_text = [] self._buildData['regular_text'] = regular_text self._buildData['mid_text'] = mid_text for day in ('Sun', 'Mon','Tue','Wed','Thu','Fri','Sat'): regular_text.append((x + 5, y, dw, y0, Qt.AlignLeft | Qt.AlignVCenter, day)) x += dw for i in range(first.daysTo(last) + 1): top = (y0 + (row * dh)) left = (x0 + (col * dw)) rect = QRectF(left - 1, top, dw, dh) # mark the current date on the calendar if ( date == curr ): self._buildData['curr_date'] = rect # mark today's date on the calendar elif ( date == today ): self._buildData['today'] = rect # determine how to draw the calendar format = 'd' if ( date.day() == 1 ): format = 'MMM d' # determine the color to draw the text if ( date.month() == curr.month() ): text = regular_text else: text = mid_text # draw the text text.append((left + 2, top + 2, dw - 4, dh - 4, Qt.AlignTop | Qt.AlignLeft, date.toString(format))) # update the limits if ( not i ): self._minimumDate = date self._maximumDate = date self._dateGrid[date.toJulianDay()] = ((row, col), rect) if ( col == (cols - 1) ): row += 1 col = 0 else: col += 1 date = date.addDays(1)
def calculate(self, scene, xaxis, yaxis): """ Calculates the grid data before rendering. :param scene | <XChartScene> xaxis | <XChartAxis> yaxis | <XChartAxis> """ # build the grid location sceneRect = scene.sceneRect() # process axis information h_lines = [] h_alt = [] h_labels = [] v_lines = [] v_alt = [] v_labels = [] xlabels = [] xcount = 1 xsections = 1 xdelta = 0 xdeltamin = 0 ylabels = [] ycount = 1 ysections = 1 ydeltamin = 0 ydelta = 0 axis_lft = 0 axis_rht = 0 axis_bot = 0 axis_top = 0 # precalculate xaxis information for width changes if xaxis and self.showXAxis(): size = sceneRect.width() xdeltamin = xaxis.minimumLabelWidth() result = self.calculateAxis(xaxis, size, xdeltamin) xlabels, xcount, xsections, newWidth, xdelta = result if newWidth != size: sceneRect.setWidth(newWidth) # precalculate yaxis information for height changes if yaxis and self.showYAxis(): size = sceneRect.height() ydeltamin = yaxis.minimumLabelHeight() result = self.calculateAxis(yaxis, size, ydeltamin) ylabels, ycount, ysections, newHeight, ydelta = result if newHeight != size: sceneRect.setHeight(newHeight) # generate the xaxis if xaxis and self.showXAxis(): x = sceneRect.left() + xdeltamin / 2 axis_lft = x axis_rht = x alt = False for i in range(xcount): v_lines.append( QLineF(x, sceneRect.top(), x, sceneRect.bottom())) # store alternate info if alt: alt_rect = QRectF(x - xdelta, sceneRect.top(), xdelta, sceneRect.height()) v_alt.append(alt_rect) # store label information v_labels.append((x, xdelta, xlabels[i])) axis_rht = x x += xdelta alt = not alt # generate the yaxis if yaxis and self.showYAxis(): y = sceneRect.bottom() - ydeltamin / 2 axis_bot = y axis_top = y alt = False for i in range(ycount): h_lines.append( QLineF(sceneRect.left(), y, sceneRect.right(), y)) # store alternate color if alt: alt_rect = QRectF(sceneRect.left(), y, sceneRect.width(), ydelta) h_alt.append(alt_rect) # store the vertical information h_labels.append((y, ydelta, ylabels[i])) axis_top = y y -= ydelta alt = not alt # assign the build data self._buildData['grid_h_lines'] = h_lines self._buildData['grid_h_alt'] = h_alt self._buildData['grid_h_labels'] = h_labels self._buildData['grid_v_lines'] = v_lines self._buildData['grid_v_alt'] = v_alt self._buildData['grid_v_labels'] = v_labels self._buildData['grid_rect'] = sceneRect self._buildData['axis_rect'] = QRectF(axis_lft, axis_top, axis_rht - axis_lft, axis_bot - axis_top) scene.setSceneRect(sceneRect) return sceneRect
def layout(self, scene, nodes, center=None, padX=None, padY=None, direction=None, animationGroup=None): """ Lays out the nodes for this scene based on a block layering algorithm. :param scene | <XNodeScene> nodes | [<XNode>, ..] center | <QPointF> || None padX | <int> || None padY | <int> || None direction | <Qt.Direction> animationGroup | <QAnimationGroup> || None :return {<XNode>: <QRectF>, ..} | new rects per affected node """ nodes = filter(lambda x: x is not None and x.isVisible(), nodes) # make sure we have at least 1 node, otherwise, it is already laid out if not nodes or len(nodes) == 1: return {} # calculate the default padding based on the scene if padX == None: if direction == Qt.Vertical: padX = 2 * scene.cellWidth() else: padX = 4 * scene.cellWidth() if padY == None: if direction == Qt.Vertical: padY = 4 * scene.cellHeight() else: padY = 2 * scene.cellWidth() # step 1: create a mapping of the connections connection_map = self.connectionMap(scene, nodes) # step 2: organize the nodes into layers based on their connection chain layers = self.generateLayers(scene, nodes, connection_map) # step 3: calculate the total dimensions for the layout bounds = QRectF() # step 3.1: compare the nodes together that have common connections layer_widths = [] layer_heights = [] node_heights = {} node_widths = {} for layer_index, layer in enumerate(layers): layer_w = 0 layer_h = 0 layer_node_w = [] layer_node_h = [] for node in layer: rect = node.rect() layer_node_w.append(rect.width()) layer_node_h.append(rect.height()) if direction == Qt.Vertical: layer_w += rect.width() layer_h = max(rect.height(), layer_h) else: layer_w = max(rect.width(), layer_w) layer_h += rect.height() # update the bounding area if direction == Qt.Vertical: layer_w += padX * 1 - len(layer) bounds.setWidth(max(layer_w, bounds.width())) bounds.setHeight(bounds.height() + layer_h) else: layer_h += padY * 1 - len(layer) bounds.setWidth(bounds.width() + layer_w) bounds.setHeight(max(layer_h, bounds.height())) node_widths[layer_index] = layer_node_w node_heights[layer_index] = layer_node_h layer_widths.append(layer_w) layer_heights.append(layer_h) if not center: center = scene.sceneRect().center() w = bounds.width() h = bounds.height() bounds.setX(center.x() - bounds.width() / 2.0) bounds.setY(center.y() - bounds.height() / 2.0) bounds.setWidth(w) bounds.setHeight(h) # step 4: assign positions for each node by layer processed_nodes = {} layer_grps = [(i, layer) for i, layer in enumerate(layers)] layer_grps.sort(key=lambda x: len(x[1])) used_rects = [n.sceneRect() for n in scene.nodes() if not n in nodes] for layer_index, layer in reversed(layer_grps): layer_width = layer_widths[layer_index] layer_height = layer_heights[layer_index] # determine the starting point for this layer if direction == Qt.Vertical: offset = layer_index * padY + sum(layer_heights[:layer_index]) point = QPointF(bounds.x(), offset + bounds.y()) else: offset = layer_index * padX + sum(layer_widths[:layer_index]) point = QPointF(offset + bounds.x(), bounds.y()) # assign node positions based on existing connections for node_index, node in enumerate(layer): max_, min_ = (None, None) inputs, outputs = connection_map[node] for connected_node in inputs + outputs: if not connected_node in processed_nodes: continue npos = processed_nodes[connected_node] nrect = connected_node.rect() rect = QRectF(npos.x(), npos.y(), nrect.width(), nrect.height()) if direction == Qt.Vertical: if min_ is None: min_ = rect.left() min_ = min(rect.left(), min_) max_ = max(rect.right(), max_) else: if min_ is None: min_ = rect.top() min_ = min(rect.top(), min_) max_ = max(rect.bottom(), max_) if direction == Qt.Vertical: off_x = 0 off_y = (layer_height - node.rect().height()) / 2.0 start_x = (bounds.width() - layer_width) start_y = 0 else: off_x = (layer_width - node.rect().width()) / 2.0 off_y = 0 start_x = 0 start_y = (bounds.height() - layer_height) # align against existing nodes point_x = -1 point_y = -1 offset = 0 before = True found_point = True new_rect = QRectF() while found_point: if not None in (min_, max_): if direction == Qt.Vertical: off_x = (max_ - min_)/2.0 - node.rect().width()/2.0 if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = min_ + off_x point_y = point.y() + off_y else: off_y = (max_ - min_)/2.0 - node.rect().height()/2.0 if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = min_ + off_y # otherwise, align based on its position in the layer else: if direction == Qt.Vertical: off_x = sum(node_widths[layer_index][:node_index]) off_x += node_index * padX off_x += start_x if before: off_x -= offset offset += node.rect().width() + padX else: off_x += offset point_x = point.x() + off_x point_y = point.y() + off_y else: off_y = sum(node_heights[layer_index][:node_index]) off_y += node_index * padY off_y += start_y if before: off_y -= offset offset += node.rect().height() + padY else: off_y += offset point_x = point.x() + off_x point_y = point.y() + off_y # determine if we've already used this point before before = not before found_point = False orect = node.rect() new_rect = QRectF(point_x, point_y, orect.width(), orect.height()) for used_rect in used_rects: if used_rect.intersects(new_rect): found_point = True break used_rects.append(new_rect) if not animationGroup: node.setPos(point_x, point_y) else: anim = XNodeAnimation(node, 'setPos') anim.setStartValue(node.pos()) anim.setEndValue(QPointF(point_x, point_y)) animationGroup.addAnimation(anim) processed_nodes[node] = QPointF(point_x, point_y) if self._testing: QApplication.processEvents() time.sleep(0.5) return processed_nodes