def test_readwrite(self):
        """
        Test reading and writing plot settings from XML
        """
        doc = QDomDocument("properties")
        original = PlotSettings('test',
                                properties={
                                    'marker_width': 2,
                                    'marker_size': 5
                                },
                                layout={
                                    'title': 'my plot',
                                    'legend_orientation': 'v'
                                })
        elem = original.write_xml(doc)
        self.assertFalse(elem.isNull())

        res = PlotSettings('gg')
        # test reading a bad element
        bad_elem = QDomElement()
        self.assertFalse(res.read_xml(bad_elem))

        self.assertTrue(res.read_xml(elem))
        self.assertEqual(res.plot_type, original.plot_type)
        self.assertEqual(res.properties, original.properties)
        self.assertEqual(res.layout, original.layout)
Exemplo n.º 2
0
    def test_readwrite(self):
        """
        Test reading and writing plot settings from XML
        """
        doc = QDomDocument("properties")
        original = PlotSettings('test',
                                properties={
                                    'marker_width': 2,
                                    'marker_size': 5
                                },
                                layout={
                                    'title': 'my plot',
                                    'legend_orientation': 'v'
                                })
        original.data_defined_properties.setProperty(
            PlotSettings.PROPERTY_FILTER,
            QgsProperty.fromExpression('"ap">50'))
        original.data_defined_properties.setProperty(
            PlotSettings.PROPERTY_MARKER_SIZE,
            QgsProperty.fromExpression('5+6'))
        original.data_defined_properties.setProperty(
            PlotSettings.PROPERTY_COLOR, QgsProperty.fromExpression("'red'"))
        original.data_defined_properties.setProperty(
            PlotSettings.PROPERTY_STROKE_WIDTH,
            QgsProperty.fromExpression('12/2'))
        elem = original.write_xml(doc)
        self.assertFalse(elem.isNull())

        res = PlotSettings('gg')
        # test reading a bad element
        bad_elem = QDomElement()
        self.assertFalse(res.read_xml(bad_elem))

        self.assertTrue(res.read_xml(elem))
        self.assertEqual(res.plot_type, original.plot_type)
        self.assertEqual(res.properties, original.properties)
        self.assertEqual(res.layout, original.layout)
        self.assertEqual(
            res.data_defined_properties.property(PlotSettings.PROPERTY_FILTER),
            original.data_defined_properties.property(
                PlotSettings.PROPERTY_FILTER))
        self.assertEqual(
            res.data_defined_properties.property(
                PlotSettings.PROPERTY_MARKER_SIZE),
            original.data_defined_properties.property(
                PlotSettings.PROPERTY_MARKER_SIZE))
        self.assertEqual(
            res.data_defined_properties.property(PlotSettings.PROPERTY_COLOR),
            original.data_defined_properties.property(
                PlotSettings.PROPERTY_COLOR))
        self.assertEqual(
            res.data_defined_properties.property(
                PlotSettings.PROPERTY_STROKE_WIDTH),
            original.data_defined_properties.property(
                PlotSettings.PROPERTY_STROKE_WIDTH))
class PlotLayoutItem(QgsLayoutItem):
    def __init__(self, layout):
        super().__init__(layout)
        self.setCacheMode(QGraphicsItem.NoCache)
        self.plot_settings = PlotSettings()
        self.web_page = LoggingWebPage(self)
        self.web_page.setNetworkAccessManager(
            QgsNetworkAccessManager.instance())

        # This makes the background transparent. (copied from QgsLayoutItemLabel)
        palette = self.web_page.palette()
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.web_page.setPalette(palette)
        self.web_page.mainFrame().setZoomFactor(10.0)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Horizontal,
                                                     Qt.ScrollBarAlwaysOff)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Vertical,
                                                     Qt.ScrollBarAlwaysOff)

        self.web_page.loadFinished.connect(self.loading_html_finished)
        self.html_loaded = False
        self.html_units_to_layout_units = self.calculate_html_units_to_layout_units(
        )

        self.sizePositionChanged.connect(self.refresh)

    def type(self):
        return ITEM_TYPE

    def icon(self):
        return GuiUtils.get_icon('dataplotly.svg')

    def calculate_html_units_to_layout_units(self):
        if not self.layout():
            return 1

        # Hm - why is this? Something internal in Plotly which is auto-scaling the html content?
        # we may need to expose this as a "scaling" setting

        return 72

    def set_plot_settings(self, settings):
        """
        Sets the plot settings to show in the item
        """
        self.plot_settings = settings
        self.html_loaded = False
        self.invalidateCache()

    def draw(self, context):
        if not self.html_loaded:
            self.load_content()

        # almost a direct copy from QgsLayoutItemLabel!
        painter = context.renderContext().painter()
        painter.save()

        # painter is scaled to dots, so scale back to layout units
        painter.scale(
            context.renderContext().scaleFactor() /
            self.html_units_to_layout_units,
            context.renderContext().scaleFactor() /
            self.html_units_to_layout_units)
        self.web_page.mainFrame().render(painter)
        painter.restore()

    def create_plot(self):
        factory = PlotFactory(self.plot_settings)
        config = {'displayModeBar': False, 'staticPlot': True}
        return factory.build_html(config)

    def load_content(self):
        self.html_loaded = False
        base_url = QUrl.fromLocalFile(
            self.layout().project().absoluteFilePath())
        self.web_page.setViewportSize(
            QSize(self.rect().width() * self.html_units_to_layout_units,
                  self.rect().height() * self.html_units_to_layout_units))
        self.web_page.mainFrame().setHtml(self.create_plot(), base_url)

    def writePropertiesToElement(self, element, document, _):
        element.appendChild(self.plot_settings.write_xml(document))
        return True

    def readPropertiesFromElement(self, element, document, context):
        res = self.plot_settings.read_xml(element.firstChildElement('Option'))
        self.html_loaded = False
        self.invalidateCache()
        return res

    def loading_html_finished(self):
        self.html_loaded = True
        self.invalidateCache()
        self.update()

    def refresh(self):
        super().refresh()
        self.html_loaded = False
        self.invalidateCache()
Exemplo n.º 4
0
class PlotLayoutItem(QgsLayoutItem):
    def __init__(self, layout):
        super().__init__(layout)
        self.setCacheMode(QGraphicsItem.NoCache)
        self.plot_settings = PlotSettings()
        self.linked_map_uuid = ''
        self.linked_map = None

        self.filter_by_map = False
        self.filter_by_atlas = False

        self.web_page = LoggingWebPage(self)
        self.web_page.setNetworkAccessManager(
            QgsNetworkAccessManager.instance())

        # This makes the background transparent. (copied from QgsLayoutItemLabel)
        palette = self.web_page.palette()
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.web_page.setPalette(palette)
        self.web_page.mainFrame().setZoomFactor(10.0)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Horizontal,
                                                     Qt.ScrollBarAlwaysOff)
        self.web_page.mainFrame().setScrollBarPolicy(Qt.Vertical,
                                                     Qt.ScrollBarAlwaysOff)

        self.web_page.loadFinished.connect(self.loading_html_finished)
        self.html_loaded = False
        self.html_units_to_layout_units = self.calculate_html_units_to_layout_units(
        )

        self.sizePositionChanged.connect(self.refresh)

    def type(self):
        return ITEM_TYPE

    def icon(self):
        return GuiUtils.get_icon('dataplotly.svg')

    def calculate_html_units_to_layout_units(self):
        if not self.layout():
            return 1

        # Hm - why is this? Something internal in Plotly which is auto-scaling the html content?
        # we may need to expose this as a "scaling" setting

        return 72

    def set_linked_map(self, map):
        """
        Sets the map linked to the plot item
        """
        if self.linked_map == map:
            return

        self.disconnect_current_map()

        self.linked_map = map
        self.linked_map.extentChanged.connect(self.map_extent_changed)
        self.linked_map.mapRotationChanged.connect(self.map_extent_changed)
        self.linked_map.destroyed.connect(self.disconnect_current_map)

    def disconnect_current_map(self):
        if not self.linked_map:
            return

        try:
            self.linked_map.extentChanged.disconnect(self.map_extent_changed)
            self.linked_map.mapRotationChanged.disconnect(
                self.map_extent_changed)
            self.linked_map.destroyed.disconnect(self.disconnect_current_map)
        except RuntimeError:
            # c++ object already gone!
            pass
        self.linked_map = None

    def set_plot_settings(self, settings):
        """
        Sets the plot settings to show in the item
        """
        self.plot_settings = settings
        self.html_loaded = False
        self.invalidateCache()

    def draw(self, context):
        if not self.html_loaded:
            self.load_content()

        # almost a direct copy from QgsLayoutItemLabel!
        painter = context.renderContext().painter()
        painter.save()

        # painter is scaled to dots, so scale back to layout units
        painter.scale(
            context.renderContext().scaleFactor() /
            self.html_units_to_layout_units,
            context.renderContext().scaleFactor() /
            self.html_units_to_layout_units)
        self.web_page.mainFrame().render(painter)
        painter.restore()

    def create_plot(self):
        if self.linked_map and self.filter_by_map:
            polygon_filter = FilterRegion(
                QgsGeometry.fromQPolygonF(
                    self.linked_map.visibleExtentPolygon()),
                self.linked_map.crs())
            self.plot_settings.properties['visible_features_only'] = True
        elif self.filter_by_atlas and self.layout().reportContext().layer(
        ) and self.layout().reportContext().feature().isValid():
            polygon_filter = FilterRegion(
                self.layout().reportContext().currentGeometry(),
                self.layout().reportContext().layer().crs())
            self.plot_settings.properties['visible_features_only'] = True
        else:
            polygon_filter = None
            self.plot_settings.properties['visible_features_only'] = False

        factory = PlotFactory(self.plot_settings,
                              self,
                              polygon_filter=polygon_filter)
        config = {'displayModeBar': False, 'staticPlot': True}
        return factory.build_html(config)

    def load_content(self):
        self.html_loaded = False
        base_url = QUrl.fromLocalFile(
            self.layout().project().absoluteFilePath())
        self.web_page.setViewportSize(
            QSize(self.rect().width() * self.html_units_to_layout_units,
                  self.rect().height() * self.html_units_to_layout_units))
        self.web_page.mainFrame().setHtml(self.create_plot(), base_url)

    def writePropertiesToElement(self, element, document, _):
        element.appendChild(self.plot_settings.write_xml(document))
        element.setAttribute('filter_by_map', 1 if self.filter_by_map else 0)
        element.setAttribute('filter_by_atlas',
                             1 if self.filter_by_atlas else 0)
        element.setAttribute('linked_map',
                             self.linked_map.uuid() if self.linked_map else '')
        return True

    def readPropertiesFromElement(self, element, document, context):
        res = self.plot_settings.read_xml(element.firstChildElement('Option'))

        self.filter_by_map = bool(int(element.attribute('filter_by_map', '0')))
        self.filter_by_atlas = bool(
            int(element.attribute('filter_by_atlas', '0')))
        self.linked_map_uuid = element.attribute('linked_map')
        self.disconnect_current_map()

        self.html_loaded = False
        self.invalidateCache()
        return res

    def finalizeRestoreFromXml(self):
        # has to happen after ALL items have been restored
        if self.layout() and self.linked_map_uuid:
            self.disconnect_current_map()
            map = self.layout().itemByUuid(self.linked_map_uuid)
            if map:
                self.set_linked_map(map)

    def loading_html_finished(self):
        self.html_loaded = True
        self.invalidateCache()
        self.update()

    def refresh(self):
        super().refresh()
        self.html_loaded = False
        self.invalidateCache()

    def map_extent_changed(self):
        if not self.linked_map or not self.filter_by_map:
            return

        self.html_loaded = False
        self.invalidateCache()

        self.update()