Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions DataPlotly/gui/plot_settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@

from qgis.PyQt.QtGui import (
QFont,
QImage,
QPainter,
QColor
)
from qgis.PyQt.QtCore import (
Expand Down Expand Up @@ -1591,15 +1589,7 @@ def save_plot_as_image(self):

plot_file = QgsFileUtils.ensureFileNameHasExtension(plot_file, ['png'])

frame = self.plot_view.page().mainFrame()
self.plot_view.page().setViewportSize(frame.contentsSize())
# render image
image = QImage(self.plot_view.page().viewportSize(),
QImage.Format.Format_ARGB32)
painter = QPainter(image)
frame.render(painter)
painter.end()
image.save(plot_file)
self.plot_view.grab().save(plot_file)
if self.message_bar:
self.message_bar.pushSuccess(self.tr('DataPlotly'),
self.tr('Plot saved to <a href="{}">{}</a>').format(
Expand Down
98 changes: 70 additions & 28 deletions DataPlotly/layouts/plot_layout_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,22 @@
from qgis.PyQt.QtCore import (
Qt,
QCoreApplication,
QRectF,
QSize,
QTimer,
QUrl,
QEventLoop,
QTimer
)
from qgis.PyQt.QtGui import QPalette
from qgis.PyQt.QtWidgets import QGraphicsItem

from qgis.core import (
QgsLayoutItem,
QgsLayoutItemRegistry,
QgsLayoutItemAbstractMetadata,
QgsNetworkAccessManager,
QgsMessageLog,
QgsGeometry,
QgsPropertyCollection
)
# from qgis.PyQt.QtWebKitWidgets import QWebPage
from qgis.PyQt.QtWebEngineWidgets import QWebEngineView
from qgis.PyQt.QtWebEngineCore import QWebEngineSettings
from qgis.PyQt.QtWebEngineCore import QWebEnginePage

from DataPlotly.core.plot_settings import PlotSettings
from DataPlotly.core.plot_factory import PlotFactory, FilterRegion
Expand All @@ -38,12 +33,12 @@
ITEM_TYPE = QgsLayoutItemRegistry.ItemType.PluginItem + 1337


class LoggingWebPage(QWebEngineView):
class LoggingWebPage(QWebEnginePage):

def __init__(self, parent=None):
super().__init__(parent)

def javaScriptConsoleMessage(self, message, lineNumber, source):
def javaScriptConsoleMessage(self, level, message, lineNumber, source):
QgsMessageLog.logMessage(f'{source}:{lineNumber} {message}', 'DataPlotly')


Expand All @@ -58,18 +53,18 @@ def __init__(self, layout):
self.linked_map = None

self.web_page = LoggingWebPage(self)
self.web_page.setNetworkAccessManager(QgsNetworkAccessManager.instance())
self.web_page.setBackgroundColor(Qt.GlobalColor.transparent)

# This makes the background transparent. (copied from QgsLayoutItemLabel)
palette = self.web_page.palette()
palette.setBrush(QPalette.ColorRole.Base, Qt.GlobalColor.transparent)
self.web_page.setPalette(palette)
self.web_page.mainFrame().setZoomFactor(10.0)
self.web_page.mainFrame().setScrollBarPolicy(Qt.Orientation.Horizontal, Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.web_page.mainFrame().setScrollBarPolicy(Qt.Orientation.Vertical, Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.web_view = QWebEngineView()
self.web_view.setPage(self.web_page)
self.web_view.setAttribute(Qt.WidgetAttribute.WA_DontShowOnScreen)
self.web_view.setZoomFactor(10.0)
self.web_view.show()

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

self.sizePositionChanged.connect(self.refresh)
Expand All @@ -87,7 +82,7 @@ def calculate_html_units_to_layout_units(self):
# 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
return 8

def set_linked_map(self, map):
"""
Expand Down Expand Up @@ -155,7 +150,7 @@ def set_plot_settings(self, plot_id, settings):
self.invalidateCache()

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

if not self.layout().renderContext().isPreviewRender():
Expand All @@ -164,14 +159,14 @@ def draw(self, context):
while not self.html_loaded:
QCoreApplication.processEvents()

# 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)
if self._captured_pixmap and not self._captured_pixmap.isNull():
sx = self.rect().width() * context.renderContext().scaleFactor() / self._captured_pixmap.width()
sy = self.rect().height() * context.renderContext().scaleFactor() / self._captured_pixmap.height()
painter.scale(sx, sy)
painter.drawPixmap(0, 0, self._captured_pixmap)
painter.restore()

def create_plot(self):
Expand Down Expand Up @@ -218,11 +213,15 @@ def get_polygon_filter(self, index=0):
return polygon_filter, visible_features_only

def load_content(self):
import tempfile
self._loading = True
self.html_loaded = False
base_url = QUrl.fromLocalFile(self.layout().project().absoluteFilePath())
self.web_page.setViewportSize(QSize(int(self.rect().width()) * self.html_units_to_layout_units,
int(self.rect().height()) * self.html_units_to_layout_units))
self.web_page.mainFrame().setHtml(self.create_plot(), base_url)
self.web_view.resize(QSize(int(self.rect().width()) * self.html_units_to_layout_units,
int(self.rect().height()) * self.html_units_to_layout_units))
self._tmp_file = tempfile.NamedTemporaryFile(suffix='.html', delete=False)
self._tmp_file.write(self.create_plot().encode('utf-8'))
self._tmp_file.close()
self.web_page.load(QUrl.fromLocalFile(self._tmp_file.name))

def writePropertiesToElement(self, element, document, _) -> bool:
for plot_setting in self.plot_settings:
Expand Down Expand Up @@ -262,6 +261,49 @@ def finalizeRestoreFromXml(self):
self.set_linked_map(map)

def loading_html_finished(self):
self.web_page.runJavaScript("document.documentElement.style.overflow='hidden'")
self._render_retries = 0
js = """(function() {
var plot = document.querySelector('.js-plotly-plot');
if (plot && typeof Plotly !== 'undefined') {
Plotly.toImage(plot, {format: 'png', scale: 2}).then(function(dataUrl) {
window._capturedImage = dataUrl;
}).catch(function() {
window._capturedImage = '';
});
} else {
window._capturedImage = '';
}
})()"""
self.web_page.runJavaScript(js)
self._wait_for_image_capture()

def _wait_for_image_capture(self):
"""Poll until Plotly.toImage() has produced the image."""
self.web_page.runJavaScript(
'typeof window._capturedImage === "string"',
self._on_image_capture_check)

def _on_image_capture_check(self, ready):
self._render_retries += 1
if ready or self._render_retries >= 100:
self.web_page.runJavaScript(
'window._capturedImage || ""',
self._on_image_data_received)
else:
QTimer.singleShot(50, self._wait_for_image_capture)

def _on_image_data_received(self, data_url):
import base64
from qgis.PyQt.QtGui import QPixmap
if data_url and data_url.startswith('data:image'):
base64_data = data_url.split(',', 1)[1]
image_bytes = base64.b64decode(base64_data)
self._captured_pixmap = QPixmap()
self._captured_pixmap.loadFromData(image_bytes)
else:
self._captured_pixmap = None
self._loading = False
self.html_loaded = True
self.invalidateCache()
self.update()
Expand Down
Loading