def rebuild( self, gridRect ): """ Rebuilds the tracker item. """ scene = self.scene() if ( not scene ): return self.setVisible(gridRect.contains(self.pos())) self.setZValue(100) path = QPainterPath() path.moveTo(0, 0) path.lineTo(0, gridRect.height()) tip = '' tip_point = None self._ellipses = [] items = scene.collidingItems(self) self._basePath = QPainterPath(path) for item in items: item_path = item.path() found = None for y in range(int(gridRect.top()), int(gridRect.bottom())): point = QPointF(self.pos().x(), y) if ( item_path.contains(point) ): found = QPointF(0, y - self.pos().y()) break if ( found ): path.addEllipse(found, 6, 6) self._ellipses.append(found) # update the value information value = scene.valueAt(self.mapToScene(found)) tip_point = self.mapToScene(found) hruler = scene.horizontalRuler() vruler = scene.verticalRuler() x_value = hruler.formatValue(value[0]) y_value = vruler.formatValue(value[1]) tip = '<b>x:</b> %s<br/><b>y:</b> %s' % (x_value, y_value) self.setPath(path) self.setVisible(True) # show the popup widget if ( tip ): anchor = XPopupWidget.Anchor.RightCenter widget = self.scene().chartWidget() tip_point = widget.mapToGlobal(widget.mapFromScene(tip_point)) XPopupWidget.showToolTip(tip, anchor = anchor, parent = widget, point = tip_point, foreground = QColor('blue'), background = QColor(148, 148, 255))
def rebuild(self): """ Rebuilds the dependency path for this item. """ scene = self.scene() if (not scene): return sourcePos = self.sourceItem().viewItem().pos() sourceRect = self.sourceItem().viewItem().rect() targetPos = self.targetItem().viewItem().pos() targetRect = self.targetItem().viewItem().rect() cellWidth = scene.ganttWidget().cellWidth() startX = sourcePos.x() + sourceRect.width() - (cellWidth / 2.0) startY = sourcePos.y() + (sourceRect.height() / 2.0) endX = targetPos.x() - 2 endY = targetPos.y() + (targetRect.height() / 2.0) path = QPainterPath() path.moveTo(startX, startY) path.lineTo(startX, endY) path.lineTo(endX, endY) a = QPointF(endX - 10, endY - 3) b = QPointF(endX, endY) c = QPointF(endX - 10, endY + 3) self._polygon = QPolygonF([a, b, c, a]) path.addPolygon(self._polygon) self.setPath(path)
def rebuild( self ): """ Rebuilds the dependency path for this item. """ scene = self.scene() if ( not scene ): return sourcePos = self.sourceItem().viewItem().pos() sourceRect = self.sourceItem().viewItem().rect() targetPos = self.targetItem().viewItem().pos() targetRect = self.targetItem().viewItem().rect() cellWidth = scene.ganttWidget().cellWidth() startX = sourcePos.x() + sourceRect.width() - (cellWidth / 2.0) startY = sourcePos.y() + (sourceRect.height() / 2.0) endX = targetPos.x() - 2 endY = targetPos.y() + (targetRect.height() / 2.0) path = QPainterPath() path.moveTo(startX, startY) path.lineTo(startX, endY) path.lineTo(endX, endY) a = QPointF(endX - 10, endY - 3) b = QPointF(endX, endY) c = QPointF(endX - 10, endY + 3) self._polygon = QPolygonF([a, b, c, a]) path.addPolygon(self._polygon) self.setPath(path)
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 rect = self.buildData('axis_rect') for dataset, item in items.items(): first = True pos = None home = None ellipses = [] path = QPainterPath() for value in dataset.values(): pos = self.pointAt(axes, value) ellipses.append(pos) if first: path.moveTo(pos) first = False else: path.lineTo(pos) item.setPath(path) item.setBuildData('ellipses', ellipses)
def borderPath(self): """ Returns the border path that will be drawn for this widget. :return <QPainterPath> """ path = QPainterPath() x = 1 y = 1 w = self.width() - 2 h = self.height() - 2 pad = self.popupPadding() anchor = self.anchor() # create a path for a top-center based popup if anchor == XPopupWidget.Anchor.TopCenter: path.moveTo(x, y + pad) path.lineTo(x + ((w/2) - pad), y + pad) path.lineTo(x + (w/2), y) path.lineTo(x + ((w/2) + pad), y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-left based popup elif anchor == XPopupWidget.Anchor.TopLeft: path.moveTo(x, y + pad) path.lineTo(x + pad, y) path.lineTo(x + 2 * pad, y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-right based popup elif anchor == XPopupWidget.Anchor.TopRight: path.moveTo(x, y + pad) path.lineTo(x + w - 2 * pad, y + pad) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a bottom-left based popup elif anchor == XPopupWidget.Anchor.BottomLeft: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + 2 * pad, y + h - pad) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a south based popup elif anchor == XPopupWidget.Anchor.BottomCenter: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + ((w/2) + pad), y + h - pad) path.lineTo(x + (w/2), y + h) path.lineTo(x + ((w/2) - pad), y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a bottom-right based popup elif anchor == XPopupWidget.Anchor.BottomRight: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x + w - 2 * pad, y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a right-top based popup elif anchor == XPopupWidget.Anchor.RightTop: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w - pad, y + 2 * pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-center based popup elif anchor == XPopupWidget.Anchor.RightCenter: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + ((h/2) - pad)) path.lineTo(x + w, y + (h/2)) path.lineTo(x + w - pad, y + ((h/2) + pad)) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-bottom based popup elif anchor == XPopupWidget.Anchor.RightBottom: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + h - 2 * pad) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a left-top based popup elif anchor == XPopupWidget.Anchor.LeftTop: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + 2 * pad) path.lineTo(x, y + pad) path.lineTo(x + pad, y) # create a path for an left-center based popup elif anchor == XPopupWidget.Anchor.LeftCenter: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + ((h/2) + pad)) path.lineTo(x, y + (h/2)) path.lineTo(x + pad, y + ((h/2) - pad)) path.lineTo(x + pad, y) # create a path for a left-bottom based popup elif anchor == XPopupWidget.Anchor.LeftBottom: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x + pad, y + h - 2 * pad) path.lineTo(x + pad, y) return path
def rebuildMonth(self): """ Rebuilds the current item in month mode. """ scene = self.scene() if (not scene): return start_date = self.dateStart() end_date = self.dateEnd() min_date = scene.minimumDate() max_date = scene.maximumDate() # make sure our item is visible if (not (min_date <= end_date and start_date <= max_date)): self.hide() self.setPath(QPainterPath()) return # make sure we have valid range information if (start_date < min_date): start_date = min_date start_inrange = False else: start_inrange = True if (max_date < end_date): end_date = max_date end_inrange = False else: end_inrange = True start_rect = scene.dateRect(start_date) end_rect = scene.dateRect(end_date) if (not (start_rect.isValid() and end_rect.isValid())): self.hide() return # rebuild an all day path path = QPainterPath() self.setPos(0, 0) pad = 2 offset = 18 height = 16 min_left = 10 max_right = scene.width() - 16 delta_h = start_rect.height() # draw the all day event if (self.isAllDay()): top = start_rect.top() left = start_rect.left() + 3 first = start_inrange while (top <= end_rect.top()): sub_path = QPainterPath() # calculate the end position if (end_rect.top() - 2 <= top and end_inrange): at_end = True right = end_rect.right() - pad else: at_end = False right = max_right if (first): sub_path.moveTo(left, top + offset) text_left = left + 4 else: sub_path.moveTo(left + height / 2, top + offset) text_left = left + height / 2 + 2 if (at_end): sub_path.lineTo(right, top + offset) sub_path.lineTo(right, top + offset + height) else: sub_path.lineTo(right - height / 2, top + offset) sub_path.lineTo(right, top + offset + height / 2) sub_path.lineTo(right - height / 2, top + offset + height) if (first): sub_path.lineTo(left, top + offset + height) sub_path.lineTo(left, top + offset) else: sub_path.lineTo(left + height / 2, top + offset + height) sub_path.lineTo(left, top + offset + height / 2) sub_path.lineTo(left + height / 2, top + offset) path.addPath(sub_path) data = (text_left, top + offset + 1, right, height, Qt.AlignLeft | Qt.AlignVCenter, self.title()) self._textData.append(data) left = min_left top += delta_h first = False else: text = '%s: (%s)' % (self.timeStart().toString('h:mm ap'), self.title()) font = scene.font() left = start_rect.left() + 2 * pad top = start_rect.top() + offset path.addText(left, top + height / 2, font, text) # setup the path for this item self.setPath(path) self.show() # make sure there are no collisions while (self.collidingItems()): self.setPos(self.pos().x(), self.pos().y() + height + 2) # hide the item if out of the visible scope if (delta_h - offset <= self.pos().y() + height): self.hide() break
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 rebuildDay( self ): """ Rebuilds the current item in day mode. """ scene = self.scene() if ( not scene ): return # calculate the base information start_date = self.dateStart() end_date = self.dateEnd() min_date = scene.minimumDate() max_date = scene.maximumDate() # make sure our item is visible if ( not (min_date <= end_date and start_date <= max_date)): self.hide() self.setPath(QPainterPath()) return # make sure we have valid range information if ( start_date < min_date ): start_date = min_date start_inrange = False else: start_inrange = True if ( max_date < end_date ): end_date = max_date end_inrange = False else: end_inrange = True # rebuild the path path = QPainterPath() self.setPos(0, 0) pad = 2 offset = 18 height = 16 # rebuild a timed item if ( not self.isAllDay() ): start_dtime = QDateTime(self.dateStart(), self.timeStart()) end_dtime = QDateTime(self.dateStart(), self.timeEnd().addSecs(-30*60)) start_rect = scene.dateTimeRect(start_dtime) end_rect = scene.dateTimeRect(end_dtime) left = start_rect.left() + pad top = start_rect.top() + pad right = start_rect.right() - pad bottom = end_rect.bottom() - pad path.moveTo(left, top) path.lineTo(right, top) path.lineTo(right, bottom) path.lineTo(left, bottom) path.lineTo(left, top) data = (left + 6, top + 6, right - left - 12, bottom - top - 12, Qt.AlignTop | Qt.AlignLeft, '%s - %s\n(%s)' % (self.timeStart().toString('h:mmap')[:-1], self.timeEnd().toString('h:mmap'), self.title())) self._textData.append(data) self.setPath(path) self.show()
def rebuildMonth( self ): """ Rebuilds the current item in month mode. """ scene = self.scene() if ( not scene ): return start_date = self.dateStart() end_date = self.dateEnd() min_date = scene.minimumDate() max_date = scene.maximumDate() # make sure our item is visible if ( not (min_date <= end_date and start_date <= max_date)): self.hide() self.setPath(QPainterPath()) return # make sure we have valid range information if ( start_date < min_date ): start_date = min_date start_inrange = False else: start_inrange = True if ( max_date < end_date ): end_date = max_date end_inrange = False else: end_inrange = True start_rect = scene.dateRect(start_date) end_rect = scene.dateRect(end_date) if ( not (start_rect.isValid() and end_rect.isValid()) ): self.hide() return # rebuild an all day path path = QPainterPath() self.setPos(0, 0) pad = 2 offset = 18 height = 16 min_left = 10 max_right = scene.width() - 16 delta_h = start_rect.height() # draw the all day event if ( self.isAllDay() ): top = start_rect.top() left = start_rect.left() + 3 first = start_inrange while ( top <= end_rect.top() ): sub_path = QPainterPath() # calculate the end position if ( end_rect.top() - 2 <= top and end_inrange ): at_end = True right = end_rect.right() - pad else: at_end = False right = max_right if ( first ): sub_path.moveTo(left, top + offset) text_left = left + 4 else: sub_path.moveTo(left + height / 2, top + offset) text_left = left + height / 2 + 2 if ( at_end ): sub_path.lineTo(right, top + offset) sub_path.lineTo(right, top + offset + height) else: sub_path.lineTo(right - height / 2, top + offset) sub_path.lineTo(right, top + offset + height / 2) sub_path.lineTo(right - height / 2, top + offset + height) if ( first ): sub_path.lineTo(left, top + offset + height) sub_path.lineTo(left, top + offset) else: sub_path.lineTo(left + height / 2, top + offset + height) sub_path.lineTo(left, top + offset + height / 2) sub_path.lineTo(left + height / 2, top + offset) path.addPath(sub_path) data = (text_left, top + offset + 1, right, height, Qt.AlignLeft | Qt.AlignVCenter, self.title()) self._textData.append(data) left = min_left top += delta_h first = False else: text = '%s: (%s)' % (self.timeStart().toString('h:mm ap'), self.title()) font = scene.font() left = start_rect.left() + 2 * pad top = start_rect.top() + offset path.addText(left, top + height / 2, font, text) # setup the path for this item self.setPath(path) self.show() # make sure there are no collisions while ( self.collidingItems() ): self.setPos(self.pos().x(), self.pos().y() + height + 2) # hide the item if out of the visible scope if ( delta_h - offset <= self.pos().y() + height ): self.hide() break
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 rect = self.buildData('axis_rect') half_size = self.maximumBarSize() / 2.0 for dataset, item in items.items(): path = QPainterPath() subpaths = [] for value in dataset.values(): pos = self.pointAt(axes, value) radius = min(rect.bottom() - pos.y(), 8) subpath = QPainterPath() # create a vertical bar graph if self.orientation() == Qt.Vertical: subpath.moveTo(pos.x() - half_size, rect.bottom()) subpath.lineTo(pos.x() - half_size, pos.y() + radius) subpath.quadTo(pos.x() - half_size, pos.y(), pos.x() - half_size + radius, pos.y()) subpath.lineTo(pos.x() + half_size - radius, pos.y()) subpath.quadTo(pos.x() + half_size, pos.y(), pos.x() + half_size, pos.y() + radius) subpath.lineTo(pos.x() + half_size, rect.bottom()) subpath.lineTo(pos.x() - half_size, rect.bottom()) # create a horizontal bar graph else: subpath.moveTo(rect.left(), pos.y() - half_size) subpath.lineTo(pos.x(), pos.y() - half_size) subpath.lineTo(pos.x(), pos.y() + half_size) subpath.lineTo(rect.left(), pos.y() + half_size) subpath.lineTo(rect.left(), pos.y() - half_size) path.addPath(subpath) subpaths.append(subpath) item.setPath(path) item.setBuildData('subpaths', subpaths)
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 borderPath(self): """ Returns the border path that will be drawn for this widget. :return <QPainterPath> """ path = QPainterPath() x = 1 y = 1 w = self.width() - 2 h = self.height() - 2 pad = self.popupPadding() anchor = self.anchor() # create a path for a top-center based popup if anchor == XPopupWidget.Anchor.TopCenter: path.moveTo(x, y + pad) path.lineTo(x + ((w / 2) - pad), y + pad) path.lineTo(x + (w / 2), y) path.lineTo(x + ((w / 2) + pad), y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-left based popup elif anchor == XPopupWidget.Anchor.TopLeft: path.moveTo(x, y + pad) path.lineTo(x + pad, y) path.lineTo(x + 2 * pad, y + pad) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a top-right based popup elif anchor == XPopupWidget.Anchor.TopRight: path.moveTo(x, y + pad) path.lineTo(x + w - 2 * pad, y + pad) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w, y + h) path.lineTo(x, y + h) path.lineTo(x, y + pad) # create a path for a bottom-left based popup elif anchor == XPopupWidget.Anchor.BottomLeft: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + 2 * pad, y + h - pad) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a south based popup elif anchor == XPopupWidget.Anchor.BottomCenter: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + ((w / 2) + pad), y + h - pad) path.lineTo(x + (w / 2), y + h) path.lineTo(x + ((w / 2) - pad), y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a bottom-right based popup elif anchor == XPopupWidget.Anchor.BottomRight: path.moveTo(x, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x + w - 2 * pad, y + h - pad) path.lineTo(x, y + h - pad) path.lineTo(x, y) # create a path for a right-top based popup elif anchor == XPopupWidget.Anchor.RightTop: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w, y + pad) path.lineTo(x + w - pad, y + 2 * pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-center based popup elif anchor == XPopupWidget.Anchor.RightCenter: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + ((h / 2) - pad)) path.lineTo(x + w, y + (h / 2)) path.lineTo(x + w - pad, y + ((h / 2) + pad)) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a right-bottom based popup elif anchor == XPopupWidget.Anchor.RightBottom: path.moveTo(x, y) path.lineTo(x + w - pad, y) path.lineTo(x + w - pad, y + h - 2 * pad) path.lineTo(x + w, y + h - pad) path.lineTo(x + w - pad, y + h) path.lineTo(x, y + h) path.lineTo(x, y) # create a path for a left-top based popup elif anchor == XPopupWidget.Anchor.LeftTop: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + 2 * pad) path.lineTo(x, y + pad) path.lineTo(x + pad, y) # create a path for an left-center based popup elif anchor == XPopupWidget.Anchor.LeftCenter: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x + pad, y + ((h / 2) + pad)) path.lineTo(x, y + (h / 2)) path.lineTo(x + pad, y + ((h / 2) - pad)) path.lineTo(x + pad, y) # create a path for a left-bottom based popup elif anchor == XPopupWidget.Anchor.LeftBottom: path.moveTo(x + pad, y) path.lineTo(x + w, y) path.lineTo(x + w, y + h) path.lineTo(x + pad, y + h) path.lineTo(x, y + h - pad) path.lineTo(x + pad, y + h - 2 * pad) path.lineTo(x + pad, y) return path
def rebuildDay(self): """ Rebuilds the current item in day mode. """ scene = self.scene() if (not scene): return # calculate the base information start_date = self.dateStart() end_date = self.dateEnd() min_date = scene.minimumDate() max_date = scene.maximumDate() # make sure our item is visible if (not (min_date <= end_date and start_date <= max_date)): self.hide() self.setPath(QPainterPath()) return # make sure we have valid range information if (start_date < min_date): start_date = min_date start_inrange = False else: start_inrange = True if (max_date < end_date): end_date = max_date end_inrange = False else: end_inrange = True # rebuild the path path = QPainterPath() self.setPos(0, 0) pad = 2 offset = 18 height = 16 # rebuild a timed item if (not self.isAllDay()): start_dtime = QDateTime(self.dateStart(), self.timeStart()) end_dtime = QDateTime(self.dateStart(), self.timeEnd().addSecs(-30 * 60)) start_rect = scene.dateTimeRect(start_dtime) end_rect = scene.dateTimeRect(end_dtime) left = start_rect.left() + pad top = start_rect.top() + pad right = start_rect.right() - pad bottom = end_rect.bottom() - pad path.moveTo(left, top) path.lineTo(right, top) path.lineTo(right, bottom) path.lineTo(left, bottom) path.lineTo(left, top) data = (left + 6, top + 6, right - left - 12, bottom - top - 12, Qt.AlignTop | Qt.AlignLeft, '%s - %s\n(%s)' % (self.timeStart().toString('h:mmap')[:-1], self.timeEnd().toString('h:mmap'), self.title())) self._textData.append(data) self.setPath(path) self.show()
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 paintMilestone( self, painter ): """ Paints this item as the milestone look. :param painter | <QPainter> """ # generate the rect rect = self.rect() padding = self.padding() gantt = self.scene().ganttWidget() cell_w = gantt.cellWidth() cell_h = gantt.cellHeight() x = rect.width() - cell_w y = self.padding() w = cell_w h = rect.height() - padding - 2 # grab the color options color = self.color() alt_color = self.alternateColor() if ( self.isSelected() ): color = self.highlightColor() alt_color = self.alternateHighlightColor() # create the background brush gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, color) gradient.setColorAt(0.8, alt_color) gradient.setColorAt(1, color) painter.setPen(self.borderColor()) painter.setBrush(QBrush(gradient)) pen = painter.pen() pen.setWidthF(0.5) painter.setPen(pen) painter.setRenderHint( painter.Antialiasing ) path = QPainterPath() path.moveTo(x - cell_w / 3.0, y + h / 2.0) path.lineTo(x, y) path.lineTo(x + cell_w / 3.0, y + h / 2.0) path.lineTo(x, y + h) path.lineTo(x - cell_w / 3.0, y + h / 2.0) painter.drawPath(path)
def paintGroup( self, painter ): """ Paints this item as the group look. :param painter | <QPainter> """ # generate the rect rect = self.rect() padding = self.padding() gantt = self.scene().ganttWidget() cell_w = gantt.cellWidth() cell_h = gantt.cellHeight() x = 0 y = self.padding() w = rect.width() h = rect.height() - (padding + 1) # grab the color options color = self.color() alt_color = self.alternateColor() if ( self.isSelected() ): color = self.highlightColor() alt_color = self.alternateHighlightColor() # create the background brush gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, color) gradient.setColorAt(0.8, alt_color) gradient.setColorAt(1, color) painter.setPen(self.borderColor()) painter.setBrush(QBrush(gradient)) pen = painter.pen() pen.setWidthF(0.5) painter.setPen(pen) painter.setRenderHint( painter.Antialiasing ) path = QPainterPath() path.moveTo(x - cell_w / 4.0, y) path.lineTo(w + cell_w / 4.0, y) path.lineTo(w + cell_w / 4.0, y + h / 2.0) path.lineTo(w, h) path.lineTo(w - cell_w / 4.0, y + h / 2.0) path.lineTo(x + cell_w / 4.0, y + h / 2.0) path.lineTo(x, h) path.lineTo(x - cell_w / 4.0, y + h / 2.0) path.lineTo(x - cell_w / 4.0, y) painter.drawPath(path) # create the progress brush if ( self.showProgress() ): gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, self.progressColor()) gradient.setColorAt(0.8, self.alternateProgressColor()) gradient.setColorAt(1, self.progressColor()) prog_w = (w - 4) * (self._percentComplete/100.0) painter.setPen(Qt.NoPen) painter.setBrush(QBrush(gradient)) painter.drawRect(x, y, prog_w, y + h / 2.0) # draw the text on this item if ( self.text() ): painter.setPen(self.textColor()) painter.drawText(x, y, w, h, Qt.AlignCenter, self.text())
def paintMilestone(self, painter): """ Paints this item as the milestone look. :param painter | <QPainter> """ # generate the rect rect = self.rect() padding = self.padding() gantt = self.scene().ganttWidget() cell_w = gantt.cellWidth() cell_h = gantt.cellHeight() x = rect.width() - cell_w y = self.padding() w = cell_w h = rect.height() - padding - 2 # grab the color options color = self.color() alt_color = self.alternateColor() if (self.isSelected()): color = self.highlightColor() alt_color = self.alternateHighlightColor() # create the background brush gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, color) gradient.setColorAt(0.8, alt_color) gradient.setColorAt(1, color) painter.setPen(self.borderColor()) painter.setBrush(QBrush(gradient)) pen = painter.pen() pen.setWidthF(0.5) painter.setPen(pen) painter.setRenderHint(painter.Antialiasing) path = QPainterPath() path.moveTo(x - cell_w / 3.0, y + h / 2.0) path.lineTo(x, y) path.lineTo(x + cell_w / 3.0, y + h / 2.0) path.lineTo(x, y + h) path.lineTo(x - cell_w / 3.0, y + h / 2.0) painter.drawPath(path)
def paintGroup(self, painter): """ Paints this item as the group look. :param painter | <QPainter> """ # generate the rect rect = self.rect() padding = self.padding() gantt = self.scene().ganttWidget() cell_w = gantt.cellWidth() cell_h = gantt.cellHeight() x = 0 y = self.padding() w = rect.width() h = rect.height() - (padding + 1) # grab the color options color = self.color() alt_color = self.alternateColor() if (self.isSelected()): color = self.highlightColor() alt_color = self.alternateHighlightColor() # create the background brush gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, color) gradient.setColorAt(0.8, alt_color) gradient.setColorAt(1, color) painter.setPen(self.borderColor()) painter.setBrush(QBrush(gradient)) pen = painter.pen() pen.setWidthF(0.5) painter.setPen(pen) painter.setRenderHint(painter.Antialiasing) path = QPainterPath() path.moveTo(x - cell_w / 4.0, y) path.lineTo(w + cell_w / 4.0, y) path.lineTo(w + cell_w / 4.0, y + h / 2.0) path.lineTo(w, h) path.lineTo(w - cell_w / 4.0, y + h / 2.0) path.lineTo(x + cell_w / 4.0, y + h / 2.0) path.lineTo(x, h) path.lineTo(x - cell_w / 4.0, y + h / 2.0) path.lineTo(x - cell_w / 4.0, y) painter.drawPath(path) # create the progress brush if (self.showProgress()): gradient = QLinearGradient() gradient.setStart(0, 0) gradient.setFinalStop(0, h) gradient.setColorAt(0, self.progressColor()) gradient.setColorAt(0.8, self.alternateProgressColor()) gradient.setColorAt(1, self.progressColor()) prog_w = (w - 4) * (self._percentComplete / 100.0) painter.setPen(Qt.NoPen) painter.setBrush(QBrush(gradient)) painter.drawRect(x, y, prog_w, y + h / 2.0) # draw the text on this item if (self.text()): painter.setPen(self.textColor()) painter.drawText(x, y, w, h, Qt.AlignCenter, self.text())
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)