python: graphviz -- how to specify node render order - python-3.x

How can I ensure that nodes are rendered in the order that they are defined in my source code? In my code, I have node 'A' defined first, and so was hoping it would be rendered at the 'top' of the graph, and then the three 'child class' nodes would appear below it (all in a row as they are in the embedded image). but as can be see in the embedded image, the opposite has occurred. I am using Python 3.7.12 in a Jupyter Notebook.
from IPython.display import display
import graphviz
p = graphviz.Digraph(node_attr={'shape':'box'},format='png')
p.node(name='A', label='parent class')
with p.subgraph() as s:
s.attr(rank='same')
s.node(name='B', label='child class 1')
s.node(name='C', label='child class 2')
s.node(name='D', label='child class 3')
p.edges(['BA', 'CA', 'DA'])
p.render('a.gv',view=False)
with open('a.gv') as f:
dot = f.read()
display(graphviz.Source(dot))
Does anyone know how to fix this?

OK I just had to set the 'rankdir' graph attribute to 'BT' which I believe means 'bottom-top'
here's the updated code:
from IPython.display import display
import graphviz
p = graphviz.Digraph(graph_attr={'rankdir':"BT"},
node_attr={'shape':'box'},
format='png')
p.node(name='A', label='parent class')
with p.subgraph() as s:
s.attr(rank='same')
s.node(name='B', label='child class 1')
s.node(name='C', label='child class 2')
s.node(name='D', label = 'child class 3')
p.edges(['BA', 'CA', 'DA'])
p.render('a.gv',view=False)
with open('a.gv') as f:
dot = f.read()
display(graphviz.Source(dot))

Related

PySide2 updating a graph

I have been knocking my head against the wall on the following issue for quite some times now and need some fresh pair of eyes to help me out.
In Qt Designer I created a tab with a QComboBox (to select a feature), a QPushButton (to instruct the plotting of the feature) and a QWidget (plot area, called mywidget). The whole code is largely inspired from various codes found on SO.
In main.py I connected the QPushButton to the following function (defined within my QtApp class):
def launchGraph(self):
df1 = ... #data from a data source
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar = NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
ax1f1 = self.mywidget.figure.add_subplot(111)
ax1f1.clear()
ax1f1.xaxis.set_major_formatter(mdates.DateFormatter('%b%-y'))
ax1f1.plot(df1['x'], df1['y'], linewidth=1, color='blue')
ax1f1.set(title='My Little Graph')
self.mywidget.canvas.draw()
The issue is that when I launched my window, select a feature and click the button, the correct graph is being shown. If I changed the feature and click the plot button, nothing happens. I did print the feature of the combobox and it prints the correct up-to-date value from the combobox however the graph is not replaced/updated. I also added a test-variable isgraph and used self.mywidget.figure.clear() but no success neither. canvas.repaint() doesn't update the graph neither. It feels like I need to use a test-variable to check whether a graph is there or not and if yes then I need to clen up the content of mywidget. But that seems overcomplicated for this issue (?)
For info I import the following:
from gui import main
from PySide2 import QtWidgets, QtCore, QtGui
from matplotlib.figure import Figure
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
Edit:
Here is the minimal/adapted full code:
from gui import main
from PySide2 import QtWidgets, QtCore, QtGui
from matplotlib.figure import Figure
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
class MyQtApp(main.Ui_MainWindow, QtWidgets.QMainWindow):
def __init__(self):
super(MyQtApp, self).__init__()
self.setupUi(self)
self.graphBtn.clicked.connect(self.launchGraph)
self.show()
def launchGraph(self):
if self.mycb.currrentText() == 'feature1':
df1 = ... #data from a data source
else: (#== feature2)
df1 = ... #some other data
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar =
NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
ax = self.mywidget.figure.add_subplot(111)
ax.clear()
ax.plot(df1['x'], df1['y'])
self.mywidget.canvas.draw()
In Qt Designer (file main.ui comnverted into. main.py), I placed:
- one combobox, called mycb and having 2 values: [feature1, feature2]
- one push button, called graphBtn
- a simple and empty QWidget called mywidget
The problem is most likely, that when you run the launchGraph after the initial run, the function creates another ax1f1 underneath the initial one. Therefore the initial one keeps on showing and no errors are displayed.
In this particular case, you want to keep working with the initial ax1f1 instead of re-declaring another one.
Something like this could fix the problem:
def launchGraph(self):
if self.mycb.currrentText() == 'feature1':
df1 = ['some_data'] #data from a data source
else: (#== feature2)
df1 = ['some_other_data'] #some other data
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar = NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
try:
self.ax1f1.clear()
except:
self.a1f1 = self.mywidget.figure.add_subplot(111)
self.ax1f1.clear()
self.ax1f1.xaxis.set_major_formatter(mdates.DateFormatter('%b%-y'))
self.ax1f1.plot(df1['x'], df1['y'], linewidth=1, color='blue')
self.ax1f1.set(title='My Little Graph')
self.mywidget.canvas.draw()

How can I get the individual points and their attributes from a scatterplot in pyqtgraph?

I was able to create a ScatterPlotItem in pyqtgraph without a hitch by promoting a Graphics View widget to a PlotWidget in Qt Designer. I plotted some random data on it and now I want to access the individual points I click on. The docs say that one can connect the sigClicked(self, points) signal, which, in theory, should return the points under the cursor. But that does not seem to be the case, because when I click on a point I get the same object regardless of which point I clicked. I suspect that this signal returns the entire ScatterPlotItem and not any specific point.
Here is my code so far:
import sys, time
from timeit import default_timer as timer
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QUrl, QEvent
from PyQt5.QtWidgets import *
from PyQt5 import QtMultimedia
from PyQt5.uic import loadUi
import pyqtgraph as pg
import numpy as np
class ScatterExample(QMainWindow):
def __init__(self):
# Main Loop
super(ScatterExample, self).__init__()
loadUi('<path/to/ui file>.ui', self)
self.setWindowTitle('ScatterExample')
self.scatter = pg.ScatterPlotItem(pxMode=False, pen=pg.mkPen(width=1, color='g'), symbol='t', size=1)
self.scatter.sigClicked.connect(self.onPointsClicked)
self.Scatter_Plot_View.addItem(self.scatter) # Scatter_Plot_View is the Graphics View I promoted to PlotWidget
n = 5
print('Number of points: ' + str(n))
data = np.random.normal(size=(2, n))
pos = [{'pos': data[:, i]} for i in range(n)]
now = pg.ptime.time()
self.scatter.setData(pos)
print(self.scatter.data)
def onPointsClicked(self, points):
print('Ain\'t getting individual points ', points)
points.setPen('b', width=2) # this turns EVERY point blue, not just the one clicked.
The above print statement prints:
Ain't getting individual points <pyqtgraph.graphicsItems.ScatterPlotItem.ScatterPlotItem object at 0x000001C36577F948>
How can I get the points I click on and their corresponding attributes, such as x and y coordinates?
As eyllansec was kind enough to suggest, I changed my def onPointsClicked(self, points): to def onPointsClicked(self, obj, points): and now pyqtgraph works a expected.

How to dynamically update label texts in kivy that is imported from excel file?

I am creating a questionnaire form in kivy. I have added few label widgets in my GUI. I don't want to define label texts statically in my code, instead my objective is to dynamically update label texts that is fetched from an excel file.
For example: my excel file has 2 questions:
Name of the company?
Department?
I have 2 label widgets in my GUI, and the text of widgets should be:
Name of the company?
Department?
respectively and has to be dynamically fetched from the excel file.
I encountered an error when i tried to run my code.
Questionnaire.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
import pandas as pd
class FetchData():
file = pd.read_excel("Questionnaire.xlsx")
Quest = file['QUESTIONS']
class Questions(Widget):
Data = FetchData().Quest
qvars =[]
company = ObjectProperty(None)
department = ObjectProperty(None)
qvars.append(company)
qvars.append(department)
def validate(self):
for i in range(len(self.qvars)):
self.qvars[i].text = self.Data[i]
class QuestionnaireApp(App):
def build(self):
return Questions()
if __name__=="__main__":
QuestionnaireApp().run()
Questionnaire.kv
<Questions>:
company:company
department:department
GridLayout:
cols:1
size:root.width, root.height
GridLayout:
cols:1
Label:
id:company
TextInput:
Label:
id:department
TextInput:
Button:
text:"process"
on_release: root.validate()
I am getting the following error:
File "C:/Users/pavan m sunder/virtual environments/android/Questionnaire.py", line 23, in validate
self.qvars[i].text = self.Data[i]
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
I referred to similar questions that had the same error but none matches specifically to my problem.
Using your qvars list messes things up because it's a list of the property objects, which doesn't have the right behaviour - Kivy Properties are descriptors, they only work at class level.
Instead, just access self.company or self.department in your methods.

Class attribute of type DataFrame cannot be inspected in pycharm debugger session

I have set up a function and a class method in python 3.6 that both fetch the boston data set. In the latter case the boston data set is stored as a class attribute of the object.
The 'Bunch' type is converted to a pandas dataframe in the exact same manner in both instances.
When I inspect both in the pycharm debugger, using the View as Array / Data Frame functionality of pycharm...
https://www.jetbrains.com/help/pycharm/viewing-as-array.html
...I can view the df that results by calling the function and assigning the output to a variable, but not the df that is assigned to the class attribute of the instantiated object.
from sklearn.datasets import load_boston
import pandas as pd
# define function to get boston data
def get_boston():
boston = load_boston()
df = pd.DataFrame(boston.data, columns=boston.feature_names)
df['target'] = boston.target
return df
class MyData:
"""
Object with boston data as attribute
"""
def __init__(self, raw_data=None, processed_data=None):
self.raw_data = raw_data
self.processed_data = processed_data
def get_data(self):
boston = load_boston()
df = pd.DataFrame(boston.data, columns=boston.feature_names)
df['target'] = boston.target
self.raw_data = df
my_data_1 = MyData()
my_data_1.get_data()
my_data_2 = get_boston()
print(my_data_1.raw_data.head(5))
When I run the debugger session, I can inspect the my_data_2 variable with the 'View as Data Frame' function in pycharm, but when I do the same for the class attribute raw_data, the 'View as Data Frame' dialog window shows - 'nothing to show', even though I can print it's content to the console properly (using pandas.DataFrame.head() method)
I am using PyCharm 2018.3.3 on Ubuntu Linux.

Return output of the function executed 'on_click'

How to get the output of the function executed on_click by ipywidgets.Button
outside the function to use in next steps? For example, I want to get back the value of a after every click to use in the next cell of the jupyter-notebook. So far I only get None.
from ipywidgets import Button
def add_num(ex):
a = b+1
print('a = ', a)
return a
b = 1
buttons = Button(description="Load/reload file list")
a = buttons.on_click(add_num)
display(buttons)
print(a)
The best way that I have found to do this type of thing is by creating a subclass of widgets.Button and then add a new traitlet attribute . That way you can load the value(s) that you want to perform operations on when you create a new button object. You can access this new traitlet attribute whenever you want inside or outside of the function. Here is an example;
from ipywidgets import widgets
from IPython.display import display
from traitlets import traitlets
class LoadedButton(widgets.Button):
"""A button that can holds a value as a attribute."""
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def add_num(ex):
ex.value = ex.value+1
print(ex.value)
lb = LoadedButton(description="Loaded", value=1)
lb.on_click(add_num)
display(lb)
Hope that helps. Please comment below if this does not solve your problem.

Resources