├── .gitignore
├── LICENSE.md
├── QtQuick_controls_v1
├── MainForm.ui.qml
├── QtQuickControls1.PNG
├── SubplotTool.qml
├── main.qml
├── mpl_qtquick1.bat
├── mpl_qtquick1.py
└── qml.qrc
├── QtQuick_controls_v2
├── COPYING-ICONS.txt
├── MainForm.ui.qml
├── QtQuickControls2.PNG
├── SubplotTool.qml
├── document-open.svg
├── help-about.svg
├── main.qml
├── mpl_qtquick2.bat
├── mpl_qtquick2.py
├── qml.qrc
└── window-close.svg
├── QtWidgets
├── QtWidgets_UI.PNG
├── mpl_qtwidgets.bat
└── mpl_qtwidgets.py
├── README.md
├── backend
├── backend_qtquick5
│ ├── Figure.qml
│ ├── FigureToolbar.qml
│ ├── SubplotTool.qml
│ ├── __init__.py
│ └── backend_qquick5agg.py
├── mpl_qquick.bat
├── mpl_qquick.py
├── mpl_qquick_toolbar.bat
└── mpl_qquick_toolbar.py
├── environment.yml
└── qt_mpl_data.csv
/.gitignore:
--------------------------------------------------------------------------------
1 | backend/backend_qtquick5/__pycache__
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Frédéric Collonval
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/QtQuick_controls_v1/MainForm.ui.qml:
--------------------------------------------------------------------------------
1 | import Backend 1.0
2 | import QtQuick 2.6
3 | import QtQuick.Controls 1.5
4 | import QtQuick.Layouts 1.3
5 | import QtQuick.Dialogs 1.2
6 |
7 | Item {
8 | width: 640
9 | height: 320
10 |
11 |
12 | RowLayout {
13 | id: hbox
14 | spacing: 5
15 | anchors.horizontalCenter: parent.horizontalCenter
16 | anchors.fill: parent
17 |
18 | ColumnLayout {
19 | spacing : 0
20 |
21 | FigureToolbar {
22 | id: mplView
23 | objectName : "figure"
24 | width : 480
25 | height: 320
26 |
27 | Layout.fillWidth: true
28 | Layout.fillHeight: true
29 |
30 | Layout.minimumWidth: 10
31 | Layout.minimumHeight: 10
32 | }
33 |
34 | MessageDialog {
35 | id: messageDialog
36 | }
37 |
38 | FileDialog {
39 | id: saveFileDialog
40 | title: "Choose a filename to save to"
41 | folder: mplView.defaultDirectory
42 | nameFilters: mplView.fileFilters
43 | selectedNameFilter: mplView.defaultFileFilter
44 | selectExisting: false
45 |
46 | onAccepted: {
47 | try{
48 | mplView.print_figure(fileUrl)
49 | }
50 | catch (error){
51 | messageDialog.title = "Error saving file"
52 | messageDialog.text = error
53 | messageDialog.icon = StandardIcon.Critical
54 | messageDialog.open()
55 | }
56 | }
57 | }
58 |
59 | SubplotTool {
60 | id: setMargin
61 |
62 | left.value: mplView.left
63 | right.value: mplView.right
64 | top.value: mplView.top
65 | bottom.value: mplView.bottom
66 |
67 | hspace.value: mplView.hspace
68 | wspace.value: mplView.wspace
69 |
70 | function initMargin() {
71 | // Init slider value
72 | setMargin.left.value = mplView.left
73 | setMargin.right.value = mplView.right
74 | setMargin.top.value = mplView.top
75 | setMargin.bottom.value = mplView.bottom
76 |
77 | setMargin.hspace.value = mplView.hspace
78 | setMargin.wspace.value = mplView.wspace
79 |
80 | // Invert parameter bindings
81 | mplView.left = Qt.binding(function() { return setMargin.left.value })
82 | mplView.right = Qt.binding(function() { return setMargin.right.value })
83 | mplView.top = Qt.binding(function() { return setMargin.top.value })
84 | mplView.bottom = Qt.binding(function() { return setMargin.bottom.value })
85 |
86 | mplView.hspace = Qt.binding(function() { return setMargin.hspace.value })
87 | mplView.wspace = Qt.binding(function() { return setMargin.wspace.value })
88 | }
89 |
90 | onReset: {
91 | mplView.reset_margin()
92 | setMargin.initMargin()
93 | }
94 |
95 | onTightLayout: {
96 | mplView.tight_layout()
97 | setMargin.initMargin()
98 | }
99 | }
100 |
101 |
102 | ToolBar {
103 | id: toolbar
104 |
105 | Layout.alignment: Qt.AlignLeft | Qt.Bottom
106 | Layout.fillWidth: true
107 |
108 | RowLayout {
109 | Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
110 | anchors.fill: parent
111 |
112 | ToolButton {
113 | id : home
114 |
115 | iconSource: "image://mplIcons/home"
116 |
117 | onClicked: {
118 | mplView.home()
119 | }
120 | }
121 |
122 | ToolButton {
123 | id : back
124 | iconSource: "image://mplIcons/back"
125 |
126 | onClicked: {
127 | mplView.back()
128 | }
129 | }
130 |
131 | ToolButton {
132 | id : forward
133 |
134 | iconSource: "image://mplIcons/forward"
135 |
136 | onClicked: {
137 | mplView.forward()
138 | }
139 | }
140 |
141 | // Fake separator
142 | Label {
143 | text : "|"
144 | }
145 |
146 |
147 | ExclusiveGroup {
148 | // Gather pan and zoom tools to make them auto-exclusive
149 | id: pan_zoom
150 | }
151 |
152 | ToolButton {
153 | id : pan
154 |
155 | iconSource: "image://mplIcons/move"
156 |
157 | exclusiveGroup: pan_zoom
158 | checkable: true
159 |
160 | onClicked: {
161 | mplView.pan()
162 | }
163 | }
164 |
165 | ToolButton {
166 | id : zoom
167 |
168 | iconSource: "image://mplIcons/zoom_to_rect"
169 |
170 | exclusiveGroup: pan_zoom
171 | checkable: true
172 |
173 | onClicked: {
174 | mplView.zoom()
175 | }
176 | }
177 |
178 | Label {
179 | text : "|"
180 | }
181 |
182 | ToolButton {
183 | id : subplots
184 | iconSource: "image://mplIcons/subplots"
185 |
186 | onClicked: {
187 | setMargin.initMargin()
188 | setMargin.open()
189 | }
190 | }
191 |
192 | ToolButton {
193 | id : save
194 |
195 | iconSource: "image://mplIcons/filesave"
196 |
197 | onClicked: {
198 | saveFileDialog.open()
199 | }
200 | }
201 | /*
202 | ToolButton {
203 | id : figureOptions
204 |
205 | iconSource: "image://mplIcons/qt4_editor_options"
206 |
207 | visible: mplView.figureOptions
208 |
209 | onClicked: {
210 | }
211 | }
212 | */
213 | Item {
214 | Layout.fillWidth: true
215 | }
216 |
217 | Label{
218 | id: locLabel
219 |
220 | Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
221 |
222 | text: mplView.message
223 | }
224 | }
225 | }
226 | }
227 |
228 | Connections {
229 | target: dataModel
230 | onDataChanged: {
231 | draw_mpl.update_figure()
232 | }
233 | }
234 |
235 | Rectangle {
236 | id: right
237 | width: 160
238 | Layout.alignment: Qt.AlignLeft | Qt.AlignTop
239 | Layout.fillHeight: true
240 |
241 | ColumnLayout {
242 | id: right_vbox
243 |
244 | spacing: 2
245 |
246 | Label {
247 | id: log_label
248 | text: qsTr("Data series:")
249 | }
250 |
251 | ListView {
252 | id: series_list_view
253 | width: 110
254 | height: 160
255 | Layout.fillWidth: true
256 | model: dataModel
257 | delegate: RowLayout {
258 | CheckBox {
259 | checked : selected
260 |
261 | onClicked: {
262 | selected = checked;
263 | }
264 | }
265 |
266 | Text {
267 | text: name
268 | anchors.verticalCenter: parent.verticalCenter
269 | }
270 | spacing: 10
271 | }
272 | }
273 |
274 | RowLayout {
275 | id: rowLayout1
276 | width: 100
277 | height: 100
278 |
279 | Label {
280 | id: spin_label1
281 | text: qsTr("X from")
282 | }
283 |
284 | SpinBox {
285 | id: from_spin
286 | value: draw_mpl.xFrom
287 | minimumValue: 0
288 | maximumValue: dataModel.lengthData - 1;
289 | enabled: series_list_view.count > 0;
290 | }
291 |
292 | Binding {
293 | target: draw_mpl
294 | property: "xFrom"
295 | value: from_spin.value
296 | }
297 |
298 | Label {
299 | id: spin_label2
300 | text: qsTr("to")
301 | }
302 |
303 | SpinBox {
304 | id: to_spin
305 | value: draw_mpl.xTo
306 | minimumValue: 0
307 | maximumValue: dataModel.lengthData - 1;
308 | enabled: series_list_view.count > 0;
309 | }
310 |
311 | Binding {
312 | target: draw_mpl
313 | property: "xTo"
314 | value: to_spin.value
315 | }
316 | }
317 |
318 | CheckBox {
319 | id: legend_cb
320 | text: qsTr("Show Legend")
321 | checked: draw_mpl.legend
322 | }
323 |
324 | Binding {
325 | target: draw_mpl
326 | property: "legend"
327 | value: legend_cb.checked
328 | }
329 | }
330 | }
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/QtQuick_controls_v1/QtQuickControls1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcollonval/matplotlib_qtquick_playground/21de5e9734e26ebc1dbd8d7fafc023248d9ce607/QtQuick_controls_v1/QtQuickControls1.PNG
--------------------------------------------------------------------------------
/QtQuick_controls_v1/SubplotTool.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.6
2 | import QtQuick.Layouts 1.2
3 | import QtQuick.Controls 1.0
4 | import QtQuick.Dialogs 1.2
5 |
6 | Dialog {
7 | id: subplotTool
8 |
9 | title: "Margins & spacing"
10 |
11 | property alias left: left_slider
12 | property alias right: right_slider
13 | property alias top: top_slider
14 | property alias bottom: bottom_slider
15 | property alias hspace: hspace_slider
16 | property alias wspace: wspace_slider
17 |
18 | signal reset
19 | signal tightLayout
20 |
21 | contentItem : ColumnLayout {
22 | anchors.fill: parent
23 |
24 | GroupBox {
25 | id: borders
26 | title: "Borders"
27 |
28 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
29 | Layout.fillWidth: true
30 | Layout.fillHeight: true
31 |
32 | GridLayout{
33 | columns: 3
34 | anchors.fill: parent
35 |
36 | Label {
37 | text: "top"
38 | }
39 | Slider{
40 | id: top_slider
41 | minimumValue: bottom_slider.value
42 | maximumValue: 1
43 | value: 1
44 | stepSize: 0.01
45 |
46 | Layout.fillWidth: true
47 | }
48 | Label{
49 | text: top_slider.value.toFixed(2)
50 | }
51 |
52 | Label {
53 | text: "bottom"
54 | }
55 | Slider{
56 | id: bottom_slider
57 | minimumValue: 0
58 | maximumValue: top_slider.value
59 | value: 0
60 | stepSize: 0.01
61 |
62 | Layout.fillWidth: true
63 | }
64 | Label{
65 | text: bottom_slider.value.toFixed(2)
66 | }
67 |
68 | Label {
69 | text: "left"
70 | }
71 | Slider{
72 | id: left_slider
73 | minimumValue: 0
74 | maximumValue: right_slider.value
75 | value: 0
76 | stepSize: 0.01
77 |
78 | Layout.fillWidth: true
79 | }
80 | Label{
81 | text: left_slider.value.toFixed(2)
82 | }
83 |
84 | Label {
85 | text: "right"
86 | }
87 | Slider{
88 | id: right_slider
89 | minimumValue: left_slider.value
90 | maximumValue: 1
91 | value: 1
92 | stepSize: 0.01
93 |
94 | Layout.fillWidth: true
95 | }
96 | Label{
97 | text: right_slider.value.toFixed(2)
98 | }
99 |
100 | }
101 | }
102 |
103 | GroupBox {
104 | id: spacings
105 | title: "Spacings"
106 |
107 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
108 | Layout.fillWidth: true
109 | Layout.fillHeight: true
110 |
111 | GridLayout{
112 | columns: 3
113 | anchors.fill: parent
114 |
115 | Label {
116 | text: "hspace"
117 | }
118 | Slider{
119 | id: hspace_slider
120 | minimumValue: 0
121 | maximumValue: 1
122 | value: 0
123 | stepSize: 0.01
124 |
125 | Layout.fillWidth: true
126 | }
127 | Label{
128 | text: hspace_slider.value.toFixed(2)
129 | }
130 |
131 | Label {
132 | text: "wspace"
133 | }
134 | Slider{
135 | id: wspace_slider
136 | minimumValue: 0
137 | maximumValue: 1
138 | value: 0
139 | stepSize: 0.01
140 |
141 | Layout.fillWidth: true
142 | }
143 | Label{
144 | text: wspace_slider.value.toFixed(2)
145 | }
146 | }
147 | }
148 |
149 | RowLayout {
150 | id: buttons
151 |
152 | Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
153 | Layout.fillWidth: true
154 |
155 | Button {
156 | id: tight_layout
157 | text: "Tight Layout"
158 |
159 | Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
160 |
161 | onClicked: {
162 | subplotTool.tightLayout()
163 | }
164 | }
165 |
166 | Item {
167 | Layout.fillWidth: true
168 | }
169 |
170 | Button {
171 | id: reset
172 | text: "Reset"
173 |
174 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
175 |
176 | onClicked: {
177 | subplotTool.reset()
178 | }
179 | }
180 |
181 | Button {
182 | id: close
183 | text: "Close"
184 |
185 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
186 |
187 | onClicked: {
188 | subplotTool.close()
189 | }
190 | }
191 | }
192 |
193 | }
194 | }
--------------------------------------------------------------------------------
/QtQuick_controls_v1/main.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.6
2 | import QtQuick.Controls 1.5
3 | import QtQuick.Dialogs 1.2
4 | import QtQuick.Layouts 1.0
5 | import QtQuick.Window 2.1
6 |
7 | ApplicationWindow {
8 | visible: true
9 | width: 640
10 | height: 335
11 | title: qsTr("Hello World")
12 |
13 | FileDialog {
14 | id: fileDialog
15 | nameFilters: ["CSV files (*.csv)", "All Files (*.*)"]
16 | onAccepted: {
17 | draw_mpl.filename = fileUrl
18 | }
19 | }
20 |
21 | menuBar: MenuBar {
22 | Menu {
23 | title: qsTr("&File")
24 | MenuItem {
25 | text: qsTr("&Load a file")
26 | onTriggered: {
27 | fileDialog.open()
28 | }
29 | }
30 | MenuItem {
31 | text: qsTr("&Quit")
32 | onTriggered: Qt.quit();
33 | }
34 | }
35 | Menu {
36 | title: qsTr("&Help")
37 |
38 | MenuItem{
39 | text: qsTr("&About")
40 | onTriggered: messageDialog.show("About the demo", draw_mpl.about)
41 | }
42 | }
43 | }
44 |
45 | MainForm {
46 | anchors.fill: parent
47 | }
48 |
49 | statusBar: Text {
50 | text: draw_mpl.statusText
51 | }
52 |
53 | MessageDialog {
54 | id: messageDialog
55 |
56 | function show(title, caption) {
57 | messageDialog.title = title;
58 | messageDialog.text = caption;
59 | messageDialog.open();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/QtQuick_controls_v1/mpl_qtquick1.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | CALL C:\Anaconda3\Scripts\activate.bat qtquick
3 | python %~dpn0.py %*
--------------------------------------------------------------------------------
/QtQuick_controls_v1/mpl_qtquick1.py:
--------------------------------------------------------------------------------
1 | """
2 | Series of data are loaded from a .csv file, and their names are
3 | displayed in a checkable list view. The user can select the series
4 | it wants from the list and plot them on a matplotlib canvas.
5 | Use the sample .csv file that comes with the script for an example
6 | of data series.
7 |
8 | [2016-11-05] Convert to QtQuick 2.0 - QtQuick Controls 1.0
9 | [2016-11-01] Update to PyQt5.6 and python 3.5
10 |
11 | Frederic Collonval (fcollonval@gmail.com)
12 |
13 | Inspired from the work of Eli Bendersky (eliben@gmail.com):
14 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
15 |
16 | License: MIT License
17 | Last modified: 2016-11-05
18 | """
19 | import sys, os, csv
20 | from PyQt5.QtCore import QAbstractListModel, QModelIndex, QObject, QSize, Qt, QUrl, QVariant, pyqtProperty, pyqtSlot, pyqtSignal
21 | from PyQt5.QtGui import QGuiApplication, QColor, QImage, QPixmap
22 | # from PyQt5.QtWidgets import QApplication
23 | from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
24 | from PyQt5.QtQuick import QQuickImageProvider
25 |
26 | import matplotlib
27 | matplotlib.use('Agg')
28 | # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
29 | # from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
30 | # from matplotlib.figure import Figure
31 | import matplotlib.pyplot as plt
32 |
33 | import numpy as np
34 |
35 | sys.path.append('../backend')
36 | from backend_qtquick5 import FigureCanvasQTAggToolbar, MatplotlibIconProvider
37 |
38 | class DataSerie(object):
39 |
40 | def __init__(self, name, data, selected=False):
41 | self._name = name
42 | self._data = data
43 | self._selected = selected
44 |
45 | def name(self):
46 | return self._name
47 |
48 | def selected(self):
49 | return self._selected
50 |
51 | def data(self):
52 | return self._data
53 |
54 | class DataSeriesModel(QAbstractListModel):
55 |
56 | # Define role enum
57 | SelectedRole = Qt.UserRole
58 | NameRole = Qt.UserRole + 1
59 | DataRole = Qt.UserRole + 2
60 |
61 | _roles = {
62 | SelectedRole : b"selected",
63 | NameRole : b"name",
64 | DataRole : b"data"
65 | }
66 |
67 | lengthDataChanged = pyqtSignal()
68 |
69 | def __init__(self, parent=None):
70 | QAbstractListModel.__init__(self, parent)
71 |
72 | self._data_series = list()
73 | self._length_data = 0
74 |
75 | @pyqtProperty(int, notify=lengthDataChanged)
76 | def lengthData(self):
77 | return self._length_data
78 |
79 | @lengthData.setter
80 | def lengthData(self, length):
81 | if self._length_data != length:
82 | self._length_data = length
83 | self.lengthDataChanged.emit()
84 |
85 | def roleNames(self):
86 | return self._roles
87 |
88 | def load_from_file(self, filename=None):
89 | self._data_series.clear()
90 | self._length_data = 0
91 |
92 | if filename:
93 | with open(filename, 'r') as f:
94 | for line in csv.reader(f):
95 | series = DataSerie(line[0],
96 | [i for i in map(int, line[1:])])
97 | self.add_data(series)
98 |
99 | def add_data(self, data_series):
100 | self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
101 | self._data_series.append(data_series)
102 | self.lengthData = max(self.lengthData, len(data_series.data()))
103 | self.endInsertRows()
104 |
105 | def rowCount(self, parent=QModelIndex()):
106 | return len(self._data_series)
107 |
108 | def data(self, index, role=Qt.DisplayRole):
109 | if(index.row() < 0 or index.row() >= len(self._data_series)):
110 | return QVariant()
111 |
112 | series = self._data_series[index.row()]
113 |
114 | if role == self.SelectedRole:
115 | return series.selected()
116 | elif role == self.NameRole:
117 | return series.name()
118 | elif role == self.DataRole:
119 | return series.data()
120 |
121 | return QVariant()
122 |
123 | def setData(self, index, value, role=Qt.EditRole):
124 | if(index.row() < 0 or index.row() >= len(self._data_series)):
125 | return False
126 |
127 | series = self._data_series[index.row()]
128 |
129 | if role == self.SelectedRole:
130 | series._selected = value
131 | self.dataChanged.emit(index, index, [role,])
132 | return True
133 |
134 | return False
135 |
136 | class Form(QObject):
137 |
138 | xFromChanged = pyqtSignal()
139 | xToChanged = pyqtSignal()
140 | legendChanged = pyqtSignal()
141 | statusTextChanged = pyqtSignal()
142 | stateChanged = pyqtSignal()
143 |
144 | def __init__(self, parent=None, data=None):
145 | QObject.__init__(self, parent)
146 |
147 | self._status_text = "Please load a data file"
148 |
149 | self._filename = ""
150 | self._x_from = 0
151 | self._x_to = 1
152 | self._legend = False
153 |
154 | # default dpi=80, so size = (480, 320)
155 | self._figure = None
156 | self.axes = None
157 |
158 | self._data = data
159 |
160 | @property
161 | def figure(self):
162 | return self._figure
163 |
164 | @figure.setter
165 | def figure(self, fig):
166 | self._figure = fig
167 | self._figure.set_facecolor('white')
168 | self.axes = self.figure.add_subplot(111)
169 |
170 | # Signal connection
171 | self.xFromChanged.connect(self._figure.canvas.draw_idle)
172 | self.xToChanged.connect(self._figure.canvas.draw_idle)
173 | self.legendChanged.connect(self._figure.canvas.draw_idle)
174 | self.stateChanged.connect(self._figure.canvas.draw_idle)
175 |
176 | @pyqtProperty('QString', notify=statusTextChanged)
177 | def statusText(self):
178 | return self._status_text
179 |
180 | @statusText.setter
181 | def statusText(self, text):
182 | if self._status_text != text:
183 | self._status_text = text
184 | self.statusTextChanged.emit()
185 |
186 | @pyqtProperty('QString')
187 | def filename(self):
188 | return self._filename
189 |
190 | @filename.setter
191 | def filename(self, filename):
192 | if filename:
193 | filename = QUrl(filename).toLocalFile()
194 | if filename != self._filename:
195 | self._filename = filename
196 | self._data.load_from_file(filename)
197 | self.statusText = "Loaded " + filename
198 | self.xTo = self._data.lengthData
199 |
200 | @pyqtProperty(int, notify=xFromChanged)
201 | def xFrom(self):
202 | return self._x_from
203 |
204 | @xFrom.setter
205 | def xFrom(self, x_from):
206 | if self.figure is None:
207 | return
208 |
209 | x_from = int(x_from)
210 | if self._x_from != x_from:
211 | self._x_from = x_from
212 | self.axes.set_xlim(left=self._x_from)
213 | self.xFromChanged.emit()
214 |
215 | @pyqtProperty(int, notify=xToChanged)
216 | def xTo(self):
217 | return self._x_to
218 |
219 | @xTo.setter
220 | def xTo(self, x_to):
221 | if self.figure is None:
222 | return
223 |
224 | x_to = int(x_to)
225 | if self._x_to != x_to:
226 | self._x_to = x_to
227 | self.axes.set_xlim(right=self._x_to)
228 | self.xToChanged.emit()
229 |
230 | @pyqtProperty(bool, notify=legendChanged)
231 | def legend(self):
232 | return self._legend
233 |
234 | @legend.setter
235 | def legend(self, legend):
236 | if self.figure is None:
237 | return
238 |
239 | if self._legend != legend:
240 | self._legend = legend
241 | if self._legend:
242 | self.axes.legend()
243 | else:
244 | leg = self.axes.get_legend()
245 | if leg is not None:
246 | leg.remove()
247 | self.legendChanged.emit()
248 |
249 | @pyqtProperty('QString', constant=True)
250 | def about(self):
251 | msg = __doc__
252 | return msg.strip()
253 |
254 |
255 | @pyqtSlot()
256 | def update_figure(self):
257 | if self.figure is None:
258 | return
259 |
260 | self.axes.clear()
261 | self.axes.grid(True)
262 |
263 | has_series = False
264 |
265 | for row in range(self._data.rowCount()):
266 | model_index = self._data.index(row, 0)
267 | checked = self._data.data(model_index, DataSeriesModel.SelectedRole)
268 |
269 | if checked:
270 | has_series = True
271 | name = self._data.data(model_index, DataSeriesModel.NameRole)
272 | values = self._data.data(model_index, DataSeriesModel.DataRole)
273 |
274 | self.axes.plot(range(len(values)), values, 'o-', label=name)
275 |
276 | self.axes.set_xlim((self.xFrom, self.xTo))
277 | if has_series and self.legend:
278 | self.axes.legend()
279 |
280 | self.stateChanged.emit()
281 |
282 |
283 | def main():
284 | app = QGuiApplication(sys.argv)
285 |
286 | qmlRegisterType(FigureCanvasQTAggToolbar, "Backend", 1, 0, "FigureToolbar")
287 |
288 | imgProvider = MatplotlibIconProvider()
289 |
290 | engine = QQmlApplicationEngine(parent=app)
291 | engine.addImageProvider("mplIcons", imgProvider)
292 |
293 | context = engine.rootContext()
294 | data_model = DataSeriesModel()
295 | context.setContextProperty("dataModel", data_model)
296 | mainApp = Form(data=data_model)
297 | context.setContextProperty("draw_mpl", mainApp)
298 |
299 | engine.load(QUrl('main.qml'))
300 |
301 | win = engine.rootObjects()[0]
302 | mainApp.figure = win.findChild(QObject, "figure").getFigure()
303 |
304 | rc = app.exec_()
305 | sys.exit(rc)
306 |
307 |
308 | if __name__ == "__main__":
309 | main()
--------------------------------------------------------------------------------
/QtQuick_controls_v1/qml.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | main.qml
4 | MainForm.ui.qml
5 |
6 |
7 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/COPYING-ICONS.txt:
--------------------------------------------------------------------------------
1 | The Breeze Icon Theme in icons/
2 |
3 | Copyright (C) 2014 Uri Herrera and others
4 |
5 | This library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 3 of the License, or (at your option) any later version.
9 |
10 | This library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with this library. If not, see .
--------------------------------------------------------------------------------
/QtQuick_controls_v2/MainForm.ui.qml:
--------------------------------------------------------------------------------
1 | import Backend 1.0
2 | import QtQuick 2.6
3 | import Qt.labs.controls 1.0
4 | import QtQuick.Layouts 1.3
5 | import QtQuick.Dialogs 1.2
6 |
7 | Item {
8 | anchors.fill: parent
9 |
10 | RowLayout {
11 | id: hbox
12 | spacing: 5
13 | anchors.horizontalCenter: parent.horizontalCenter
14 | anchors.fill: parent
15 |
16 | ColumnLayout {
17 | spacing : 0
18 | width: 640
19 | height: 480
20 |
21 | Layout.fillWidth: true
22 |
23 | FigureToolbar {
24 | id: mplView
25 | objectName : "figure"
26 |
27 | Layout.fillWidth: true
28 | Layout.fillHeight: true
29 |
30 | Layout.minimumWidth: 10
31 | Layout.minimumHeight: 10
32 | }
33 |
34 | MessageDialog {
35 | id: messageDialog
36 | }
37 |
38 | FileDialog {
39 | id: saveFileDialog
40 | title: "Choose a filename to save to"
41 | folder: mplView.defaultDirectory
42 | nameFilters: mplView.fileFilters
43 | selectedNameFilter: mplView.defaultFileFilter
44 | selectExisting: false
45 |
46 | onAccepted: {
47 | try{
48 | mplView.print_figure(fileUrl)
49 | }
50 | catch (error){
51 | messageDialog.title = "Error saving file"
52 | messageDialog.text = error
53 | messageDialog.icon = StandardIcon.Critical
54 | messageDialog.open()
55 | }
56 | }
57 | }
58 |
59 | SubplotTool {
60 | id: setMargin
61 |
62 | left.value: mplView.left
63 | right.value: mplView.right
64 | top.value: mplView.top
65 | bottom.value: mplView.bottom
66 |
67 | hspace.value: mplView.hspace
68 | wspace.value: mplView.wspace
69 |
70 | function initMargin() {
71 | // Init slider value
72 | setMargin.left.value = mplView.left
73 | setMargin.right.value = mplView.right
74 | setMargin.top.value = mplView.top
75 | setMargin.bottom.value = mplView.bottom
76 |
77 | setMargin.hspace.value = mplView.hspace
78 | setMargin.wspace.value = mplView.wspace
79 |
80 | // Invert parameter bindings
81 | mplView.left = Qt.binding(function() { return setMargin.left.value })
82 | mplView.right = Qt.binding(function() { return setMargin.right.value })
83 | mplView.top = Qt.binding(function() { return setMargin.top.value })
84 | mplView.bottom = Qt.binding(function() { return setMargin.bottom.value })
85 |
86 | mplView.hspace = Qt.binding(function() { return setMargin.hspace.value })
87 | mplView.wspace = Qt.binding(function() { return setMargin.wspace.value })
88 | }
89 |
90 | onReset: {
91 | mplView.reset_margin()
92 | setMargin.initMargin()
93 | }
94 |
95 | onTightLayout: {
96 | mplView.tight_layout()
97 | setMargin.initMargin()
98 | }
99 | }
100 |
101 |
102 | ToolBar {
103 | id: toolbar
104 | height: 48
105 |
106 | Layout.maximumHeight: height
107 | Layout.minimumHeight: height
108 | Layout.alignment: Qt.AlignLeft | Qt.Bottom
109 | Layout.fillWidth: true
110 |
111 | RowLayout {
112 | Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
113 | anchors.fill: parent
114 | spacing: 0
115 |
116 | ToolButton {
117 | id : home
118 |
119 | contentItem: Image{
120 | fillMode: Image.PreserveAspectFit
121 | source: "image://mplIcons/home"
122 | }
123 | onClicked: {
124 | mplView.home()
125 | }
126 | }
127 |
128 | ToolButton {
129 | id : back
130 | contentItem: Image{
131 | fillMode: Image.PreserveAspectFit
132 | source: "image://mplIcons/back"
133 | }
134 | onClicked: {
135 | mplView.back()
136 | }
137 | }
138 |
139 | ToolButton {
140 | id : forward
141 |
142 | contentItem: Image{
143 | fillMode: Image.PreserveAspectFit
144 | source: "image://mplIcons/forward"
145 | }
146 | onClicked: {
147 | mplView.forward()
148 | }
149 | }
150 |
151 | // Fake separator
152 | Label {
153 | text : "|"
154 | }
155 |
156 | ButtonGroup {
157 | // Gather pan and zoom tools to make them auto-exclusive
158 | id: pan_zoom
159 | }
160 |
161 | ToolButton {
162 | id : pan
163 |
164 | contentItem: Image{
165 | fillMode: Image.PreserveAspectFit
166 | source: "image://mplIcons/move"
167 | }
168 |
169 | ButtonGroup.group: pan_zoom
170 | checkable: true
171 |
172 | onClicked: {
173 | mplView.pan()
174 | }
175 | }
176 |
177 | ToolButton {
178 | id : zoom
179 |
180 | contentItem: Image{
181 | fillMode: Image.PreserveAspectFit
182 | source: "image://mplIcons/zoom_to_rect"
183 | }
184 |
185 | ButtonGroup.group: pan_zoom
186 | checkable: true
187 |
188 | onClicked: {
189 | mplView.zoom()
190 | }
191 | }
192 |
193 | Label {
194 | text : "|"
195 | }
196 |
197 | ToolButton {
198 | id : subplots
199 | contentItem: Image{
200 | fillMode: Image.PreserveAspectFit
201 | source: "image://mplIcons/subplots"
202 | }
203 | onClicked: {
204 | setMargin.initMargin()
205 | setMargin.open()
206 | }
207 | }
208 |
209 | ToolButton {
210 | id : save
211 | contentItem: Image{
212 | fillMode: Image.PreserveAspectFit
213 | source: "image://mplIcons/filesave"
214 | }
215 | onClicked: {
216 | saveFileDialog.open()
217 | }
218 | }
219 | /*
220 | ToolButton {
221 | id : figureOptions
222 |
223 | contentItem: Image{
224 | fillMode: Image.PreserveAspectFit
225 | source: "image://mplIcons/qt4_editor_options"
226 | }
227 |
228 | visible: mplView.figureOptions
229 |
230 | onClicked: {
231 | }
232 | }
233 | */
234 | Item {
235 | Layout.fillWidth: true
236 | }
237 |
238 | Label{
239 | id: locLabel
240 |
241 | Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
242 |
243 | text: mplView.message
244 | }
245 | }
246 | }
247 | }
248 |
249 | Connections {
250 | target: dataModel
251 | onDataChanged: {
252 | draw_mpl.update_figure()
253 | }
254 | }
255 |
256 | Pane {
257 | id: right
258 | Layout.alignment: Qt.AlignLeft | Qt.AlignTop
259 | Layout.fillHeight: true
260 |
261 | ColumnLayout {
262 | id: right_vbox
263 |
264 | spacing: 2
265 |
266 | Label {
267 | id: log_label
268 | text: qsTr("Data series:")
269 | }
270 |
271 | ListView {
272 | id: series_list_view
273 | height: 180
274 | Layout.fillWidth: true
275 |
276 | clip: true
277 |
278 | model: dataModel
279 | delegate: CheckBox {
280 | checked : false;
281 | text: name
282 | onClicked: {
283 | selected = checked;
284 | }
285 | }
286 | }
287 |
288 | RowLayout {
289 | id: rowLayout1
290 | Layout.fillWidth: true
291 |
292 | Label {
293 | id: spin_label1
294 | text: qsTr("X")
295 | }
296 |
297 | RangeSlider {
298 | id: xSlider
299 | first.value: draw_mpl.xFrom
300 | second.value: draw_mpl.xTo
301 | from: 0
302 | to: dataModel.lengthData - 1;
303 | enabled: series_list_view.count > 0;
304 |
305 | }
306 |
307 | Binding {
308 | target: draw_mpl
309 | property: "xFrom"
310 | value: xSlider.first.value
311 | }
312 |
313 | Binding {
314 | target: draw_mpl
315 | property: "xTo"
316 | value: xSlider.second.value
317 | }
318 |
319 | }
320 |
321 | Switch {
322 | id: legend_cb
323 | text: qsTr("Show Legend")
324 | checked: draw_mpl.legend
325 | }
326 |
327 | Binding {
328 | target: draw_mpl
329 | property: "legend"
330 | value: legend_cb.checked
331 | }
332 |
333 | }
334 | }
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/QtQuickControls2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcollonval/matplotlib_qtquick_playground/21de5e9734e26ebc1dbd8d7fafc023248d9ce607/QtQuick_controls_v2/QtQuickControls2.PNG
--------------------------------------------------------------------------------
/QtQuick_controls_v2/SubplotTool.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.6
2 | import QtQuick.Layouts 1.2
3 | import Qt.labs.controls 1.0
4 | import QtQuick.Dialogs 1.2
5 |
6 | Dialog {
7 | id: subplotTool
8 |
9 | title: "Margins & spacing"
10 |
11 | property alias left: left_slider
12 | property alias right: right_slider
13 | property alias top: top_slider
14 | property alias bottom: bottom_slider
15 | property alias hspace: hspace_slider
16 | property alias wspace: wspace_slider
17 |
18 | signal reset
19 | signal tightLayout
20 |
21 | contentItem : ColumnLayout {
22 | anchors.fill: parent
23 |
24 | GroupBox {
25 | id: borders
26 | title: "Borders"
27 |
28 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
29 | Layout.fillWidth: true
30 | Layout.fillHeight: true
31 |
32 | GridLayout{
33 | columns: 3
34 | anchors.fill: parent
35 |
36 | Label {
37 | text: "top"
38 | }
39 | Slider{
40 | id: top_slider
41 | from: bottom_slider.value
42 | to: 1
43 | value: 1
44 | stepSize: 0.01
45 | snapMode: Slider.SnapOnRelease
46 |
47 | Layout.fillWidth: true
48 | }
49 | Label{
50 | text: top_slider.value.toFixed(2)
51 | }
52 |
53 | Label {
54 | text: "bottom"
55 | }
56 | Slider{
57 | id: bottom_slider
58 | from: 0
59 | to: top_slider.value
60 | value: 0
61 | stepSize: 0.01
62 | snapMode: Slider.SnapOnRelease
63 |
64 | Layout.fillWidth: true
65 | }
66 | Label{
67 | text: bottom_slider.value.toFixed(2)
68 | }
69 |
70 | Label {
71 | text: "left"
72 | }
73 | Slider{
74 | id: left_slider
75 | from: 0
76 | to: right_slider.value
77 | value: 0
78 | stepSize: 0.01
79 | snapMode: Slider.SnapOnRelease
80 |
81 | Layout.fillWidth: true
82 | }
83 | Label{
84 | text: left_slider.value.toFixed(2)
85 | }
86 |
87 | Label {
88 | text: "right"
89 | }
90 | Slider{
91 | id: right_slider
92 | from: left_slider.value
93 | to: 1
94 | value: 1
95 | stepSize: 0.01
96 | snapMode: Slider.SnapOnRelease
97 |
98 | Layout.fillWidth: true
99 | }
100 | Label{
101 | text: right_slider.value.toFixed(2)
102 | }
103 |
104 | }
105 | }
106 |
107 | GroupBox {
108 | id: spacings
109 | title: "Spacings"
110 |
111 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
112 | Layout.fillWidth: true
113 | Layout.fillHeight: true
114 |
115 | GridLayout{
116 | columns: 3
117 | anchors.fill: parent
118 |
119 | Label {
120 | text: "hspace"
121 | }
122 | Slider{
123 | id: hspace_slider
124 | from: 0
125 | to: 1
126 | value: 0
127 | stepSize: 0.01
128 | snapMode: Slider.SnapOnRelease
129 |
130 | Layout.fillWidth: true
131 | }
132 | Label{
133 | text: hspace_slider.value.toFixed(2)
134 | }
135 |
136 | Label {
137 | text: "wspace"
138 | }
139 | Slider{
140 | id: wspace_slider
141 | from: 0
142 | to: 1
143 | value: 0
144 | stepSize: 0.01
145 | snapMode: Slider.SnapOnRelease
146 |
147 | Layout.fillWidth: true
148 | }
149 | Label{
150 | text: wspace_slider.value.toFixed(2)
151 | }
152 | }
153 | }
154 |
155 | RowLayout {
156 | id: buttons
157 |
158 | anchors.bottom: parent.bottom
159 | Layout.fillWidth: true
160 |
161 | Button {
162 | id: tight_layout
163 | text: "Tight Layout"
164 |
165 | Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
166 |
167 | onClicked: {
168 | subplotTool.tightLayout()
169 | }
170 | }
171 |
172 | Item {
173 | Layout.fillWidth: true
174 | }
175 |
176 | Button {
177 | id: reset
178 | text: "Reset"
179 |
180 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
181 |
182 | onClicked: {
183 | subplotTool.reset()
184 | }
185 | }
186 |
187 | Button {
188 | id: close
189 | text: "Close"
190 |
191 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
192 |
193 | onClicked: {
194 | subplotTool.close()
195 | }
196 | }
197 | }
198 |
199 | }
200 | }
--------------------------------------------------------------------------------
/QtQuick_controls_v2/document-open.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/help-about.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/main.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.6
2 | import Qt.labs.controls 1.0
3 | import QtQuick.Dialogs 1.2
4 | import QtQuick.Layouts 1.0
5 | import QtQuick.Window 2.1
6 | import Qt.labs.controls.material 1.0
7 | import Qt.labs.controls.universal 1.0
8 |
9 | ApplicationWindow {
10 | id: root
11 | visible: true
12 | width: 940
13 | height: 500
14 | title: qsTr("Hello World")
15 |
16 | Material.theme : Material.Light
17 | Material.accent : Material.LightGreen
18 | Universal.theme : Universal.Light
19 | Universal.accent : Universal.Amber
20 |
21 | FileDialog {
22 | id: fileDialog
23 | nameFilters: ["CSV files (*.csv)", "All Files (*.*)"]
24 | onAccepted: {
25 | draw_mpl.filename = fileUrl
26 | }
27 | }
28 |
29 | header: ToolBar {
30 | id: head
31 |
32 | RowLayout {
33 | Layout.alignment: Qt.AlignLeft | Qt.AlignTop
34 | anchors.fill: parent
35 | ToolButton {
36 | width: 32
37 | // text: qsTr("Load a file")
38 | contentItem: Image{
39 | fillMode: Image.PreserveAspectFit
40 | source: "document-open.svg"
41 | }
42 | onClicked: {
43 | fileDialog.open()
44 | }
45 | }
46 | ToolButton{
47 | width: 32
48 | // text: qsTr("About")
49 | contentItem: Image{
50 | fillMode: Image.PreserveAspectFit
51 | source: "help-about.svg"
52 | }
53 | onClicked: messageDialog.show("About the demo", draw_mpl.about)
54 | }
55 | ToolButton {
56 | width: 32
57 | // text: qsTr("Quit")
58 | contentItem: Image{
59 | fillMode: Image.PreserveAspectFit
60 | source: "window-close.svg"
61 | }
62 | onClicked: Qt.quit();
63 | }
64 | Item {
65 | Layout.fillWidth: true
66 | }
67 | }
68 | }
69 |
70 | MainForm {
71 | id: mainView
72 | width: parent.width
73 | height: 320
74 |
75 | transform: [
76 | Scale {
77 | id: scale;
78 | xScale: yScale;
79 | yScale: Math.min(root.width/mainView.width,
80 | (root.height-head.height-foot.height)/mainView.height);
81 | },
82 | Translate {
83 | x: (root.width-mainView.width*scale.xScale)/2;
84 | y: (root.height-head.height-foot.height-mainView.height*scale.yScale)/2;}
85 | ]
86 | }
87 |
88 | footer: Label {
89 | id: foot
90 | text: draw_mpl.statusText
91 | }
92 |
93 | MessageDialog {
94 | id: messageDialog
95 |
96 | function show(title, caption) {
97 | messageDialog.title = title;
98 | messageDialog.text = caption;
99 | messageDialog.open();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/mpl_qtquick2.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | CALL C:\Anaconda3\Scripts\activate.bat qtquick
3 | python %~dpn0.py %*
--------------------------------------------------------------------------------
/QtQuick_controls_v2/mpl_qtquick2.py:
--------------------------------------------------------------------------------
1 | """
2 | Series of data are loaded from a .csv file, and their names are
3 | displayed in a checkable list view. The user can select the series
4 | it wants from the list and plot them on a matplotlib canvas.
5 | Use the sample .csv file that comes with the script for an example
6 | of data series.
7 |
8 | [2016-11-06] Convert to QtQuick 2.0 - Qt.labs.controls 1.0
9 | [2016-11-05] Convert to QtQuick 2.0 - QtQuick Controls 1.0
10 | [2016-11-01] Update to PyQt5.6 and python 3.5
11 |
12 | Frederic Collonval (fcollonval@gmail.com)
13 |
14 | Inspired from the work of Eli Bendersky (eliben@gmail.com):
15 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
16 |
17 | License: MIT License
18 | Last modified: 2016-11-06
19 | """
20 | import sys, os, csv
21 | from PyQt5.QtCore import QAbstractListModel, QModelIndex, QObject, QSize, Qt, QUrl, QVariant, pyqtProperty, pyqtSlot, pyqtSignal
22 | from PyQt5.QtGui import QGuiApplication, QColor, QImage, QPixmap
23 | # from PyQt5.QtWidgets import QApplication
24 | from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
25 | from PyQt5.QtQuick import QQuickImageProvider
26 |
27 | import matplotlib
28 | matplotlib.use('Agg')
29 | # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
30 | # from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
31 | # from matplotlib.figure import Figure
32 | import matplotlib.pyplot as plt
33 |
34 | import numpy as np
35 |
36 | sys.path.append('../backend')
37 | from backend_qtquick5 import FigureCanvasQTAggToolbar, MatplotlibIconProvider
38 |
39 | class DataSerie(object):
40 |
41 | def __init__(self, name, data, selected=False):
42 | self._name = name
43 | self._data = data
44 | self._selected = selected
45 |
46 | def name(self):
47 | return self._name
48 |
49 | def selected(self):
50 | return self._selected
51 |
52 | def data(self):
53 | return self._data
54 |
55 | class DataSeriesModel(QAbstractListModel):
56 |
57 | # Define role enum
58 | SelectedRole = Qt.UserRole
59 | NameRole = Qt.UserRole + 1
60 | DataRole = Qt.UserRole + 2
61 |
62 | _roles = {
63 | SelectedRole : b"selected",
64 | NameRole : b"name",
65 | DataRole : b"data"
66 | }
67 |
68 | lengthDataChanged = pyqtSignal()
69 |
70 | def __init__(self, parent=None):
71 | QAbstractListModel.__init__(self, parent)
72 |
73 | self._data_series = list()
74 | self._length_data = 0
75 |
76 | @pyqtProperty(int, notify=lengthDataChanged)
77 | def lengthData(self):
78 | return self._length_data
79 |
80 | @lengthData.setter
81 | def lengthData(self, length):
82 | if self._length_data != length:
83 | self._length_data = length
84 | self.lengthDataChanged.emit()
85 |
86 | def roleNames(self):
87 | return self._roles
88 |
89 | def load_from_file(self, filename=None):
90 | self._data_series.clear()
91 | self._length_data = 0
92 |
93 | if filename:
94 | with open(filename, 'r') as f:
95 | for line in csv.reader(f):
96 | series = DataSerie(line[0],
97 | [i for i in map(int, line[1:])])
98 | self.add_data(series)
99 |
100 | def add_data(self, data_series):
101 | self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
102 | self._data_series.append(data_series)
103 | self.lengthData = max(self.lengthData, len(data_series.data()))
104 | self.endInsertRows()
105 |
106 | def rowCount(self, parent=QModelIndex()):
107 | return len(self._data_series)
108 |
109 | def data(self, index, role=Qt.DisplayRole):
110 | if(index.row() < 0 or index.row() >= len(self._data_series)):
111 | return QVariant()
112 |
113 | series = self._data_series[index.row()]
114 |
115 | if role == self.SelectedRole:
116 | return series.selected()
117 | elif role == self.NameRole:
118 | return series.name()
119 | elif role == self.DataRole:
120 | return series.data()
121 |
122 | return QVariant()
123 |
124 | def setData(self, index, value, role=Qt.EditRole):
125 | if(index.row() < 0 or index.row() >= len(self._data_series)):
126 | return False
127 |
128 | series = self._data_series[index.row()]
129 |
130 | if role == self.SelectedRole:
131 | series._selected = not value
132 | self.dataChanged.emit(index, index, [role,])
133 | return True
134 |
135 | return False
136 |
137 | class Form(QObject):
138 |
139 | xFromChanged = pyqtSignal()
140 | xToChanged = pyqtSignal()
141 | legendChanged = pyqtSignal()
142 | statusTextChanged = pyqtSignal()
143 | stateChanged = pyqtSignal()
144 |
145 | def __init__(self, parent=None, data=None):
146 | QObject.__init__(self, parent)
147 |
148 | self._status_text = "Please load a data file"
149 |
150 | self._filename = ""
151 | self._x_from = 0
152 | self._x_to = 1
153 | self._legend = False
154 |
155 | # default dpi=80, so size = (480, 320)
156 | self._figure = None
157 | self.axes = None
158 |
159 | self._data = data
160 |
161 | @property
162 | def figure(self):
163 | return self._figure
164 |
165 | @figure.setter
166 | def figure(self, fig):
167 | self._figure = fig
168 | self._figure.set_facecolor('white')
169 | self.axes = self.figure.add_subplot(111)
170 |
171 | # Signal connection
172 | self.xFromChanged.connect(self._figure.canvas.draw_idle)
173 | self.xToChanged.connect(self._figure.canvas.draw_idle)
174 | self.legendChanged.connect(self._figure.canvas.draw_idle)
175 | self.stateChanged.connect(self._figure.canvas.draw_idle)
176 |
177 | @pyqtProperty('QString', notify=statusTextChanged)
178 | def statusText(self):
179 | return self._status_text
180 |
181 | @statusText.setter
182 | def statusText(self, text):
183 | if self._status_text != text:
184 | self._status_text = text
185 | self.statusTextChanged.emit()
186 |
187 | @pyqtProperty('QString')
188 | def filename(self):
189 | return self._filename
190 |
191 | @filename.setter
192 | def filename(self, filename):
193 | if filename:
194 | filename = QUrl(filename).toLocalFile()
195 | if filename != self._filename:
196 | self._filename = filename
197 | self._data.load_from_file(filename)
198 | self.statusText = "Loaded " + filename
199 | self.xTo = self._data.lengthData
200 | self.update_figure()
201 |
202 | @pyqtProperty(int, notify=xFromChanged)
203 | def xFrom(self):
204 | return self._x_from
205 |
206 | @xFrom.setter
207 | def xFrom(self, x_from):
208 | if self.figure is None:
209 | return
210 |
211 | x_from = int(x_from)
212 | if self._x_from != x_from:
213 | self._x_from = x_from
214 | self.axes.set_xlim(left=self._x_from)
215 | self.xFromChanged.emit()
216 |
217 | @pyqtProperty(int, notify=xToChanged)
218 | def xTo(self):
219 | return self._x_to
220 |
221 | @xTo.setter
222 | def xTo(self, x_to):
223 | if self.figure is None:
224 | return
225 |
226 | x_to = int(x_to)
227 | if self._x_to != x_to:
228 | self._x_to = x_to
229 | self.axes.set_xlim(right=self._x_to)
230 | self.xToChanged.emit()
231 |
232 | @pyqtProperty(bool, notify=legendChanged)
233 | def legend(self):
234 | return self._legend
235 |
236 | @legend.setter
237 | def legend(self, legend):
238 | if self.figure is None:
239 | return
240 |
241 | if self._legend != legend:
242 | self._legend = legend
243 | if self._legend:
244 | self.axes.legend()
245 | else:
246 | leg = self.axes.get_legend()
247 | if leg is not None:
248 | leg.remove()
249 | self.legendChanged.emit()
250 |
251 | @pyqtProperty('QString', constant=True)
252 | def about(self):
253 | msg = __doc__
254 | return msg.strip()
255 |
256 |
257 | @pyqtSlot()
258 | def update_figure(self):
259 | if self.figure is None:
260 | return
261 |
262 | self.axes.clear()
263 | self.axes.grid(True)
264 |
265 | has_series = False
266 |
267 | for row in range(self._data.rowCount()):
268 | model_index = self._data.index(row, 0)
269 | checked = self._data.data(model_index, DataSeriesModel.SelectedRole)
270 |
271 | if checked:
272 | has_series = True
273 | name = self._data.data(model_index, DataSeriesModel.NameRole)
274 | values = self._data.data(model_index, DataSeriesModel.DataRole)
275 |
276 | self.axes.plot(range(len(values)), values, 'o-', label=name)
277 |
278 | self.axes.set_xlim((self.xFrom, self.xTo))
279 | if has_series and self.legend:
280 | self.axes.legend()
281 |
282 | self.stateChanged.emit()
283 |
284 |
285 | def main():
286 | argv = sys.argv
287 |
288 | # Trick to set the style / not found how to do it in pythonic way
289 | argv.extend(["-style", "universal"])
290 | app = QGuiApplication(argv)
291 |
292 | qmlRegisterType(FigureCanvasQTAggToolbar, "Backend", 1, 0, "FigureToolbar")
293 | imgProvider = MatplotlibIconProvider()
294 |
295 | # !! You must specified the QApplication as parent of QQmlApplicationEngine
296 | # otherwise a segmentation fault is raised when exiting the app
297 | engine = QQmlApplicationEngine(parent=app)
298 | engine.addImageProvider("mplIcons", imgProvider)
299 |
300 | context = engine.rootContext()
301 | data_model = DataSeriesModel()
302 | context.setContextProperty("dataModel", data_model)
303 | mainApp = Form(data=data_model)
304 | context.setContextProperty("draw_mpl", mainApp)
305 |
306 | engine.load(QUrl('main.qml'))
307 |
308 | win = engine.rootObjects()[0]
309 | mainApp.figure = win.findChild(QObject, "figure").getFigure()
310 |
311 | rc = app.exec_()
312 | # There is some trouble arising when deleting all the objects here
313 | # but I have not figure out how to solve the error message.
314 | # It looks like 'app' is destroyed before some QObject
315 | sys.exit(rc)
316 |
317 |
318 | if __name__ == "__main__":
319 | main()
--------------------------------------------------------------------------------
/QtQuick_controls_v2/qml.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | main.qml
4 | MainForm.ui.qml
5 |
6 |
7 |
--------------------------------------------------------------------------------
/QtQuick_controls_v2/window-close.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/QtWidgets/QtWidgets_UI.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fcollonval/matplotlib_qtquick_playground/21de5e9734e26ebc1dbd8d7fafc023248d9ce607/QtWidgets/QtWidgets_UI.PNG
--------------------------------------------------------------------------------
/QtWidgets/mpl_qtwidgets.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | CALL C:\Anaconda3\Scripts\activate.bat qtquick
3 | python %~dpn0.py %*
--------------------------------------------------------------------------------
/QtWidgets/mpl_qtwidgets.py:
--------------------------------------------------------------------------------
1 | """
2 | Series of data are loaded from a .csv file, and their names are
3 | displayed in a checkable list view. The user can select the series
4 | it wants from the list and plot them on a matplotlib canvas.
5 | Use the sample .csv file that comes with the script for an example
6 | of data series.
7 |
8 | [2016-11-01] Update to PyQt5.6 and python 3.5
9 |
10 | Frederic Collonval (fcollonval@gmail.com)
11 |
12 | Inspired from the work of Eli Bendersky (eliben@gmail.com):
13 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
14 |
15 | License: MIT License
16 | License: this code is in the public domain
17 | Last modified: 2016-11-01
18 | """
19 | import sys, os, csv
20 | from PyQt5.QtCore import *
21 | from PyQt5.QtGui import *
22 | from PyQt5.QtWidgets import *
23 |
24 | import matplotlib
25 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
26 | from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
27 | from matplotlib.figure import Figure
28 |
29 |
30 | class Form(QMainWindow):
31 | def __init__(self, parent=None):
32 | super(Form, self).__init__(parent)
33 | self.setWindowTitle('PyQt & matplotlib demo: Data plotting')
34 |
35 | self.data = DataHolder()
36 | self.series_list_model = QStandardItemModel()
37 |
38 | self.create_menu()
39 | self.create_main_frame()
40 | self.create_status_bar()
41 |
42 | self.update_ui()
43 | self.on_show()
44 |
45 | def load_file(self, filename=None):
46 | filename, filters = QFileDialog.getOpenFileName(self,
47 | 'Open a data file', '.', 'CSV files (*.csv);;All Files (*.*)')
48 |
49 | if filename:
50 | self.data.load_from_file(filename)
51 | self.fill_series_list(self.data.series_names())
52 | self.status_text.setText("Loaded " + filename)
53 | self.update_ui()
54 |
55 | def update_ui(self):
56 | if self.data.series_count() > 0 and self.data.series_len() > 0:
57 | self.from_spin.setValue(0)
58 | self.to_spin.setValue(self.data.series_len() - 1)
59 |
60 | for w in [self.from_spin, self.to_spin]:
61 | w.setRange(0, self.data.series_len() - 1)
62 | w.setEnabled(True)
63 | else:
64 | for w in [self.from_spin, self.to_spin]:
65 | w.setEnabled(False)
66 |
67 | def on_show(self):
68 | self.axes.clear()
69 | self.axes.grid(True)
70 |
71 | has_series = False
72 |
73 | for row in range(self.series_list_model.rowCount()):
74 | model_index = self.series_list_model.index(row, 0)
75 | checked = self.series_list_model.data(model_index,
76 | Qt.CheckStateRole) == QVariant(Qt.Checked)
77 | name = str(self.series_list_model.data(model_index))
78 |
79 | if checked:
80 | has_series = True
81 |
82 | x_from = self.from_spin.value()
83 | x_to = self.to_spin.value()
84 | series = self.data.get_series_data(name)[x_from:x_to + 1]
85 | self.axes.plot(range(len(series)), series, 'o-', label=name)
86 |
87 | if has_series and self.legend_cb.isChecked():
88 | self.axes.legend()
89 | self.canvas.draw()
90 |
91 | def on_about(self):
92 | msg = __doc__
93 | QMessageBox.about(self, "About the demo", msg.strip())
94 |
95 | def fill_series_list(self, names):
96 | self.series_list_model.clear()
97 |
98 | for name in names:
99 | item = QStandardItem(name)
100 | item.setCheckState(Qt.Unchecked)
101 | item.setCheckable(True)
102 | self.series_list_model.appendRow(item)
103 |
104 | def create_main_frame(self):
105 | self.main_frame = QWidget()
106 |
107 | plot_frame = QWidget()
108 |
109 | self.dpi = 100
110 | self.fig = Figure((6.0, 4.0), dpi=self.dpi)
111 | self.canvas = FigureCanvas(self.fig)
112 | self.canvas.setParent(self.main_frame)
113 |
114 | self.axes = self.fig.add_subplot(111)
115 | self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
116 |
117 | log_label = QLabel("Data series:")
118 | self.series_list_view = QListView()
119 | self.series_list_view.setModel(self.series_list_model)
120 |
121 | spin_label1 = QLabel('X from')
122 | self.from_spin = QSpinBox()
123 | spin_label2 = QLabel('to')
124 | self.to_spin = QSpinBox()
125 |
126 | spins_hbox = QHBoxLayout()
127 | spins_hbox.addWidget(spin_label1)
128 | spins_hbox.addWidget(self.from_spin)
129 | spins_hbox.addWidget(spin_label2)
130 | spins_hbox.addWidget(self.to_spin)
131 | spins_hbox.addStretch(1)
132 |
133 | self.legend_cb = QCheckBox("Show L&egend")
134 | self.legend_cb.setChecked(False)
135 |
136 | self.show_button = QPushButton("&Show")
137 | # self.connect(self.show_button, SIGNAL('clicked()'), self.on_show)
138 | self.show_button.clicked.connect(self.on_show)
139 |
140 | left_vbox = QVBoxLayout()
141 | left_vbox.addWidget(self.canvas)
142 | left_vbox.addWidget(self.mpl_toolbar)
143 |
144 | right_vbox = QVBoxLayout()
145 | right_vbox.addWidget(log_label)
146 | right_vbox.addWidget(self.series_list_view)
147 | right_vbox.addLayout(spins_hbox)
148 | right_vbox.addWidget(self.legend_cb)
149 | right_vbox.addWidget(self.show_button)
150 | right_vbox.addStretch(1)
151 |
152 | hbox = QHBoxLayout()
153 | hbox.addLayout(left_vbox)
154 | hbox.addLayout(right_vbox)
155 | self.main_frame.setLayout(hbox)
156 |
157 | self.setCentralWidget(self.main_frame)
158 |
159 | def create_status_bar(self):
160 | self.status_text = QLabel("Please load a data file")
161 | self.statusBar().addWidget(self.status_text, 1)
162 |
163 | def create_menu(self):
164 | self.file_menu = self.menuBar().addMenu("&File")
165 |
166 | load_action = self.create_action("&Load file",
167 | shortcut="Ctrl+L", slot=self.load_file, tip="Load a file")
168 | quit_action = self.create_action("&Quit", slot=self.close,
169 | shortcut="Ctrl+Q", tip="Close the application")
170 |
171 | self.add_actions(self.file_menu,
172 | (load_action, None, quit_action))
173 |
174 | self.help_menu = self.menuBar().addMenu("&Help")
175 | about_action = self.create_action("&About",
176 | shortcut='F1', slot=self.on_about,
177 | tip='About the demo')
178 |
179 | self.add_actions(self.help_menu, (about_action,))
180 |
181 | def add_actions(self, target, actions):
182 | for action in actions:
183 | if action is None:
184 | target.addSeparator()
185 | else:
186 | target.addAction(action)
187 |
188 | def create_action( self, text, slot=None, shortcut=None,
189 | icon=None, tip=None, checkable=False,
190 | signal="triggered"):
191 | action = QAction(text, self)
192 | if icon is not None:
193 | action.setIcon(QIcon(":/%s.png" % icon))
194 | if shortcut is not None:
195 | action.setShortcut(shortcut)
196 | if tip is not None:
197 | action.setToolTip(tip)
198 | action.setStatusTip(tip)
199 | if slot is not None:
200 | # self.connect(action, SIGNAL(signal), slot)
201 | getattr(action, signal).connect(slot)
202 | if checkable:
203 | action.setCheckable(True)
204 | return action
205 |
206 |
207 | class DataHolder(object):
208 | """ Just a thin wrapper over a dictionary that holds integer
209 | data series. Each series has a name and a list of numbers
210 | as its data. The length of all series is assumed to be
211 | the same.
212 | The series can be read from a CSV file, where each line
213 | is a separate series. In each series, the first item in
214 | the line is the name, and the rest are data numbers.
215 | """
216 | def __init__(self, filename=None):
217 | self.load_from_file(filename)
218 |
219 | def load_from_file(self, filename=None):
220 | self.data = {}
221 | self.names = []
222 |
223 | if filename:
224 | with open(filename, 'r') as f:
225 | for line in csv.reader(f):
226 | self.names.append(line[0])
227 | self.data[line[0]] = [i for i in map(int, line[1:])]
228 | self.datalen = len(line[1:])
229 |
230 | def series_names(self):
231 | """ Names of the data series
232 | """
233 | return self.names
234 |
235 | def series_len(self):
236 | """ Length of a data series
237 | """
238 | return self.datalen
239 |
240 | def series_count(self):
241 | return len(self.data)
242 |
243 | def get_series_data(self, name):
244 | return self.data[name]
245 |
246 |
247 | def main():
248 | app = QApplication(sys.argv)
249 | form = Form()
250 | form.show()
251 | app.exec_()
252 |
253 |
254 | if __name__ == "__main__":
255 | main()
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | :warning: **This project is archived as no longer maintained**
2 |
3 | # matplotlib_qtquick_playground
4 | Port of the example kindly provided by Eli Bendersky to PyQt5:
5 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
6 |
7 | Derivation of the example have been made based on the three following Qt technologies:
8 | - QtWidgets
9 | - QtQuick Controls 1.0
10 | - QtQuick Controls 2.0 (actually Qt.labs.controls 1.0 as I used PyQt 5.6)
11 |
12 | The goal of this work was to play around with QtQuick and PyQt5. The integration of matplotlib with QtWidgets is the best
13 | as a backend support full interactivity and navigation toolbar. A new matplotlib backend based on a QQuickItem has been
14 | created to restore maximal interactivity.
15 |
16 | The logic behind QtWidgets GUI and QtQuick is quite different. For example, in the former, the Python script takes care of
17 | reading all widgets before updating the figure. But in the latter, QtQuick controls are binded to Python properties that
18 | emit signal forcing the figure to update.
19 |
20 | ## QtWidgets version
21 |
22 | 
23 |
24 | ## QtQuick Controls 1.0 version
25 |
26 | 
27 |
28 | ## QtQuick Controls 2.0 version
29 |
30 | 
31 |
32 | Code functions
33 | ==============
34 |
35 | Series of data are loaded from a .csv file, and their names are
36 | displayed in a checkable list view. The user can select the series
37 | it wants from the list and plot them on a matplotlib canvas.
38 | Use the sample .csv file that comes with the scripts for an example
39 | of data series.
40 |
41 | Requirements
42 | ============
43 |
44 | * Python >= 3.5
45 | * PyQt = 5.6 (if you plan to use PyQt 5.7, references have changed as QtQuick.Controls 2.0 have integrated the official library)
46 | * matplolib >= 1.4
47 |
48 | License
49 | =======
50 |
51 | MIT License
52 |
53 | Copyright (C) 2016 Frederic Collonval
54 |
55 | The code for QtQuick Controls 2.0 makes used of the KDE Breeze Icons Theme (https://github.com/KDE/breeze-icons) distributed under LGPLv3
56 |
57 | > The Breeze Icon Theme in icons/
58 |
59 | > Copyright (C) 2014 Uri Herrera and others
60 |
--------------------------------------------------------------------------------
/backend/backend_qtquick5/Figure.qml:
--------------------------------------------------------------------------------
1 | import Backend 1.0
2 | import QtQuick 2.6
3 |
4 | Item {
5 | width: 480
6 | height: 320
7 |
8 | FigureCanvas {
9 | id: mplView
10 | objectName : "figure"
11 | anchors.fill: parent
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/backend_qtquick5/FigureToolbar.qml:
--------------------------------------------------------------------------------
1 | import Backend 1.0
2 | import QtQuick 2.6
3 | import QtQuick.Layouts 1.2
4 | import Qt.labs.controls 1.0
5 | import QtQuick.Dialogs 1.2
6 |
7 | Item{
8 | width: 640
9 | height: 480
10 |
11 | ColumnLayout {
12 | spacing : 0
13 | anchors.fill: parent
14 |
15 | FigureToolbar {
16 | id: mplView
17 | objectName : "figure"
18 |
19 | Layout.fillWidth: true
20 | Layout.fillHeight: true
21 |
22 | Layout.minimumWidth: 10
23 | Layout.minimumHeight: 10
24 | }
25 |
26 | MessageDialog {
27 | id: messageDialog
28 | }
29 |
30 | FileDialog {
31 | id: saveFileDialog
32 | title: "Choose a filename to save to"
33 | folder: mplView.defaultDirectory
34 | nameFilters: mplView.fileFilters
35 | selectedNameFilter: mplView.defaultFileFilter
36 | selectExisting: false
37 |
38 | onAccepted: {
39 | try{
40 | mplView.print_figure(fileUrl)
41 | }
42 | catch (error){
43 | messageDialog.title = "Error saving file"
44 | messageDialog.text = error
45 | messageDialog.icon = StandardIcon.Critical
46 | messageDialog.open()
47 | }
48 | }
49 | }
50 |
51 | SubplotTool {
52 | id: setMargin
53 |
54 | left.value: mplView.left
55 | right.value: mplView.right
56 | top.value: mplView.top
57 | bottom.value: mplView.bottom
58 |
59 | hspace.value: mplView.hspace
60 | wspace.value: mplView.wspace
61 |
62 | function initMargin() {
63 | // Init slider value
64 | setMargin.left.value = mplView.left
65 | setMargin.right.value = mplView.right
66 | setMargin.top.value = mplView.top
67 | setMargin.bottom.value = mplView.bottom
68 |
69 | setMargin.hspace.value = mplView.hspace
70 | setMargin.wspace.value = mplView.wspace
71 |
72 | // Invert parameter bindings
73 | mplView.left = Qt.binding(function() { return setMargin.left.value })
74 | mplView.right = Qt.binding(function() { return setMargin.right.value })
75 | mplView.top = Qt.binding(function() { return setMargin.top.value })
76 | mplView.bottom = Qt.binding(function() { return setMargin.bottom.value })
77 |
78 | mplView.hspace = Qt.binding(function() { return setMargin.hspace.value })
79 | mplView.wspace = Qt.binding(function() { return setMargin.wspace.value })
80 | }
81 |
82 | onReset: {
83 | mplView.reset_margin()
84 | setMargin.initMargin()
85 | }
86 |
87 | onTightLayout: {
88 | mplView.tight_layout()
89 | setMargin.initMargin()
90 | }
91 | }
92 |
93 |
94 | ToolBar {
95 | id: toolbar
96 |
97 | Layout.alignment: Qt.AlignLeft | Qt.Bottom
98 | Layout.fillWidth: true
99 |
100 | RowLayout {
101 | Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
102 | anchors.fill: parent
103 | spacing: 0
104 |
105 | ToolButton {
106 | id : home
107 | contentItem: Image{
108 | fillMode: Image.PreserveAspectFit
109 | source: "image://mplIcons/home"
110 | }
111 | onClicked: {
112 | mplView.home()
113 | }
114 | }
115 |
116 | ToolButton {
117 | id : back
118 | contentItem: Image{
119 | fillMode: Image.PreserveAspectFit
120 | source: "image://mplIcons/back"
121 | }
122 | onClicked: {
123 | mplView.back()
124 | }
125 | }
126 |
127 | ToolButton {
128 | id : forward
129 |
130 | contentItem: Image{
131 | fillMode: Image.PreserveAspectFit
132 | source: "image://mplIcons/forward"
133 | }
134 | onClicked: {
135 | mplView.forward()
136 | }
137 | }
138 |
139 | // Fake separator
140 | Label {
141 | text : "|"
142 | }
143 |
144 | ButtonGroup {
145 | // Gather pan and zoom tools to make them auto-exclusive
146 | id: pan_zoom
147 | }
148 |
149 | ToolButton {
150 | id : pan
151 |
152 | contentItem: Image{
153 | fillMode: Image.PreserveAspectFit
154 | source: "image://mplIcons/move"
155 | }
156 |
157 | ButtonGroup.group: pan_zoom
158 | checkable: true
159 |
160 | onClicked: {
161 | mplView.pan()
162 | }
163 | }
164 |
165 | ToolButton {
166 | id : zoom
167 |
168 | contentItem: Image{
169 | fillMode: Image.PreserveAspectFit
170 | source: "image://mplIcons/zoom_to_rect"
171 | }
172 |
173 | ButtonGroup.group: pan_zoom
174 | checkable: true
175 |
176 | onClicked: {
177 | mplView.zoom()
178 | }
179 | }
180 |
181 | Label {
182 | text : "|"
183 | }
184 |
185 | ToolButton {
186 | id : subplots
187 | contentItem: Image{
188 | fillMode: Image.PreserveAspectFit
189 | source: "image://mplIcons/subplots"
190 | }
191 | onClicked: {
192 | setMargin.initMargin()
193 | setMargin.open()
194 | }
195 | }
196 |
197 | ToolButton {
198 | id : save
199 | contentItem: Image{
200 | fillMode: Image.PreserveAspectFit
201 | source: "image://mplIcons/filesave"
202 | }
203 | onClicked: {
204 | saveFileDialog.open()
205 | }
206 | }
207 |
208 | Item {
209 | Layout.fillWidth: true
210 | }
211 |
212 | Label{
213 | id: locLabel
214 |
215 | Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
216 |
217 | text: mplView.message
218 | }
219 | }
220 | }
221 | }
222 | }
--------------------------------------------------------------------------------
/backend/backend_qtquick5/SubplotTool.qml:
--------------------------------------------------------------------------------
1 | import QtQuick 2.6
2 | import QtQuick.Layouts 1.2
3 | import Qt.labs.controls 1.0
4 | import QtQuick.Dialogs 1.2
5 |
6 | Dialog {
7 | id: subplotTool
8 |
9 | title: "Margins & spacing"
10 |
11 | property alias left: left_slider
12 | property alias right: right_slider
13 | property alias top: top_slider
14 | property alias bottom: bottom_slider
15 | property alias hspace: hspace_slider
16 | property alias wspace: wspace_slider
17 |
18 | signal reset
19 | signal tightLayout
20 |
21 | contentItem : ColumnLayout {
22 | anchors.fill: parent
23 |
24 | GroupBox {
25 | id: borders
26 | title: "Borders"
27 |
28 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
29 | Layout.fillWidth: true
30 | Layout.fillHeight: true
31 |
32 | GridLayout{
33 | columns: 3
34 | anchors.fill: parent
35 |
36 | Label {
37 | text: "top"
38 | }
39 | Slider{
40 | id: top_slider
41 | from: bottom_slider.value
42 | to: 1
43 | value: 1
44 | stepSize: 0.01
45 | snapMode: Slider.SnapOnRelease
46 |
47 | Layout.fillWidth: true
48 | }
49 | Label{
50 | text: top_slider.value.toFixed(2)
51 | }
52 |
53 | Label {
54 | text: "bottom"
55 | }
56 | Slider{
57 | id: bottom_slider
58 | from: 0
59 | to: top_slider.value
60 | value: 0
61 | stepSize: 0.01
62 | snapMode: Slider.SnapOnRelease
63 |
64 | Layout.fillWidth: true
65 | }
66 | Label{
67 | text: bottom_slider.value.toFixed(2)
68 | }
69 |
70 | Label {
71 | text: "left"
72 | }
73 | Slider{
74 | id: left_slider
75 | from: 0
76 | to: right_slider.value
77 | value: 0
78 | stepSize: 0.01
79 | snapMode: Slider.SnapOnRelease
80 |
81 | Layout.fillWidth: true
82 | }
83 | Label{
84 | text: left_slider.value.toFixed(2)
85 | }
86 |
87 | Label {
88 | text: "right"
89 | }
90 | Slider{
91 | id: right_slider
92 | from: left_slider.value
93 | to: 1
94 | value: 1
95 | stepSize: 0.01
96 | snapMode: Slider.SnapOnRelease
97 |
98 | Layout.fillWidth: true
99 | }
100 | Label{
101 | text: right_slider.value.toFixed(2)
102 | }
103 |
104 | }
105 | }
106 |
107 | GroupBox {
108 | id: spacings
109 | title: "Spacings"
110 |
111 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
112 | Layout.fillWidth: true
113 | Layout.fillHeight: true
114 |
115 | GridLayout{
116 | columns: 3
117 | anchors.fill: parent
118 |
119 | Label {
120 | text: "hspace"
121 | }
122 | Slider{
123 | id: hspace_slider
124 | from: 0
125 | to: 1
126 | value: 0
127 | stepSize: 0.01
128 | snapMode: Slider.SnapOnRelease
129 |
130 | Layout.fillWidth: true
131 | }
132 | Label{
133 | text: hspace_slider.value.toFixed(2)
134 | }
135 |
136 | Label {
137 | text: "wspace"
138 | }
139 | Slider{
140 | id: wspace_slider
141 | from: 0
142 | to: 1
143 | value: 0
144 | stepSize: 0.01
145 | snapMode: Slider.SnapOnRelease
146 |
147 | Layout.fillWidth: true
148 | }
149 | Label{
150 | text: wspace_slider.value.toFixed(2)
151 | }
152 | }
153 | }
154 |
155 | RowLayout {
156 | id: buttons
157 |
158 | anchors.bottom: parent.bottom
159 | Layout.fillWidth: true
160 |
161 | Button {
162 | id: tight_layout
163 | text: "Tight Layout"
164 |
165 | Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
166 |
167 | onClicked: {
168 | subplotTool.tightLayout()
169 | }
170 | }
171 |
172 | Item {
173 | Layout.fillWidth: true
174 | }
175 |
176 | Button {
177 | id: reset
178 | text: "Reset"
179 |
180 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
181 |
182 | onClicked: {
183 | subplotTool.reset()
184 | }
185 | }
186 |
187 | Button {
188 | id: close
189 | text: "Close"
190 |
191 | Layout.alignment: Qt.AlignRight | Qt.AlignBottom
192 |
193 | onClicked: {
194 | subplotTool.close()
195 | }
196 | }
197 | }
198 |
199 | }
200 | }
--------------------------------------------------------------------------------
/backend/backend_qtquick5/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from .backend_qquick5agg import FigureCanvasQTAgg, FigureCanvasQTAggToolbar, MatplotlibIconProvider
3 |
--------------------------------------------------------------------------------
/backend/backend_qtquick5/backend_qquick5agg.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | import os
3 | import sys
4 | import traceback
5 |
6 | import matplotlib
7 | from matplotlib.backends.backend_agg import FigureCanvasAgg
8 | from matplotlib.backend_bases import cursors
9 | from matplotlib.figure import Figure
10 | from matplotlib.backends.backend_qt5 import TimerQT
11 |
12 | from matplotlib.externals import six
13 |
14 | from PyQt5 import QtCore, QtGui, QtQuick, QtWidgets
15 |
16 | DEBUG = False
17 |
18 | class MatplotlibIconProvider(QtQuick.QQuickImageProvider):
19 | """ This class provide the matplotlib icons for the navigation toolbar.
20 | """
21 |
22 | def __init__(self, img_type = QtQuick.QQuickImageProvider.Pixmap):
23 | self.basedir = os.path.join(matplotlib.rcParams['datapath'], 'images')
24 | QtQuick.QQuickImageProvider.__init__(self, img_type)
25 |
26 | def requestImage(self, id, size):
27 | img = QtGui.QImage(os.path.join(self.basedir, id + '.png'))
28 | size = img.size()
29 | return img, size
30 |
31 | def requestPixmap(self, id, size):
32 | img, size = self.requestImage(id, size)
33 | pixmap = QtGui.QPixmap.fromImage(img)
34 |
35 | return pixmap, size
36 |
37 | class FigureCanvasQtQuickAgg(QtQuick.QQuickPaintedItem, FigureCanvasAgg):
38 | """ This class creates a QtQuick Item encapsulating a Matplotlib
39 | Figure and all the functions to interact with the 'standard'
40 | Matplotlib navigation toolbar.
41 | """
42 |
43 | # map Qt button codes to MouseEvent's ones:
44 | buttond = {
45 | QtCore.Qt.LeftButton: 1,
46 | QtCore.Qt.MidButton: 2,
47 | QtCore.Qt.RightButton: 3,
48 | # QtCore.Qt.XButton1: None,
49 | # QtCore.Qt.XButton2: None,
50 | }
51 |
52 | cursord = {
53 | cursors.MOVE: QtCore.Qt.SizeAllCursor,
54 | cursors.HAND: QtCore.Qt.PointingHandCursor,
55 | cursors.POINTER: QtCore.Qt.ArrowCursor,
56 | cursors.SELECT_REGION: QtCore.Qt.CrossCursor,
57 | }
58 |
59 | messageChanged = QtCore.pyqtSignal(str)
60 |
61 | leftChanged = QtCore.pyqtSignal()
62 | rightChanged = QtCore.pyqtSignal()
63 | topChanged = QtCore.pyqtSignal()
64 | bottomChanged = QtCore.pyqtSignal()
65 | wspaceChanged = QtCore.pyqtSignal()
66 | hspaceChanged = QtCore.pyqtSignal()
67 |
68 | def __init__(self, figure, parent=None, coordinates=True):
69 | if DEBUG:
70 | print('FigureCanvasQtQuickAgg qtquick5: ', figure)
71 | # _create_qApp()
72 | if figure is None:
73 | figure = Figure((6.0, 4.0))
74 |
75 | QtQuick.QQuickPaintedItem.__init__(self, parent=parent)
76 | FigureCanvasAgg.__init__(self, figure=figure)
77 |
78 | self._drawRect = None
79 | self.blitbox = None
80 |
81 | # Activate hover events and mouse press events
82 | self.setAcceptHoverEvents(True)
83 | self.setAcceptedMouseButtons(QtCore.Qt.AllButtons)
84 |
85 | self._agg_draw_pending = False
86 |
87 | def getFigure(self):
88 | return self.figure
89 |
90 | def drawRectangle(self, rect):
91 | self._drawRect = rect
92 | self.update()
93 |
94 | def paint(self, p):
95 | """
96 | Copy the image from the Agg canvas to the qt.drawable.
97 | In Qt, all drawing should be done inside of here when a widget is
98 | shown onscreen.
99 | """
100 | # if the canvas does not have a renderer, then give up and wait for
101 | # FigureCanvasAgg.draw(self) to be called
102 | if not hasattr(self, 'renderer'):
103 | return
104 |
105 | if DEBUG:
106 | print('FigureCanvasQtQuickAgg.paint: ', self,
107 | self.get_width_height())
108 |
109 | if self.blitbox is None:
110 | # matplotlib is in rgba byte order. QImage wants to put the bytes
111 | # into argb format and is in a 4 byte unsigned int. Little endian
112 | # system is LSB first and expects the bytes in reverse order
113 | # (bgra).
114 | if QtCore.QSysInfo.ByteOrder == QtCore.QSysInfo.LittleEndian:
115 | stringBuffer = self.renderer._renderer.tostring_bgra()
116 | else:
117 | stringBuffer = self.renderer._renderer.tostring_argb()
118 |
119 | refcnt = sys.getrefcount(stringBuffer)
120 |
121 | # convert the Agg rendered image -> qImage
122 | qImage = QtGui.QImage(stringBuffer, self.renderer.width,
123 | self.renderer.height,
124 | QtGui.QImage.Format_ARGB32)
125 | # get the rectangle for the image
126 | rect = qImage.rect()
127 | # p = QtGui.QPainter(self)
128 | # reset the image area of the canvas to be the back-ground color
129 | p.eraseRect(rect)
130 | # draw the rendered image on to the canvas
131 | p.drawPixmap(QtCore.QPoint(0, 0), QtGui.QPixmap.fromImage(qImage))
132 |
133 | # draw the zoom rectangle to the QPainter
134 | if self._drawRect is not None:
135 | p.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.DotLine))
136 | x, y, w, h = self._drawRect
137 | p.drawRect(x, y, w, h)
138 |
139 | else:
140 | bbox = self.blitbox
141 | l, b, r, t = bbox.extents
142 | w = int(r) - int(l)
143 | h = int(t) - int(b)
144 | t = int(b) + h
145 | reg = self.copy_from_bbox(bbox)
146 | stringBuffer = reg.to_string_argb()
147 | qImage = QtGui.QImage(stringBuffer, w, h,
148 | QtGui.QImage.Format_ARGB32)
149 |
150 | pixmap = QtGui.QPixmap.fromImage(qImage)
151 | p.drawPixmap(QtCore.QPoint(l, self.renderer.height-t), pixmap)
152 |
153 | # draw the zoom rectangle to the QPainter
154 | if self._drawRect is not None:
155 | p.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.DotLine))
156 | x, y, w, h = self._drawRect
157 | p.drawRect(x, y, w, h)
158 |
159 | self.blitbox = None
160 |
161 | def draw(self):
162 | """
163 | Draw the figure with Agg, and queue a request for a Qt draw.
164 | """
165 | # The Agg draw is done here; delaying causes problems with code that
166 | # uses the result of the draw() to update plot elements.
167 | FigureCanvasAgg.draw(self)
168 | self.update()
169 |
170 | def draw_idle(self):
171 | """
172 | Queue redraw of the Agg buffer and request Qt paintEvent.
173 | """
174 | # The Agg draw needs to be handled by the same thread matplotlib
175 | # modifies the scene graph from. Post Agg draw request to the
176 | # current event loop in order to ensure thread affinity and to
177 | # accumulate multiple draw requests from event handling.
178 | # TODO: queued signal connection might be safer than singleShot
179 | if not self._agg_draw_pending:
180 | self._agg_draw_pending = True
181 | QtCore.QTimer.singleShot(0, self.__draw_idle_agg)
182 |
183 | def __draw_idle_agg(self, *args):
184 | if self.height() < 0 or self.width() < 0:
185 | self._agg_draw_pending = False
186 | return
187 | try:
188 | FigureCanvasAgg.draw(self)
189 | self.update()
190 | except Exception:
191 | # Uncaught exceptions are fatal for PyQt5, so catch them instead.
192 | traceback.print_exc()
193 | finally:
194 | self._agg_draw_pending = False
195 |
196 | def blit(self, bbox=None):
197 | """
198 | Blit the region in bbox
199 | """
200 | # If bbox is None, blit the entire canvas. Otherwise
201 | # blit only the area defined by the bbox.
202 | if bbox is None and self.figure:
203 | bbox = self.figure.bbox
204 |
205 | self.blitbox = bbox
206 | l, b, w, h = bbox.bounds
207 | t = b + h
208 | self.repaint(l, self.renderer.height-t, w, h)
209 |
210 | def geometryChanged(self, new_geometry, old_geometry):
211 | w = new_geometry.width()
212 | h = new_geometry.height()
213 |
214 | if (w <= 0.0) and (h <= 0.0):
215 | return
216 |
217 | if DEBUG:
218 | print('resize (%d x %d)' % (w, h))
219 | print("FigureCanvasQtQuickAgg.geometryChanged(%d, %d)" % (w, h))
220 | dpival = self.figure.dpi
221 | winch = w / dpival
222 | hinch = h / dpival
223 | self.figure.set_size_inches(winch, hinch)
224 | FigureCanvasAgg.resize_event(self)
225 | self.draw_idle()
226 | QtQuick.QQuickPaintedItem.geometryChanged(self, new_geometry, old_geometry)
227 |
228 | def hoverEnterEvent(self, event):
229 | FigureCanvasAgg.enter_notify_event(self, guiEvent=event)
230 |
231 | def hoverLeaveEvent(self, event):
232 | QtWidgets.QApplication.restoreOverrideCursor()
233 | FigureCanvasAgg.leave_notify_event(self, guiEvent=event)
234 |
235 | def hoverMoveEvent(self, event):
236 | x = event.pos().x()
237 | # flipy so y=0 is bottom of canvas
238 | y = self.figure.bbox.height - event.pos().y()
239 | FigureCanvasAgg.motion_notify_event(self, x, y, guiEvent=event)
240 |
241 | # if DEBUG:
242 | # print('hover move')
243 |
244 | # hoverMoveEvent kicks in when no mouse buttons are pressed
245 | # otherwise mouseMoveEvent are emitted
246 | def mouseMoveEvent(self, event):
247 | x = event.x()
248 | # flipy so y=0 is bottom of canvas
249 | y = self.figure.bbox.height - event.y()
250 | FigureCanvasAgg.motion_notify_event(self, x, y, guiEvent=event)
251 | # if DEBUG:
252 | # print('mouse move')
253 |
254 | def mousePressEvent(self, event):
255 | x = event.pos().x()
256 | # flipy so y=0 is bottom of canvas
257 | y = self.figure.bbox.height - event.pos().y()
258 | button = self.buttond.get(event.button())
259 | if button is not None:
260 | FigureCanvasAgg.button_press_event(self, x, y, button,
261 | guiEvent=event)
262 | if DEBUG:
263 | print('button pressed:', event.button())
264 |
265 | def mouseReleaseEvent(self, event):
266 | x = event.x()
267 | # flipy so y=0 is bottom of canvas
268 | y = self.figure.bbox.height - event.y()
269 | button = self.buttond.get(event.button())
270 | if button is not None:
271 | FigureCanvasAgg.button_release_event(self, x, y, button,
272 | guiEvent=event)
273 | if DEBUG:
274 | print('button released')
275 |
276 | def mouseDoubleClickEvent(self, event):
277 | x = event.pos().x()
278 | # flipy so y=0 is bottom of canvas
279 | y = self.figure.bbox.height - event.pos().y()
280 | button = self.buttond.get(event.button())
281 | if button is not None:
282 | FigureCanvasAgg.button_press_event(self, x, y,
283 | button, dblclick=True,
284 | guiEvent=event)
285 | if DEBUG:
286 | print('button doubleclicked:', event.button())
287 |
288 | def wheelEvent(self, event):
289 | x = event.x()
290 | # flipy so y=0 is bottom of canvas
291 | y = self.figure.bbox.height - event.y()
292 | # from QWheelEvent::delta doc
293 | if event.pixelDelta().x() == 0 and event.pixelDelta().y() == 0:
294 | steps = event.angleDelta().y() / 120
295 | else:
296 | steps = event.pixelDelta().y()
297 |
298 | if steps != 0:
299 | FigureCanvasAgg.scroll_event(self, x, y, steps, guiEvent=event)
300 | if DEBUG:
301 | print('scroll event: '
302 | 'steps = %i ' % (steps))
303 |
304 | def keyPressEvent(self, event):
305 | key = self._get_key(event)
306 | if key is None:
307 | return
308 | FigureCanvasAgg.key_press_event(self, key, guiEvent=event)
309 | if DEBUG:
310 | print('key press', key)
311 |
312 | def keyReleaseEvent(self, event):
313 | key = self._get_key(event)
314 | if key is None:
315 | return
316 | FigureCanvasAgg.key_release_event(self, key, guiEvent=event)
317 | if DEBUG:
318 | print('key release', key)
319 |
320 | def _get_key(self, event):
321 | if event.isAutoRepeat():
322 | return None
323 |
324 | event_key = event.key()
325 | event_mods = int(event.modifiers()) # actually a bitmask
326 |
327 | # get names of the pressed modifier keys
328 | # bit twiddling to pick out modifier keys from event_mods bitmask,
329 | # if event_key is a MODIFIER, it should not be duplicated in mods
330 | mods = [name for name, mod_key, qt_key in MODIFIER_KEYS
331 | if event_key != qt_key and (event_mods & mod_key) == mod_key]
332 | try:
333 | # for certain keys (enter, left, backspace, etc) use a word for the
334 | # key, rather than unicode
335 | key = SPECIAL_KEYS[event_key]
336 | except KeyError:
337 | # unicode defines code points up to 0x0010ffff
338 | # QT will use Key_Codes larger than that for keyboard keys that are
339 | # are not unicode characters (like multimedia keys)
340 | # skip these
341 | # if you really want them, you should add them to SPECIAL_KEYS
342 | MAX_UNICODE = 0x10ffff
343 | if event_key > MAX_UNICODE:
344 | return None
345 |
346 | key = six.unichr(event_key)
347 | # qt delivers capitalized letters. fix capitalization
348 | # note that capslock is ignored
349 | if 'shift' in mods:
350 | mods.remove('shift')
351 | else:
352 | key = key.lower()
353 |
354 | mods.reverse()
355 | return '+'.join(mods + [key])
356 |
357 | def new_timer(self, *args, **kwargs):
358 | """
359 | Creates a new backend-specific subclass of
360 | :class:`backend_bases.Timer`. This is useful for getting
361 | periodic events through the backend's native event
362 | loop. Implemented only for backends with GUIs.
363 |
364 | optional arguments:
365 |
366 | *interval*
367 | Timer interval in milliseconds
368 |
369 | *callbacks*
370 | Sequence of (func, args, kwargs) where func(*args, **kwargs)
371 | will be executed by the timer every *interval*.
372 | """
373 | return TimerQT(*args, **kwargs)
374 |
375 | def flush_events(self):
376 | global qApp
377 | qApp.processEvents()
378 |
379 | def start_event_loop(self, timeout):
380 | FigureCanvasAgg.start_event_loop_default(self, timeout)
381 |
382 | start_event_loop.__doc__ = \
383 | FigureCanvasAgg.start_event_loop_default.__doc__
384 |
385 | def stop_event_loop(self):
386 | FigureCanvasAgg.stop_event_loop_default(self)
387 |
388 | stop_event_loop.__doc__ = FigureCanvasAgg.stop_event_loop_default.__doc__
389 |
390 |
391 | class FigureQtQuickAggToolbar(FigureCanvasQtQuickAgg):
392 | """ This class creates a QtQuick Item encapsulating a Matplotlib
393 | Figure and all the functions to interact with the 'standard'
394 | Matplotlib navigation toolbar.
395 | """
396 |
397 | cursord = {
398 | cursors.MOVE: QtCore.Qt.SizeAllCursor,
399 | cursors.HAND: QtCore.Qt.PointingHandCursor,
400 | cursors.POINTER: QtCore.Qt.ArrowCursor,
401 | cursors.SELECT_REGION: QtCore.Qt.CrossCursor,
402 | }
403 |
404 | messageChanged = QtCore.pyqtSignal(str)
405 |
406 | leftChanged = QtCore.pyqtSignal()
407 | rightChanged = QtCore.pyqtSignal()
408 | topChanged = QtCore.pyqtSignal()
409 | bottomChanged = QtCore.pyqtSignal()
410 | wspaceChanged = QtCore.pyqtSignal()
411 | hspaceChanged = QtCore.pyqtSignal()
412 |
413 | def __init__(self, figure, parent=None, coordinates=True):
414 | if DEBUG:
415 | print('FigureQtQuickAggToolbar qtquick5: ', figure)
416 |
417 | FigureCanvasQtQuickAgg.__init__(self, figure=figure, parent=parent)
418 |
419 | self._message = ""
420 | #
421 | # Attributes from NavigationToolbar2QT
422 | #
423 | self.coordinates = coordinates
424 | self._actions = {}
425 |
426 | # reference holder for subplots_adjust window
427 | self.adj_window = None
428 | #
429 | # Attributes from NavigationToolbar2
430 | #
431 | self.canvas = self.figure.canvas
432 | self.toolbar = self
433 | # a dict from axes index to a list of view limits
434 | self._views = matplotlib.cbook.Stack()
435 | self._positions = matplotlib.cbook.Stack() # stack of subplot positions
436 | self._xypress = None # the location and axis info at the time
437 | # of the press
438 | self._idPress = None
439 | self._idRelease = None
440 | self._active = None
441 | self._lastCursor = None
442 |
443 | self._idDrag = self.canvas.mpl_connect(
444 | 'motion_notify_event', self.mouse_move)
445 |
446 | self._ids_zoom = []
447 | self._zoom_mode = None
448 |
449 | self._button_pressed = None # determined by the button pressed
450 | # at start
451 |
452 | self.mode = '' # a mode string for the status bar
453 | self.set_history_buttons()
454 |
455 | #
456 | # Store margin
457 | #
458 | self._defaults = {}
459 | for attr in ('left', 'bottom', 'right', 'top', 'wspace', 'hspace', ):
460 | val = getattr(self.figure.subplotpars, attr)
461 | self._defaults[attr] = val
462 | setattr(self, attr, val)
463 |
464 | @QtCore.pyqtProperty('QString', notify=messageChanged)
465 | def message(self):
466 | return self._message
467 |
468 | @message.setter
469 | def message(self, msg):
470 | if msg != self._message:
471 | self._message = msg
472 | self.messageChanged.emit(msg)
473 |
474 | @QtCore.pyqtProperty('QString', constant=True)
475 | def defaultDirectory(self):
476 | startpath = matplotlib.rcParams.get('savefig.directory', '')
477 | return os.path.expanduser(startpath)
478 |
479 | @QtCore.pyqtProperty('QStringList', constant=True)
480 | def fileFilters(self):
481 | filetypes = self.canvas.get_supported_filetypes_grouped()
482 | sorted_filetypes = list(six.iteritems(filetypes))
483 | sorted_filetypes.sort()
484 |
485 | filters = []
486 | for name, exts in sorted_filetypes:
487 | exts_list = " ".join(['*.%s' % ext for ext in exts])
488 | filter = '%s (%s)' % (name, exts_list)
489 | filters.append(filter)
490 |
491 | return filters
492 |
493 | @QtCore.pyqtProperty('QString', constant=True)
494 | def defaultFileFilter(self):
495 | default_filetype = self.canvas.get_default_filetype()
496 |
497 | selectedFilter = None
498 | for filter in self.fileFilters:
499 | exts = filter.split('(', maxsplit=1)[1]
500 | exts = exts[:-1].split()
501 | if default_filetype in exts:
502 | selectedFilter = filter
503 | break
504 |
505 | if selectedFilter is None:
506 | selectedFilter = self.fileFilters[0]
507 |
508 | return selectedFilter
509 |
510 | @QtCore.pyqtProperty(float, notify=leftChanged)
511 | def left(self):
512 | return self.figure.subplotpars.left
513 |
514 | @left.setter
515 | def left(self, value):
516 | if value != self.figure.subplotpars.left:
517 | self.figure.subplots_adjust(left=value)
518 | self.leftChanged.emit()
519 |
520 | self.figure.canvas.draw_idle()
521 |
522 | @QtCore.pyqtProperty(float, notify=rightChanged)
523 | def right(self):
524 | return self.figure.subplotpars.right
525 |
526 | @right.setter
527 | def right(self, value):
528 | if value != self.figure.subplotpars.right:
529 | self.figure.subplots_adjust(right=value)
530 | self.rightChanged.emit()
531 |
532 | self.figure.canvas.draw_idle()
533 |
534 | @QtCore.pyqtProperty(float, notify=topChanged)
535 | def top(self):
536 | return self.figure.subplotpars.top
537 |
538 | @top.setter
539 | def top(self, value):
540 | if value != self.figure.subplotpars.top:
541 | self.figure.subplots_adjust(top=value)
542 | self.topChanged.emit()
543 |
544 | self.figure.canvas.draw_idle()
545 |
546 | @QtCore.pyqtProperty(float, notify=bottomChanged)
547 | def bottom(self):
548 | return self.figure.subplotpars.bottom
549 |
550 | @bottom.setter
551 | def bottom(self, value):
552 | if value != self.figure.subplotpars.bottom:
553 | self.figure.subplots_adjust(bottom=value)
554 | self.bottomChanged.emit()
555 |
556 | self.figure.canvas.draw_idle()
557 |
558 | @QtCore.pyqtProperty(float, notify=hspaceChanged)
559 | def hspace(self):
560 | return self.figure.subplotpars.hspace
561 |
562 | @hspace.setter
563 | def hspace(self, value):
564 | if value != self.figure.subplotpars.hspace:
565 | self.figure.subplots_adjust(hspace=value)
566 | self.hspaceChanged.emit()
567 |
568 | self.figure.canvas.draw_idle()
569 |
570 | @QtCore.pyqtProperty(float, notify=wspaceChanged)
571 | def wspace(self):
572 | return self.figure.subplotpars.wspace
573 |
574 | @wspace.setter
575 | def wspace(self, value):
576 | if value != self.figure.subplotpars.wspace:
577 | self.figure.subplots_adjust(wspace=value)
578 | self.wspaceChanged.emit()
579 |
580 | self.figure.canvas.draw_idle()
581 |
582 | def mouse_move(self, event):
583 | self._set_cursor(event)
584 |
585 | if event.inaxes and event.inaxes.get_navigate():
586 |
587 | try:
588 | s = event.inaxes.format_coord(event.xdata, event.ydata)
589 | except (ValueError, OverflowError):
590 | pass
591 | else:
592 | artists = [a for a in event.inaxes.mouseover_set
593 | if a.contains(event)]
594 |
595 | if artists:
596 |
597 | a = max(enumerate(artists), key=lambda x: x[1].zorder)[1]
598 | if a is not event.inaxes.patch:
599 | data = a.get_cursor_data(event)
600 | if data is not None:
601 | s += ' [{:s}]'.format(a.format_cursor_data(data))
602 |
603 | if len(self.mode):
604 | self.message = '{:s}, {:s}'.format(self.mode, s)
605 | else:
606 | self.message = s
607 | else:
608 | self.message = self.mode
609 |
610 | def dynamic_update(self):
611 | self.canvas.draw_idle()
612 |
613 | def push_current(self):
614 | """push the current view limits and position onto the stack"""
615 | views = []
616 | pos = []
617 | for a in self.canvas.figure.get_axes():
618 | views.append(a._get_view())
619 | # Store both the original and modified positions
620 | pos.append((
621 | a.get_position(True).frozen(),
622 | a.get_position().frozen()))
623 | self._views.push(views)
624 | self._positions.push(pos)
625 | self.set_history_buttons()
626 |
627 | def set_history_buttons(self):
628 | """Enable or disable back/forward button"""
629 | pass
630 |
631 | def _update_view(self):
632 | """Update the viewlim and position from the view and
633 | position stack for each axes
634 | """
635 |
636 | views = self._views()
637 | if views is None:
638 | return
639 | pos = self._positions()
640 | if pos is None:
641 | return
642 | for i, a in enumerate(self.canvas.figure.get_axes()):
643 | a._set_view(views[i])
644 | # Restore both the original and modified positions
645 | a.set_position(pos[i][0], 'original')
646 | a.set_position(pos[i][1], 'active')
647 |
648 | self.canvas.draw_idle()
649 |
650 | @QtCore.pyqtSlot()
651 | def home(self, *args):
652 | """Restore the original view"""
653 | self._views.home()
654 | self._positions.home()
655 | self.set_history_buttons()
656 | self._update_view()
657 |
658 | @QtCore.pyqtSlot()
659 | def forward(self, *args):
660 | """Move forward in the view lim stack"""
661 | self._views.forward()
662 | self._positions.forward()
663 | self.set_history_buttons()
664 | self._update_view()
665 |
666 | @QtCore.pyqtSlot()
667 | def back(self, *args):
668 | """move back up the view lim stack"""
669 | self._views.back()
670 | self._positions.back()
671 | self.set_history_buttons()
672 | self._update_view()
673 |
674 | def _set_cursor(self, event):
675 | if not event.inaxes or not self._active:
676 | if self._lastCursor != cursors.POINTER:
677 | self.set_cursor(cursors.POINTER)
678 | self._lastCursor = cursors.POINTER
679 | else:
680 | if self._active == 'ZOOM':
681 | if self._lastCursor != cursors.SELECT_REGION:
682 | self.set_cursor(cursors.SELECT_REGION)
683 | self._lastCursor = cursors.SELECT_REGION
684 | elif (self._active == 'PAN' and
685 | self._lastCursor != cursors.MOVE):
686 | self.set_cursor(cursors.MOVE)
687 |
688 | self._lastCursor = cursors.MOVE
689 |
690 | def set_cursor(self, cursor):
691 | """
692 | Set the current cursor to one of the :class:`Cursors`
693 | enums values
694 | """
695 | if DEBUG:
696 | print('Set cursor', cursor)
697 | self.canvas.setCursor(self.cursord[cursor])
698 |
699 | def draw_with_locators_update(self):
700 | """Redraw the canvases, update the locators"""
701 | for a in self.canvas.figure.get_axes():
702 | xaxis = getattr(a, 'xaxis', None)
703 | yaxis = getattr(a, 'yaxis', None)
704 | locators = []
705 | if xaxis is not None:
706 | locators.append(xaxis.get_major_locator())
707 | locators.append(xaxis.get_minor_locator())
708 | if yaxis is not None:
709 | locators.append(yaxis.get_major_locator())
710 | locators.append(yaxis.get_minor_locator())
711 |
712 | for loc in locators:
713 | loc.refresh()
714 | self.canvas.draw_idle()
715 |
716 | def press(self, event):
717 | """Called whenever a mouse button is pressed."""
718 | pass
719 |
720 | def press_pan(self, event):
721 | """the press mouse button in pan/zoom mode callback"""
722 |
723 | if event.button == 1:
724 | self._button_pressed = 1
725 | elif event.button == 3:
726 | self._button_pressed = 3
727 | else:
728 | self._button_pressed = None
729 | return
730 |
731 | x, y = event.x, event.y
732 |
733 | # push the current view to define home if stack is empty
734 | if self._views.empty():
735 | self.push_current()
736 |
737 | self._xypress = []
738 | for i, a in enumerate(self.canvas.figure.get_axes()):
739 | if (x is not None and y is not None and a.in_axes(event) and
740 | a.get_navigate() and a.can_pan()):
741 | a.start_pan(x, y, event.button)
742 | self._xypress.append((a, i))
743 | self.canvas.mpl_disconnect(self._idDrag)
744 | self._idDrag = self.canvas.mpl_connect('motion_notify_event',
745 | self.drag_pan)
746 |
747 | self.press(event)
748 |
749 | def release(self, event):
750 | """this will be called whenever mouse button is released"""
751 | pass
752 |
753 | def release_pan(self, event):
754 | """the release mouse button callback in pan/zoom mode"""
755 |
756 | if self._button_pressed is None:
757 | return
758 | self.canvas.mpl_disconnect(self._idDrag)
759 | self._idDrag = self.canvas.mpl_connect(
760 | 'motion_notify_event', self.mouse_move)
761 | for a, ind in self._xypress:
762 | a.end_pan()
763 | if not self._xypress:
764 | return
765 | self._xypress = []
766 | self._button_pressed = None
767 | self.push_current()
768 | self.release(event)
769 | self.draw_with_locators_update()
770 |
771 | def drag_pan(self, event):
772 | """the drag callback in pan/zoom mode"""
773 |
774 | for a, ind in self._xypress:
775 | #safer to use the recorded button at the press than current button:
776 | #multiple button can get pressed during motion...
777 | a.drag_pan(self._button_pressed, event.key, event.x, event.y)
778 | self.dynamic_update()
779 |
780 | @QtCore.pyqtSlot()
781 | def pan(self, *args):
782 | """Activate the pan/zoom tool. pan with left button, zoom with right"""
783 | # set the pointer icon and button press funcs to the
784 | # appropriate callbacks
785 |
786 | if self._active == 'PAN':
787 | self._active = None
788 | else:
789 | self._active = 'PAN'
790 | if self._idPress is not None:
791 | self._idPress = self.canvas.mpl_disconnect(self._idPress)
792 | self.mode = ''
793 |
794 | if self._idRelease is not None:
795 | self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
796 | self.mode = ''
797 |
798 | if self._active:
799 | self._idPress = self.canvas.mpl_connect(
800 | 'button_press_event', self.press_pan)
801 | self._idRelease = self.canvas.mpl_connect(
802 | 'button_release_event', self.release_pan)
803 | self.mode = 'pan/zoom'
804 | self.canvas.widgetlock(self)
805 | else:
806 | self.canvas.widgetlock.release(self)
807 |
808 | for a in self.canvas.figure.get_axes():
809 | a.set_navigate_mode(self._active)
810 |
811 | self.message = self.mode
812 |
813 | def draw_rubberband(self, event, x0, y0, x1, y1):
814 | """Draw a rectangle rubberband to indicate zoom limits"""
815 | height = self.canvas.figure.bbox.height
816 | y1 = height - y1
817 | y0 = height - y0
818 |
819 | w = abs(x1 - x0)
820 | h = abs(y1 - y0)
821 |
822 | rect = [int(val)for val in (min(x0, x1), min(y0, y1), w, h)]
823 | self.canvas.drawRectangle(rect)
824 |
825 | def remove_rubberband(self):
826 | """Remove the rubberband"""
827 | self.canvas.drawRectangle(None)
828 |
829 | def _switch_on_zoom_mode(self, event):
830 | self._zoom_mode = event.key
831 | self.mouse_move(event)
832 |
833 | def _switch_off_zoom_mode(self, event):
834 | self._zoom_mode = None
835 | self.mouse_move(event)
836 |
837 | def drag_zoom(self, event):
838 | """the drag callback in zoom mode"""
839 |
840 | if self._xypress:
841 | x, y = event.x, event.y
842 | lastx, lasty, a, ind, view = self._xypress[0]
843 |
844 | # adjust x, last, y, last
845 | x1, y1, x2, y2 = a.bbox.extents
846 | x, lastx = max(min(x, lastx), x1), min(max(x, lastx), x2)
847 | y, lasty = max(min(y, lasty), y1), min(max(y, lasty), y2)
848 |
849 | if self._zoom_mode == "x":
850 | x1, y1, x2, y2 = a.bbox.extents
851 | y, lasty = y1, y2
852 | elif self._zoom_mode == "y":
853 | x1, y1, x2, y2 = a.bbox.extents
854 | x, lastx = x1, x2
855 |
856 | self.draw_rubberband(event, x, y, lastx, lasty)
857 |
858 | def press_zoom(self, event):
859 | """the press mouse button in zoom to rect mode callback"""
860 | # If we're already in the middle of a zoom, pressing another
861 | # button works to "cancel"
862 | if self._ids_zoom != []:
863 | for zoom_id in self._ids_zoom:
864 | self.canvas.mpl_disconnect(zoom_id)
865 | self.release(event)
866 | self.draw_with_locators_update()
867 | self._xypress = None
868 | self._button_pressed = None
869 | self._ids_zoom = []
870 | return
871 |
872 | if event.button == 1:
873 | self._button_pressed = 1
874 | elif event.button == 3:
875 | self._button_pressed = 3
876 | else:
877 | self._button_pressed = None
878 | return
879 |
880 | x, y = event.x, event.y
881 |
882 | # push the current view to define home if stack is empty
883 | if self._views.empty():
884 | self.push_current()
885 |
886 | self._xypress = []
887 | for i, a in enumerate(self.canvas.figure.get_axes()):
888 | if (x is not None and y is not None and a.in_axes(event) and
889 | a.get_navigate() and a.can_zoom()):
890 | self._xypress.append((x, y, a, i, a._get_view()))
891 |
892 | id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
893 | id2 = self.canvas.mpl_connect('key_press_event',
894 | self._switch_on_zoom_mode)
895 | id3 = self.canvas.mpl_connect('key_release_event',
896 | self._switch_off_zoom_mode)
897 |
898 | self._ids_zoom = id1, id2, id3
899 | self._zoom_mode = event.key
900 |
901 | self.press(event)
902 |
903 | def release_zoom(self, event):
904 | """the release mouse button callback in zoom to rect mode"""
905 | for zoom_id in self._ids_zoom:
906 | self.canvas.mpl_disconnect(zoom_id)
907 | self._ids_zoom = []
908 |
909 | self.remove_rubberband()
910 |
911 | if not self._xypress:
912 | return
913 |
914 | last_a = []
915 |
916 | for cur_xypress in self._xypress:
917 | x, y = event.x, event.y
918 | lastx, lasty, a, ind, view = cur_xypress
919 | # ignore singular clicks - 5 pixels is a threshold
920 | # allows the user to "cancel" a zoom action
921 | # by zooming by less than 5 pixels
922 | if ((abs(x - lastx) < 5 and self._zoom_mode!="y") or
923 | (abs(y - lasty) < 5 and self._zoom_mode!="x")):
924 | self._xypress = None
925 | self.release(event)
926 | self.draw_with_locators_update()
927 | return
928 |
929 | # detect twinx,y axes and avoid double zooming
930 | twinx, twiny = False, False
931 | if last_a:
932 | for la in last_a:
933 | if a.get_shared_x_axes().joined(a, la):
934 | twinx = True
935 | if a.get_shared_y_axes().joined(a, la):
936 | twiny = True
937 | last_a.append(a)
938 |
939 | if self._button_pressed == 1:
940 | direction = 'in'
941 | elif self._button_pressed == 3:
942 | direction = 'out'
943 | else:
944 | continue
945 |
946 | a._set_view_from_bbox((lastx, lasty, x, y), direction,
947 | self._zoom_mode, twinx, twiny)
948 |
949 | self.draw_with_locators_update()
950 | self._xypress = None
951 | self._button_pressed = None
952 |
953 | self._zoom_mode = None
954 |
955 | self.push_current()
956 | self.release(event)
957 |
958 | @QtCore.pyqtSlot()
959 | def zoom(self, *args):
960 | """Activate zoom to rect mode"""
961 | if self._active == 'ZOOM':
962 | self._active = None
963 | else:
964 | self._active = 'ZOOM'
965 |
966 | if self._idPress is not None:
967 | self._idPress = self.canvas.mpl_disconnect(self._idPress)
968 | self.mode = ''
969 |
970 | if self._idRelease is not None:
971 | self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
972 | self.mode = ''
973 |
974 | if self._active:
975 | self._idPress = self.canvas.mpl_connect('button_press_event',
976 | self.press_zoom)
977 | self._idRelease = self.canvas.mpl_connect('button_release_event',
978 | self.release_zoom)
979 | self.mode = 'zoom rect'
980 | self.canvas.widgetlock(self)
981 | else:
982 | self.canvas.widgetlock.release(self)
983 |
984 | for a in self.canvas.figure.get_axes():
985 | a.set_navigate_mode(self._active)
986 |
987 | self.message = self.mode
988 |
989 | @QtCore.pyqtSlot()
990 | def tight_layout(self):
991 | self.figure.tight_layout()
992 | # self._setSliderPositions()
993 | self.draw_idle()
994 |
995 | @QtCore.pyqtSlot()
996 | def reset_margin(self):
997 | self.figure.subplots_adjust(**self._defaults)
998 | # self._setSliderPositions()
999 | self.draw_idle()
1000 |
1001 | @QtCore.pyqtSlot(str)
1002 | def print_figure(self, fname, *args, **kwargs):
1003 | if fname:
1004 | fname = QtCore.QUrl(fname).toLocalFile()
1005 | # save dir for next time
1006 | savefig_dir = os.path.dirname(six.text_type(fname))
1007 | matplotlib.rcParams['savefig.directory'] = savefig_dir
1008 | fname = six.text_type(fname)
1009 | FigureCanvasAgg.print_figure(self, fname, *args, **kwargs)
1010 | self.draw()
1011 |
1012 | FigureCanvasQTAgg = FigureCanvasQtQuickAgg
1013 | FigureCanvasQTAggToolbar = FigureQtQuickAggToolbar
--------------------------------------------------------------------------------
/backend/mpl_qquick.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | CALL C:\Anaconda3\Scripts\activate.bat qtquick
3 | python %~dpn0.py %*
--------------------------------------------------------------------------------
/backend/mpl_qquick.py:
--------------------------------------------------------------------------------
1 | """
2 | Series of data are loaded from a .csv file, and their names are
3 | displayed in a checkable list view. The user can select the series
4 | it wants from the list and plot them on a matplotlib canvas.
5 | Use the sample .csv file that comes with the script for an example
6 | of data series.
7 |
8 | [2016-11-06] Convert to QtQuick 2.0 - Qt.labs.controls 1.0
9 | [2016-11-05] Convert to QtQuick 2.0 - QtQuick Controls 1.0
10 | [2016-11-01] Update to PyQt5.6 and python 3.5
11 |
12 | Frederic Collonval (fcollonval@gmail.com)
13 |
14 | Inspired from the work of Eli Bendersky (eliben@gmail.com):
15 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
16 |
17 | License: MIT License
18 | Last modified: 2016-11-06
19 | """
20 | import sys, os, csv
21 | from PyQt5.QtCore import Qt, QObject, QUrl
22 | from PyQt5.QtGui import QGuiApplication
23 | from PyQt5.QtQml import qmlRegisterType
24 | from PyQt5.QtQuick import QQuickView
25 |
26 | from backend_qtquick5 import FigureCanvasQTAgg
27 |
28 | import matplotlib
29 | # matplotlib.use('module://backend_qtquick5')
30 | # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
31 | # from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
32 | # from matplotlib.figure import Figure
33 | import matplotlib.pyplot as plt
34 |
35 | import numpy as np
36 |
37 | def main():
38 | argv = sys.argv
39 |
40 | # Trick to set the style / not found how to do it in pythonic way
41 | argv.extend(["-style", "universal"])
42 | app = QGuiApplication(argv)
43 |
44 | qmlRegisterType(FigureCanvasQTAgg, "Backend", 1, 0, "FigureCanvas")
45 |
46 | view = QQuickView()
47 | view.setResizeMode(QQuickView.SizeRootObjectToView)
48 | view.setSource(QUrl('backend_qtquick5/Figure.qml'))
49 | view.show()
50 |
51 | win = view.rootObject()
52 | fig = win.findChild(QObject, "figure").getFigure()
53 | print(fig)
54 | ax = fig.add_subplot(111)
55 | x = np.linspace(-5, 5)
56 | ax.plot(x, np.sin(x))
57 |
58 | rc = app.exec_()
59 | # There is some trouble arising when deleting all the objects here
60 | # but I have not figure out how to solve the error message.
61 | # It looks like 'app' is destroyed before some QObject
62 | sys.exit(rc)
63 |
64 |
65 | if __name__ == "__main__":
66 | main()
--------------------------------------------------------------------------------
/backend/mpl_qquick_toolbar.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | CALL C:\Anaconda3\Scripts\activate.bat qtquick
3 | python %~dpn0.py %*
--------------------------------------------------------------------------------
/backend/mpl_qquick_toolbar.py:
--------------------------------------------------------------------------------
1 | """
2 | Series of data are loaded from a .csv file, and their names are
3 | displayed in a checkable list view. The user can select the series
4 | it wants from the list and plot them on a matplotlib canvas.
5 | Use the sample .csv file that comes with the script for an example
6 | of data series.
7 |
8 | [2016-11-06] Convert to QtQuick 2.0 - Qt.labs.controls 1.0
9 | [2016-11-05] Convert to QtQuick 2.0 - QtQuick Controls 1.0
10 | [2016-11-01] Update to PyQt5.6 and python 3.5
11 |
12 | Frederic Collonval (fcollonval@gmail.com)
13 |
14 | Inspired from the work of Eli Bendersky (eliben@gmail.com):
15 | https://github.com/eliben/code-for-blog/tree/master/2009/pyqt_dataplot_demo
16 |
17 | License: MIT License
18 | Last modified: 2016-11-06
19 | """
20 | import sys, os, csv
21 | from PyQt5.QtCore import Qt, QObject, QUrl
22 | from PyQt5.QtGui import QGuiApplication
23 | from PyQt5.QtQml import qmlRegisterType
24 | from PyQt5.QtQuick import QQuickView
25 |
26 | from backend_qtquick5 import FigureCanvasQTAggToolbar, MatplotlibIconProvider
27 |
28 | import matplotlib
29 | # matplotlib.use('module://backend_qtquick5')
30 | # from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
31 | # from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
32 | # from matplotlib.figure import Figure
33 | import matplotlib.pyplot as plt
34 |
35 | import numpy as np
36 |
37 | def main():
38 | argv = sys.argv
39 |
40 | app = QGuiApplication(argv)
41 |
42 | qmlRegisterType(FigureCanvasQTAggToolbar, "Backend", 1, 0, "FigureToolbar")
43 |
44 | imgProvider = MatplotlibIconProvider()
45 | view = QQuickView()
46 | view.engine().addImageProvider("mplIcons", imgProvider)
47 | view.setResizeMode(QQuickView.SizeRootObjectToView)
48 | view.setSource(QUrl('backend_qtquick5/FigureToolbar.qml'))
49 |
50 | win = view.rootObject()
51 | fig = win.findChild(QObject, "figure").getFigure()
52 | ax = fig.add_subplot(111)
53 | x = np.linspace(-5, 5)
54 | ax.plot(x, np.sin(x))
55 |
56 | view.show()
57 |
58 | rc = app.exec_()
59 | # There is some trouble arising when deleting all the objects here
60 | # but I have not figure out how to solve the error message.
61 | # It looks like 'app' is destroyed before some QObject
62 | sys.exit(rc)
63 |
64 |
65 | if __name__ == "__main__":
66 | main()
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: qtquick
2 | channels: !!python/tuple
3 | - defaults
4 | dependencies:
5 | - cycler=0.10.0=py35_0
6 | - icu=57.1=vc14_0
7 | - jpeg=8d=vc14_2
8 | - libpng=1.6.22=vc14_0
9 | - matplotlib=1.5.3=np111py35_1
10 | - mkl=11.3.3=1
11 | - numpy=1.11.2=py35_0
12 | - openssl=1.0.2j=vc14_0
13 | - pip=9.0.0=py35_0
14 | - pyparsing=2.1.4=py35_0
15 | - pyqt=5.6.0=py35_0
16 | - python=3.5.2=0
17 | - python-dateutil=2.5.3=py35_0
18 | - pytz=2016.7=py35_0
19 | - qt=5.6.0=vc14_0
20 | - setuptools=27.2.0=py35_1
21 | - sip=4.18=py35_0
22 | - six=1.10.0=py35_0
23 | - tk=8.5.18=vc14_0
24 | - vs2015_runtime=14.0.25123=0
25 | - wheel=0.29.0=py35_0
26 | - zlib=1.2.8=vc14_3
27 | prefix: C:\Anaconda3\envs\qtquick
28 |
29 |
--------------------------------------------------------------------------------
/qt_mpl_data.csv:
--------------------------------------------------------------------------------
1 | 1990 Sales,15, 82, 95, 83, 123, 9, 59, 30, 19, 40, 99, 10
2 | 1991 Sales,12, 16, 15, 17, 24, 38, 71, 88, 62, 40, 43, 12
3 | 1992 Sales,6, 26, 11, 67, 24, 38, 1, 8, 62, 40, 43, 12
4 | 1993 Sales,19, 19, 19, 19, 26, 39, 79, 89, 69, 49, 49, 19
5 |
--------------------------------------------------------------------------------