How to render Altair / Vega in a PyQt widget - pyqt

Is it possible to have Altair or Vega(-Lite) render to a PyQt widget, similar to Matplotlib supporting multiple backends? I know I can use a Qt WebView widget to render a web page with Vega-embed, but I want to prevent the overhead of having to serve this, even if locally.

The best option to visualize a plot with Altair is to use QWebEngineView since altair what is to create javascript code based on the instructions you set. IMHO the best solution is to obtain the html of the chart and set it in a QWebEngineView. In the following example I show how to do the above, in addition to enabling the characteristics of saving the image as svg or png, etc.
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
from io import StringIO
class WebEngineView(QtWebEngineWidgets.QWebEngineView):
def __init__(self, parent=None):
super().__init__(parent)
self.page().profile().downloadRequested.connect(self.onDownloadRequested)
self.windows = []
#QtCore.pyqtSlot(QtWebEngineWidgets.QWebEngineDownloadItem)
def onDownloadRequested(self, download):
if (
download.state()
== QtWebEngineWidgets.QWebEngineDownloadItem.DownloadRequested
):
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, self.tr("Save as"), download.path()
)
if path:
download.setPath(path)
download.accept()
def createWindow(self, type_):
if type_ == QtWebEngineWidgets.QWebEnginePage.WebBrowserTab:
window = QtWidgets.QMainWindow(self)
view = QtWebEngineWidgets.QWebEngineView(window)
window.resize(640, 480)
window.setCentralWidget(view)
window.show()
return view
def updateChart(self, chart, **kwargs):
output = StringIO()
chart.save(output, "html", **kwargs)
self.setHtml(output.getvalue())
if __name__ == "__main__":
import sys
import altair as alt
from vega_datasets import data
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow()
cars = data.cars()
chart = (
alt.Chart(cars)
.mark_bar()
.encode(x=alt.X("Miles_per_Gallon", bin=True), y="count()",)
.properties(title="A bar chart")
.configure_title(anchor="start")
)
view = WebEngineView()
view.updateChart(chart)
w.setCentralWidget(view)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Currently the only rendering backend for Altair is Vega-Embed, so rendering it in a PyQt widget will require running some Javascript engine. I suspect that Qt WebView is probably the best option.
If you're OK losing interactivity of charts, you could also use altair_saver as a backend to store a static PNG, SVG, or PDF of the chart and show it within a QtWidget.

Building on the great answer by eyllanesc, this is the version adapted for PyQt6:
from io import StringIO
from typing import Optional
from PyQt6 import QtCore
from PyQt6 import QtWidgets
from PyQt6.QtWebEngineCore import QWebEngineDownloadRequest
from PyQt6.QtWebEngineCore import QWebEnginePage
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QWidget
class WebEngineView(QWebEngineView):
def __init__(self, parent: Optional[QWidget] = None):
super().__init__(parent)
self.page().profile().downloadRequested.connect(self.onDownloadRequested)
self.windows = []
#QtCore.pyqtSlot(QWebEngineDownloadRequest)
def onDownloadRequested(self, download: QWebEngineDownloadRequest) -> None:
if (
download.state()
== QWebEngineDownloadRequest.DownloadState.DownloadRequested
):
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, self.tr("Save as"), download.downloadFileName()
)
if path:
download.setDownloadFileName(path)
download.accept()
def createWindow(
self, web_window_type: QWebEnginePage.WebWindowType
) -> Optional[QWebEngineView]:
if web_window_type == QWebEnginePage.WebWindowType.WebBrowserTab:
window = QtWidgets.QMainWindow(self)
view = QWebEngineView(window)
window.resize(640, 480)
window.setCentralWidget(view)
window.show()
return view
def set_chart(self, chart, **kwargs) -> None:
output = StringIO()
chart.save(output, "html", **kwargs)
self.setHtml(output.getvalue())

Related

How to translate ui file + several questions [duplicate]

I am trying to translate my small application written in pyside2/pyqt5 to several languages, for example, Chinese. After googling, I managed to change the main window to Chinese after select from the menu -> language -> Chinese. However, the pop up dialog from menu -> option still remains English version. It seems the translation info is not transferred to the dialog. How do I solve this?
Basically, I build two ui files in designer and convert to two python files:One mainui.py and one dialogui.py. I then convert the two python file into one *.ts file using
pylupdate5 -verbose mainui.py dialogui.py -ts zh_CN.ts
after that, in linguist input the translation words. I can see the items in the dialog, which means this information is not missing. Then I release the file as zh_CN.qm file. All this supporting file I attached below using google drive.
Supporting files for the question
The main file is as
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.actionChinese.triggered.connect(self.change_lang)
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
def change_lang(self):
trans = QtCore.QTranslator()
trans.load('zh_CN')
QtCore.QCoreApplication.instance().installTranslator(trans)
self.retranslateUi(self)
if __name__=='__main__':
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)
I think it should be a typical task because almost no application will only have a mainwindow.
You have to overwrite the changeEvent() method and call retranslateUi() when the event is of type QEvent::LanguageChange, on the other hand the QTranslator object must be a member of the class but it will be deleted and it will not exist when the changeEvent() method is called.
Finally assuming that the Language menu is used to establish only translations, a possible option is to establish the name of the .qm as data of the QActions and to use the triggered method of the QMenu as I show below:
from PySide2 import QtCore, QtGui, QtWidgets
from mainui import Ui_MainWindow
from dialogui import Ui_Dialog
class OptionsDialog(QtWidgets.QDialog,Ui_Dialog):
def __init__(self,parent):
super().__init__(parent)
self.setupUi(self)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(OptionsDialog, self).changeEvent(event)
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.m_translator = QtCore.QTranslator(self)
self.actionConfigure.triggered.connect(self.showdialog)
self.menuLanguage.triggered.connect(self.change_lang)
# set translation for each submenu
self.actionChinese.setData('zh_CN')
#QtCore.Slot()
def showdialog(self):
dlg = OptionsDialog(self)
dlg.exec_()
#QtCore.Slot(QtWidgets.QAction)
def change_lang(self, action):
QtCore.QCoreApplication.instance().removeTranslator(self.m_translator)
if self.m_translator.load(action.data()):
QtCore.QCoreApplication.instance().installTranslator(self.m_translator)
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.retranslateUi(self)
super(MainWindow, self).changeEvent(event)
if __name__=='__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
ret = app.exec_()
sys.exit(ret)

PyQt5 loading PDF file format into Qlabel or QWidget to display [duplicate]

I have tried adding the pdf.js viewer files in my project and it works in browsers like Chrome, Mozilla, Safari, etc, but it's not loading some pages in node-webkit and PyQt webkit.
I am trying to load the file using an iframe, like this:
<iframe src="/test/?file=/assets/pdf/example.pdf#page=3"> </iframe>
Below are some more up-to-date demo scripts for using pdf.js with PyQt4/QtWebKit or PyQt5/QtWebEngine. To try these, first download the latest stable version of pdf.js and unpack the zip file into a suitable location. (NB: if you're on Linux your distro may already have a pdf.js package, so that could be installed instead).
UPDATE:
As of Qt-5.13.0, it is also possible to use the built-in Chromium PDF Viewer with QWebEngineView:
import sys
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
PDF = 'file://path/to/my/sample.pdf'
class Window(QtWebEngineWidgets.QWebEngineView):
def __init__(self):
super().__init__()
self.settings().setAttribute(
QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled, True)
self.settings().setAttribute(
QtWebEngineWidgets.QWebEngineSettings.PdfViewerEnabled, True)
self.load(QtCore.QUrl.fromUserInput(PDF))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 800, 600)
window.show()
sys.exit(app.exec_())
PyQt5/QtWebEngine pdfjs script:
UPDATE:
NB: as of Aug 2022, it may be necessary to use the legacy build of pdfjs (i.e. the build for "older browsers" on the download page) to keep things working with PyQt5. The stable build should work okay with PyQt6, though.
import sys
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
PDFJS = 'file:///path/to/pdfjs-1.9.426-dist/web/viewer.html'
# PDFJS = 'file:///usr/share/pdf.js/web/viewer.html'
PDF = 'file:///path/to/my/sample.pdf'
class Window(QtWebEngineWidgets.QWebEngineView):
def __init__(self):
super().__init__()
self.load(QtCore.QUrl.fromUserInput('%s?file=%s' % (PDFJS, PDF)))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 800, 600)
window.show()
sys.exit(app.exec_())
PyQt4/QtWebKit pdfjs script:
import sys
from PyQt4 import QtCore, QtGui, QtWebKit
PDFJS = 'file:///path/to/pdfjs-1.9.426-dist/web/viewer.html'
# PDFJS = 'file:///usr/share/pdf.js/web/viewer.html'
PDF = 'file:///path/to/my/sample.pdf'
class Window(QtWebKit.QWebView):
def __init__(self):
super().__init__()
self.load(QtCore.QUrl.fromUserInput('%s?file=%s' % (PDFJS, PDF)))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 50, 800, 600)
window.show()
sys.exit(app.exec_())
I've found this thread over at the Qt Forums, where thebeast44 posted a snippet of Qt code answering your question. My translation to python is below.
You'll also need to unpack the res folder from the author's original code, I think he just modified the viewer... I've also attached said code here.
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtNetwork
from PyQt4 import QtWebKit
class PDFViewer(QtWebKit.QWebView):
pdf_viewer_page = 'res/pdf-viewer.html'
def __init__(self, parent=None):
super().__init__(parent)
self.settings = QtWebKit.QWebSettings.globalSettings()
self.settings.setAttribute(QtWebKit.QWebSettings.LocalContentCanAccessFileUrls, True )
self.settings.setAttribute(QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls, True )
self.settings.setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True )
nam = QtNetwork.QNetworkAccessManager()
page = QtWebKit.QWebPage(self)
page.setNetworkAccessManager(nam)
self.setPage(page)
self.loadFinished.connect(self.onLoadFinish)
self.setUrl(QtCore.QUrl(self.pdf_viewer_page))
def onLoadFinish(self, success):
if success:
self.page().mainFrame().evaluateJavaScript("init();")
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
viewer = PDFViewer(parent=None)
viewer.show()
sys.exit(app.exec_())
From PyQt5 v5.13 you can load PDF files with the chromium API. According to the documentation https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing this option is by default enabled.
This minimal example is adapted from Simple Browser
import sys
from pathlib import Path
from PyQt5 import QAxContainer
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLineEdit, QApplication
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.main_layout = QVBoxLayout(self)
self.qlineedit = QLineEdit()
self.qlineedit.returnPressed.connect(self.go_action)
self.main_layout.addWidget(self.qlineedit)
self.read_btn = QPushButton('Test')
self.read_btn.clicked.connect(self.go_action)
self.main_layout.addWidget(self.read_btn)
self.WebBrowser = QAxContainer.QAxWidget(self)
self.WebBrowser.setFocusPolicy(Qt.StrongFocus)
self.WebBrowser.setControl("{8856F961-340A-11D0-A96B-00C04FD705A2}")
self.main_layout.addWidget(self.WebBrowser)
def go_action(self):
# convert system path to web path
f = Path(self.qlineedit.text()).as_uri()
# load object
self.WebBrowser.dynamicCall('Navigate(const QString&)', f)
if __name__ == "__main__":
a = QApplication(sys.argv)
w = Main()
w.show()
sys.exit(a.exec_())
This example:

Reading frames from a folder and display in QGraphicsView in pyqt4

I am trying to read frame for a folder and display in QGraphics view.
import os
import PyQt4
from PyQt4 import QtCore, QtGui
dir = "frames"
for file in os.listdir(dir):
grview = QtGui.QGraphicsView()
scene = QtGui.QGraphicsScene()
#pixmap = QtGui.QPixmap(os.path.join(dir, file))
pixmap = QtGui.QPixmap(file)
item = QtGui.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
grview.setScene()
grview.show()
#self.scene.update()
The folder named "frames" contains jpg files and is in the same folder as the code. But still I am not able to read the frames.
I have also tried general display without graphicsview to check if it works using the code below but it too doesn't work.
import cv2
import os
import PyQt4
from PyQt4 import QtGui,QtCore
import matplotlib.pyplot as plt
from PIL import Image
def load_images_from_folder(folder):
images = []
for filename in os.listdir(folder):
img = cv2.imread(os.path.join(folder,filename))
if img is not None:
images.append(img)
return images
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
img = load_images_from_folder('/frames')
image = Image.open(img)
image.show()
I am not able to understand what am I missing. I want to display the images sequentially in QGraphicsView resembling a video and in some particular frame I have to select the object of interest by draw a rectangle around it which has to hold in the subsequent frames as well. Is the task i am attempting possible? if yes any help is appreciated
Sorry, but I have PyQt5.
Try it:
import os
import sys
from PyQt5 import Qt
#from PyQt4 import QtCore, QtGui
class MainWindow(Qt.QMainWindow):
def __init__(self, parent=None):
Qt.QMainWindow.__init__(self, parent)
self.scene = Qt.QGraphicsScene()
self.grview = Qt.QGraphicsView(self.scene)
self.setCentralWidget(self.grview)
dir = "frames"
self.listFiles = os.listdir(dir)
self.timer = Qt.QTimer(self)
self.n = 0
self.timer.timeout.connect(self.on_timeout)
self.timer.start(1000)
self.setGeometry(700, 150, 300, 300)
self.show()
def on_timeout(self):
if self.n < len(self.listFiles):
self.scene.clear()
file = self.listFiles[self.n]
pixmap = Qt.QPixmap("frames\{}".format(file)) # !!! "frames\{}"
item = Qt.QGraphicsPixmapItem(pixmap)
self.scene.addItem(item)
self.grview.setScene(self.scene) # !!! (self.scene)
self.n += 1
else:
self.timer.stop()
app = Qt.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())

one single scroll bar for two QTextEdit, pyqt4, python

How to make one single scroll bar for two QTextEdit, pyqt4, python. Or how to synchronize two scrollbars of two QTextEdit. For simultaneous scrolling texts.
Pyqt4, python.
Cross-connect the value changed signals of all the scrollbars:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit1 = QtGui.QTextEdit(self)
self.edit2 = QtGui.QTextEdit(self)
layout = QtGui.QHBoxLayout(self)
layout.addWidget(self.edit1)
layout.addWidget(self.edit2)
self.edit1.horizontalScrollBar().valueChanged.connect(
self.edit2.horizontalScrollBar().setValue)
self.edit1.verticalScrollBar().valueChanged.connect(
self.edit2.verticalScrollBar().setValue)
self.edit2.horizontalScrollBar().valueChanged.connect(
self.edit1.horizontalScrollBar().setValue)
self.edit2.verticalScrollBar().valueChanged.connect(
self.edit1.verticalScrollBar().setValue)
text = '\n'.join(name for name in dir(QtCore))
self.edit1.setText(text)
self.edit2.setText(text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 400)
window.show()
sys.exit(app.exec_())

PyQt5 Audio no response

I am trying to make an audio player based on pyqt5 . hovewer reading the examples at the site packages and visiting also the QAudioOutput strange peak sound at beginning in PyQt4 i couldn't make a step forward and it seems there is no audio output. Thanks in advance , this is my current code (python3.3)
import sys
from PyQt5 import QtGui, QtCore, QtWidgets, QtMultimedia
class Window(QtWidgets.QWidget):
def __init__(self, parent = None):
QtWidgets.QWidget.__init__(self, parent)
format = QtMultimedia.QAudioFormat()
format.setSampleRate(44100)
format.setSampleSize(16)
format.setChannelCount(1)
format.setCodec("audio/wav")
format.setByteOrder(QtMultimedia.QAudioFormat.LittleEndian)
format.setSampleType(QtMultimedia.QAudioFormat.SignedInt)
self.output = QtMultimedia.QAudioOutput(QtMultimedia.QAudioDeviceInfo.defaultOutputDevice(), format, self)
self.file=QtCore.QFile()
self.file.setFileName("data/voicemail/99dc6d529c0c5d6b2ec5cda7b26cc1bb9af58bd4774d161f66186f62bf3093cd.wav")
self.file.open(QtCore.QIODevice.ReadOnly)
self.file.seek(44)
self.output.start(self.file)
#self.file.close()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Resources