def test_dates(self): # pylint: disable=too-many-statements """ Test handling of dates """ # default plot settings settings = PlotSettings('scatter') # no source layer, fixed values must be used settings.source_layer_id = '' settings.x = [QDate(2020, 1, 1), QDate(2020, 2, 1), QDate(2020, 3, 1)] settings.y = [4, 5, 6] factory = PlotFactory(settings) # Build the dictionary from teh figure plot_dict = factory.build_plot_dict() # get the x and y fields as list for items in plot_dict['data']: # converts the QDate into strings x = [str(i.toPyDate()) for i in items['x']] y = items['y'] self.assertEqual(x, ["2020-01-01", "2020-02-01", "2020-03-01"]) self.assertEqual(y, [4, 5, 6]) settings.x = [ QDateTime(2020, 1, 1, 11, 21), QDateTime(2020, 2, 1, 0, 15), QDateTime(2020, 3, 1, 17, 23, 11) ] settings.y = [4, 5, 6] factory = PlotFactory(settings) # Build the dictionary from teh figure plot_dict = factory.build_plot_dict() # get the x and y fields as list for items in plot_dict['data']: # converts the QDate into strings x = [str(i.toString(Qt.ISODate)) for i in items['x']] y = items['y'] self.assertEqual(x, [ "2020-01-01T11:21:00", "2020-02-01T00:15:00", "2020-03-01T17:23:11" ]) self.assertEqual(y, [4, 5, 6])
def test_data_defined_stroke_color(self): """ Test data defined stroke color """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) settings = PlotSettings('scatter') settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'mg' settings.properties['in_color'] = 'red' factory = PlotFactory(settings) # should be empty, not using data defined size self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.data_defined_colors, []) class TestGenerator(QgsExpressionContextGenerator): # pylint: disable=missing-docstring, too-few-public-methods def createExpressionContext(self) -> QgsExpressionContext: # pylint: disable=missing-docstring, no-self-use context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('some_var', 10) context.appendScope(scope) context.appendScope(vl1.createExpressionContextScope()) return context generator = TestGenerator() settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_STROKE_COLOR, QgsProperty.fromExpression( 'case when round("ca"/@some_var)>10 then \'yellow\' else \'blue\' end' )) factory = PlotFactory(settings, context_generator=generator) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.data_defined_stroke_colors, [ '#0000ff', '#0000ff', '#0000ff', '#0000ff', '#0000ff', '#ffff00', '#ffff00', '#ffff00', '#ffff00' ])
def test_visible_features(self): """ Test filtering to visible features only """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) # not using visible features settings = PlotSettings('scatter') settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'ca' rect = QgsReferencedRectangle(QgsRectangle(10.1, 43.5, 10.8, 43.85), QgsCoordinateReferenceSystem(4326)) factory = PlotFactory(settings, visible_region=rect) spy = QSignalSpy(factory.plot_built) self.assertEqual(len(spy), 0) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual(factory.settings.y, [ 81.87, 22.26, 74.16, 35.05, 46.64, 126.73, 116.44, 108.25, 110.45 ]) settings.properties['visible_features_only'] = True factory = PlotFactory(settings, visible_region=rect) spy = QSignalSpy(factory.plot_built) self.assertEqual(factory.settings.x, [88, 350, 151, 203]) self.assertEqual(factory.settings.y, [22.26, 116.44, 108.25, 110.45]) factory.set_visible_region( QgsReferencedRectangle(QgsRectangle(10.6, 43.1, 12, 43.8), QgsCoordinateReferenceSystem(4326))) self.assertEqual(len(spy), 1) self.assertEqual(factory.settings.x, [98, 267, 319, 137]) self.assertEqual(factory.settings.y, [81.87, 74.16, 46.64, 126.73]) # with reprojection factory.set_visible_region( QgsReferencedRectangle( QgsRectangle(1167379, 5310986, 1367180, 5391728), QgsCoordinateReferenceSystem(3857))) self.assertEqual(len(spy), 2) self.assertEqual(factory.settings.x, [98, 267, 329, 319, 137]) self.assertEqual(factory.settings.y, [81.87, 74.16, 35.05, 46.64, 126.73])
def test_set_default_settings(self): """ Test setting dialog to a newly constructed settings object """ settings = PlotSettings() dialog = DataPlotlyPanelWidget(None, iface=IFACE) dialog.set_settings(settings) self.assertEqual(dialog.get_settings().plot_type, settings.plot_type) for k in settings.properties.keys(): if k in ['x', 'y', 'z', 'additional_hover_text', 'featureIds', 'featureBox', 'custom', 'in_color', 'marker_size']: continue print(k) self.assertEqual(dialog.get_settings().properties[k], settings.properties[k]) for k in settings.layout.keys(): print(k) self.assertEqual(dialog.get_settings().layout[k], settings.layout[k])
def test_data_defined_sizes(self): """ Test data defined marker sizes """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) settings = PlotSettings('scatter') settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'mg' settings.properties['marker_size'] = 15 factory = PlotFactory(settings) # should be empty, not using data defined size self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.data_defined_marker_sizes, []) class TestGenerator(QgsExpressionContextGenerator): # pylint: disable=missing-docstring, too-few-public-methods def createExpressionContext(self) -> QgsExpressionContext: # pylint: disable=missing-docstring, no-self-use context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('some_var', 10) context.appendScope(scope) context.appendScope(vl1.createExpressionContextScope()) return context generator = TestGenerator() settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_MARKER_SIZE, QgsProperty.fromExpression('round("ca"/@some_var *@value)')) factory = PlotFactory(settings, context_generator=generator) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual( factory.settings.data_defined_marker_sizes, [123.0, 33.0, 111.0, 53.0, 70.0, 190.0, 175.0, 162.0, 166.0])
def test_dates(self): # pylint: disable=too-many-statements """ Test handling of dates """ # default plot settings settings = PlotSettings('scatter') # no source layer, fixed values must be used settings.source_layer_id = '' settings.x = [QDate(2020, 1, 1), QDate(2020, 2, 1), QDate(2020, 3, 1)] settings.y = [4, 5, 6] factory = PlotFactory(settings) # Build the HTML/JavaScript for the plot plot_html = factory.build_html({}) # Find the plot specification in the HTML match = re.search(r'\[.*\]', plot_html) plot_dictionary = json.loads(match.group(0))[0] self.assertEqual(plot_dictionary['x'], ["2020-01-01", "2020-02-01", "2020-03-01"]) self.assertEqual(plot_dictionary['y'], [4, 5, 6]) settings.x = [ QDateTime(2020, 1, 1, 11, 21), QDateTime(2020, 2, 1, 0, 15), QDateTime(2020, 3, 1, 17, 23, 11) ] settings.y = [4, 5, 6] factory = PlotFactory(settings) # Build the HTML/JavaScript for the plot plot_html = factory.build_html({}) # Find the plot specification in the HTML match = re.search(r'\[.*\]', plot_html) plot_dictionary = json.loads(match.group(0))[0] self.assertEqual(plot_dictionary['x'], [ "2020-01-01T11:21:00", "2020-02-01T00:15:00", "2020-03-01T17:23:11" ]) self.assertEqual(plot_dictionary['y'], [4, 5, 6])
def test_read_write_project(self): """ Test reading and writing to project document """ # fake project document doc = QDomDocument("test") doc.appendChild(doc.createElement('qgis')) original = PlotSettings('test', properties={ 'marker_width': 2, 'marker_size': 5 }, layout={ 'title': 'my plot', 'legend_orientation': 'v' }) original.write_to_project(doc) res = PlotSettings('gg') res.read_from_project(doc) self.assertEqual(res.plot_type, original.plot_type) self.assertEqual(res.properties, original.properties) self.assertEqual(res.layout, original.layout)
def __init__(self, layout): super().__init__(layout) self.setCacheMode(QGraphicsItem.NoCache) self.plot_settings = [] self.plot_settings.append(PlotSettings()) self.linked_map_uuid = '' self.linked_map = None 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)
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()
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')) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_TITLE, QgsProperty.fromExpression("concat('my', '_title')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_LEGEND_TITLE, QgsProperty.fromExpression("concat('my', '_legend')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_TITLE, QgsProperty.fromExpression("concat('my', '_x_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_TITLE, QgsProperty.fromExpression("concat('my', '_y_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Z_TITLE, QgsProperty.fromExpression("concat('my', '_z_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MAX, QgsProperty.fromExpression("+1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MAX, QgsProperty.fromExpression("+1*10")) 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)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MAX)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MAX))
def test_read_write_file(self): """ Test reading and writing configuration to files """ 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')) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_TITLE, QgsProperty.fromExpression("concat('my', '_title')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_LEGEND_TITLE, QgsProperty.fromExpression("concat('my', '_legend')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_TITLE, QgsProperty.fromExpression("concat('my', '_x_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_TITLE, QgsProperty.fromExpression("concat('my', '_y_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Z_TITLE, QgsProperty.fromExpression("concat('my', '_z_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MAX, QgsProperty.fromExpression("+1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MAX, QgsProperty.fromExpression("+1*10")) path = os.path.join(tempfile.gettempdir(), 'plot_config.xml') self.assertFalse( original.write_to_file('/nooooooooo/nooooooooooo.xml')) self.assertTrue(original.write_to_file(path)) res = PlotSettings() self.assertFalse(res.read_from_file('/nooooooooo/nooooooooooo.xml')) self.assertTrue(res.read_from_file(path)) 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)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MAX)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MAX))
def test_read_write_project2(self): """ Test reading and writing to project, signals based """ p = QgsProject() 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')) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_TITLE, QgsProperty.fromExpression("concat('my', '_title')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_LEGEND_TITLE, QgsProperty.fromExpression("concat('my', '_legend')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_TITLE, QgsProperty.fromExpression("concat('my', '_x_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_TITLE, QgsProperty.fromExpression("concat('my', '_y_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Z_TITLE, QgsProperty.fromExpression("concat('my', '_z_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MAX, QgsProperty.fromExpression("+1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MAX, QgsProperty.fromExpression("+1*10")) self.test_read_write_project2_written = False def write(doc): self.test_read_write_project2_written = True original.write_to_project(doc) p.writeProject.connect(write) path = os.path.join(tempfile.gettempdir(), 'test_dataplotly_project.qgs') self.assertTrue(p.write(path)) for _ in range(100): QCoreApplication.processEvents() self.assertTrue(self.test_read_write_project2_written) p2 = QgsProject() res = PlotSettings('gg') self.test_read_write_project2_read = False def read(doc): res.read_from_project(doc) self.test_read_write_project2_read = True p2.readProject.connect(read) self.assertTrue(p2.read(path)) for _ in range(100): QCoreApplication.processEvents() self.assertTrue(self.test_read_write_project2_read) 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)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MAX)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MAX))
def test_read_write_project(self): """ Test reading and writing to project document """ # fake project document doc = QDomDocument("test") doc.appendChild(doc.createElement('qgis')) 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')) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_TITLE, QgsProperty.fromExpression("concat('my', '_title')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_LEGEND_TITLE, QgsProperty.fromExpression("concat('my', '_legend')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_TITLE, QgsProperty.fromExpression("concat('my', '_x_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_TITLE, QgsProperty.fromExpression("concat('my', '_y_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Z_TITLE, QgsProperty.fromExpression("concat('my', '_z_axis')")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MAX, QgsProperty.fromExpression("+1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MIN, QgsProperty.fromExpression("-1*10")) original.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MAX, QgsProperty.fromExpression("+1*10")) original.write_to_project(doc) res = PlotSettings('gg') res.read_from_project(doc) 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)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_LEGEND_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_X_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_TITLE)) self.assertEqual( res.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE), original.data_defined_properties.property( PlotSettings.PROPERTY_Z_TITLE)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_X_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_X_MAX)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MIN), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MIN)) self.assertEqual( res.data_defined_properties.property(PlotSettings.PROPERTY_Y_MAX), original.data_defined_properties.property( PlotSettings.PROPERTY_Y_MAX))
def test_data_defined_layout_properties(self): # pylint: disable=too-many-statements """ Test data defined stroke color """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) settings = PlotSettings('scatter') settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'mg' settings.layout['title'] = 'title' settings.layout['legend_title'] = 'legend_title' settings.layout['x_title'] = 'x_title' settings.layout['y_title'] = 'y_title' settings.layout['z_title'] = 'z_title' settings.layout['x_min'] = 0 settings.layout['x_max'] = 1 settings.layout['y_min'] = 0 settings.layout['y_max'] = 1 factory = PlotFactory(settings) # should be empty, not using data defined size self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.data_defined_title, '') self.assertEqual(factory.settings.data_defined_legend_title, '') self.assertEqual(factory.settings.data_defined_x_title, '') self.assertEqual(factory.settings.data_defined_y_title, '') self.assertEqual(factory.settings.data_defined_z_title, '') self.assertEqual(factory.settings.data_defined_x_min, None) self.assertEqual(factory.settings.data_defined_x_max, None) self.assertEqual(factory.settings.data_defined_y_min, None) self.assertEqual(factory.settings.data_defined_y_max, None) class TestGenerator(QgsExpressionContextGenerator): # pylint: disable=missing-docstring, too-few-public-methods def createExpressionContext(self) -> QgsExpressionContext: # pylint: disable=missing-docstring, no-self-use context = QgsExpressionContext() scope = QgsExpressionContextScope() scope.setVariable('some_var', 10) context.appendScope(scope) context.appendScope(vl1.createExpressionContextScope()) return context generator = TestGenerator() settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_TITLE, QgsProperty.fromExpression("concat('my', '_title_', @some_var)")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_LEGEND_TITLE, QgsProperty.fromExpression("concat('my', '_legend_', @some_var)")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_TITLE, QgsProperty.fromExpression("concat('my', '_x_axis_', @some_var)")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_TITLE, QgsProperty.fromExpression("concat('my', '_y_axis_', @some_var)")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_Z_TITLE, QgsProperty.fromExpression("concat('my', '_z_axis_', @some_var)")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MIN, QgsProperty.fromExpression("-1*@some_var")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_X_MAX, QgsProperty.fromExpression("+1*@some_var")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MIN, QgsProperty.fromExpression("-1*@some_var")) settings.data_defined_properties.setProperty( PlotSettings.PROPERTY_Y_MAX, QgsProperty.fromExpression("+1*@some_var")) factory = PlotFactory(settings, context_generator=generator) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual( factory.settings.y, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.data_defined_title, 'my_title_10') self.assertEqual(factory.settings.data_defined_legend_title, 'my_legend_10') self.assertEqual(factory.settings.data_defined_x_title, 'my_x_axis_10') self.assertEqual(factory.settings.data_defined_y_title, 'my_y_axis_10') self.assertEqual(factory.settings.data_defined_z_title, 'my_z_axis_10') self.assertEqual(factory.settings.data_defined_x_min, -10) self.assertEqual(factory.settings.data_defined_x_max, 10) self.assertEqual(factory.settings.data_defined_y_min, -10) self.assertEqual(factory.settings.data_defined_y_max, 10)
def test_values(self): # pylint: disable=too-many-statements """ Test value collection """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) # default plot settings settings = PlotSettings('scatter') # no source layer, fixed values must be used settings.source_layer_id = '' settings.x = [1, 2, 3] settings.y = [4, 5, 6] settings.z = [7, 8, 9] factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [1, 2, 3]) self.assertEqual(factory.settings.y, [4, 5, 6]) self.assertEqual(factory.settings.z, [7, 8, 9]) self.assertEqual(factory.settings.additional_hover_text, []) # use source layer settings.source_layer_id = vl1.id() # no source set => no values factory = PlotFactory(settings) self.assertEqual(factory.settings.x, []) self.assertEqual(factory.settings.y, []) self.assertEqual(factory.settings.z, []) self.assertEqual(factory.settings.additional_hover_text, []) settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'ca' factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual(factory.settings.y, [ 81.87, 22.26, 74.16, 35.05, 46.64, 126.73, 116.44, 108.25, 110.45 ]) self.assertEqual(factory.settings.z, []) self.assertEqual(factory.settings.additional_hover_text, []) # with z settings.properties['z_name'] = 'mg' factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [98, 88, 267, 329, 319, 137, 350, 151, 203]) self.assertEqual(factory.settings.y, [ 81.87, 22.26, 74.16, 35.05, 46.64, 126.73, 116.44, 108.25, 110.45 ]) self.assertEqual( factory.settings.z, [72.31, 86.03, 85.26, 81.11, 131.59, 95.36, 112.88, 80.55, 78.34]) self.assertEqual(factory.settings.additional_hover_text, []) # with expressions settings.properties['x_name'] = '"so4"/10' settings.properties[ 'y_name'] = 'case when "profm" >-16 then "ca" else "mg" end' settings.properties['z_name'] = 'case when $x < 10.5 then 1 else 0 end' factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [9.8, 8.8, 26.7, 32.9, 31.9, 13.7, 35.0, 15.1, 20.3]) self.assertEqual( factory.settings.y, [81.87, 86.03, 85.26, 35.05, 131.59, 95.36, 112.88, 108.25, 78.34]) self.assertEqual(factory.settings.z, [0, 1, 0, 0, 0, 0, 0, 1, 1]) self.assertEqual(factory.settings.additional_hover_text, []) # with some nulls settings.properties['x_name'] = '"so4"/10' settings.properties[ 'y_name'] = 'case when "profm" >-16 then "ca" else "mg" end' settings.properties[ 'z_name'] = 'case when $x < 10.5 then NULL else 1 end' factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [9.8, 26.7, 32.9, 31.9, 13.7, 35.0]) self.assertEqual(factory.settings.y, [81.87, 85.26, 35.05, 131.59, 95.36, 112.88]) self.assertEqual(factory.settings.z, [1, 1, 1, 1, 1, 1]) self.assertEqual(factory.settings.additional_hover_text, []) # with additional values settings.layout['additional_info_expression'] = 'id' factory = PlotFactory(settings) self.assertEqual(factory.settings.x, [9.8, 26.7, 32.9, 31.9, 13.7, 35.0]) self.assertEqual(factory.settings.y, [81.87, 85.26, 35.05, 131.59, 95.36, 112.88]) self.assertEqual(factory.settings.z, [1, 1, 1, 1, 1, 1]) self.assertEqual(factory.settings.additional_hover_text, [9, 7, 6, 5, 4, 3])
def test_selected_feature_values_dynamic(self): """ Test that factory proactively updates when a selection changes, when desired """ layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.shp') vl1 = QgsVectorLayer(layer_path, 'test_layer', 'ogr') vl1.setSubsetString('id < 10') self.assertTrue(vl1.isValid()) QgsProject.instance().addMapLayer(vl1) # not using selected features settings = PlotSettings('scatter') settings.properties['selected_features_only'] = False settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'ca' factory = PlotFactory(settings) spy = QSignalSpy(factory.plot_built) vl1.selectByIds([1, 3, 4]) self.assertEqual(len(spy), 0) # using selected features settings = PlotSettings('scatter') settings.properties['selected_features_only'] = True settings.source_layer_id = vl1.id() settings.properties['x_name'] = 'so4' settings.properties['y_name'] = 'ca' factory = PlotFactory(settings) spy = QSignalSpy(factory.plot_built) vl1.selectByIds([1]) self.assertEqual(len(spy), 1) self.assertEqual(factory.settings.x, [88]) self.assertEqual(factory.settings.y, [22.26]) vl1.selectByIds([1, 3, 4]) self.assertEqual(len(spy), 2) self.assertEqual(factory.settings.x, [88, 329, 319]) self.assertEqual(factory.settings.y, [22.26, 35.05, 46.64]) vl1.selectByIds([]) self.assertEqual(len(spy), 3) self.assertEqual(factory.settings.x, []) self.assertEqual(factory.settings.y, [])
def test_read_write_project(self): """ Test saving/restoring dialog state in project """ # print('read write project test') p = QgsProject.instance() dialog = DataPlotlyPanelWidget(None, override_iface=IFACE) dialog.set_plot_type('violin') # first, disable saving to project dialog.read_from_project = False dialog.save_to_project = False path = os.path.join(tempfile.gettempdir(), 'test_dataplotly_project.qgs') layer_path = os.path.join(os.path.dirname(__file__), 'test_layer.geojson') # create QgsVectorLayer from path and test validity vl = QgsVectorLayer(layer_path, 'test_layer', 'ogr') self.assertTrue(vl.isValid()) # print(dialog.layer_combo.currentLayer()) self.assertTrue(p.write(path)) res = PlotSettings() # def read(doc): # self.assertTrue(res.read_from_project(doc)) # self.assertEqual(res.plot_type, 'violin') # self.read_triggered = True p.clear() for _ in range(100): QCoreApplication.processEvents() self.assertTrue(p.read(path)) self.assertEqual(res.plot_type, 'scatter') # TODO - enable when dialog can restore properties and avoid this fragile test # # enable saving to project # dialog.save_to_project = True # dialog.read_from_project = True # self.assertTrue(p.write(path)) # for _ in range(100): # QCoreApplication.processEvents() # p.clear() # p.readProject.connect(read) # self.assertTrue(p.read(path)) # for _ in range(100): # QCoreApplication.processEvents() # self.assertTrue(self.read_triggered) # todo - test that dialog can restore properties, but requires the missing set_settings method dialog.x_combo.setExpression('"Ca"') dialog.layer_combo.setLayer(vl) dialog.x_combo.currentText() self.assertTrue(dialog.x_combo.expression(), '"Ca"')
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()
def processAlgorithm(self, parameters, context, feedback): """ :param parameters: :param context: """ layer = self.parameterAsSource(parameters, self.INPUT, context) fields = layer.fields() xfieldname = self.parameterAsString(parameters, self.XFIELD, context) yfieldname = self.parameterAsString(parameters, self.YFIELD, context) outputHtmlFile = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) outputJsonFile = self.parameterAsFileOutput(parameters, self.OUTPUT_JSON_FILE, context) plot_type = 'bar' plot_type_input = self.parameterAsInt(parameters, self.PLOT_TYPE, context) plot_type = self.PLOT_TYPE_OPTIONS[plot_type_input] plot_title = self.parameterAsString(parameters, self.PLOT_TITLE, context) in_color_input = self.parameterAsInt(parameters, self.IN_COLOR, context) in_color_hex = self.IN_COLOR_OPTIONS[in_color_input] in_color_html = self.parameterAsString(parameters, self.IN_COLOR_HTML, context) # Some controls msg = [] if plot_type in self.X_MANDATORY and not xfieldname: msg.append(self.tr("The chosen plot type needs a X field !")) if plot_type in self.Y_MANDATORY and not yfieldname: msg.append(self.tr("The chosen plot type needs a Y field !")) if msg: feedback.reportError(' '.join(msg)) raise QgsProcessingException(msg) # Build needed dictionary settings = PlotSettings(plot_type) properties = {} # Add X dimension x_title = '' if xfieldname: # get field index for x idxX = layer.fields().lookupField(xfieldname) # get list of values for x x_var = [ i[xfieldname] for i in layer.getFeatures(QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([idxX ])) ] fieldTypeX = fields[idxX].type() x_title = fields[idxX].alias() or xfieldname properties.x = x_var # Add Y dimension y_title = '' if yfieldname: # get field index for y idxY = layer.fields().lookupField(yfieldname) # get list of values for y y_var = [ i[yfieldname] for i in layer.getFeatures(QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([idxY ])) ] y_title = fields[idxY].alias() or yfieldname properties.y = y_var # Draw only markers for scatter plot if plot_type in ['scatter', 'polar']: properties['marker'] = 'markers' # Colours properties['in_color'] = in_color_html or in_color_hex or 'DodgerBlue' # Add layout layout = {'title': plot_title or layer.sourceName()} if plot_type in self.X_MANDATORY: layout['x_title'] = x_title if plot_type in self.Y_MANDATORY: layout['y_title'] = y_title settings = PlotSettings(plot_type, properties=properties, layout=layout) # Create plot instance factory = PlotFactory(settings) # Prepare results results = {self.OUTPUT_HTML_FILE: None, self.OUTPUT_JSON_FILE: None} # Save plot as HTML if outputHtmlFile: standalone_plot_path = factory.build_figure() if os.path.isfile(standalone_plot_path): # html file output copyfile(standalone_plot_path, outputHtmlFile) results[self.OUTPUT_HTML_FILE] = outputHtmlFile # Save plot as JSON if outputJsonFile: ojson = {'data': trace, 'layout': plot_layout} with codecs.open(outputJsonFile, 'w', encoding='utf-8') as f: f.write(json.dumps(ojson)) results[self.OUTPUT_JSON_FILE] = outputJsonFile return results