123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- import numpy as np
- from matplotlib.figure import Figure
- from mpl_toolkits.mplot3d import Axes3D
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
- from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
- from PyQt5.QtCore import Qt
- from PyQt5.QtWidgets import QVBoxLayout, QLabel, QSlider, QSpinBox, QPushButton, QSplitter
- from visma.io.checks import getVariables, getTokensType
- from visma.io.tokenize import getLHSandRHS
- from visma.functions.constant import Constant
- from visma.functions.operator import Binary
- from visma.functions.structure import FuncOp
- from visma.functions.variable import Variable
- def graphPlot(workspace, again, tokens):
- """Function for plotting graphs in 2D and 3D space
- 2D graphs are plotted for expression in one variable and equations in two variables. 3D graphs are plotted for expressions in two variables and equations in three variables.
- Arguments:
- workspace {QtWidgets.QWidget} -- main layout
- Returns:
- graphVars {list} -- variables to be plotted on the graph
- func {numpy.array(2D)/function(3D)} -- equation converted to compatible data type for plotting
- variables {list} -- variables in given equation
- again {bool} -- True when an equation can be plotted in 2D and 3D both else False
- Note:
- The func obtained from graphPlot() function is of different type for 2D and 3D plots. For 2D, func is a numpy array, and for 3D, func is a function.
- """
- if tokens is None:
- axisRange = workspace.axisRange
- tokens = workspace.eqToks[-1]
- else:
- axisRange = [10, 10, 10, 30]
- eqType = getTokensType(tokens)
- LHStok, RHStok = getLHSandRHS(tokens)
- variables = sorted(getVariables(LHStok, RHStok))
- dim = len(variables)
- if (dim == 1) or ((dim == 2) and eqType == "equation"):
- if again:
- variables.append('f(' + variables[0] + ')')
- graphVars, func = plotIn3D(LHStok, RHStok, variables, axisRange)
- else:
- graphVars, func = plotIn2D(LHStok, RHStok, variables, axisRange)
- if dim == 1:
- variables.append('f(' + variables[0] + ')')
- elif (dim == 2 and eqType == "expression") or ((dim == 3) and eqType == "equation"):
- graphVars, func = plotIn3D(LHStok, RHStok, variables, axisRange)
- if dim == 2:
- variables.append('f(' + variables[0] + ',' + variables[1] + ')')
- else:
- return [], None, None
- return graphVars, func, variables
- def plotIn2D(LHStok, RHStok, variables, axisRange):
- """Returns function array for 2D plots
- Arguments:
- LHStok {list} -- expression tokens
- RHStok {list} -- expression tokens
- variables {list} -- variables in equation
- axisRange {list} -- axis limits
- Returns:
- graphVars {list} -- variables for plotting
- func {numpy.array} -- equation to be plotted in 2D
- """
- xmin = -axisRange[0]
- xmax = axisRange[0]
- ymin = -axisRange[1]
- ymax = axisRange[1]
- xdelta = 0.01 * (xmax - xmin)
- ydelta = 0.01 * (ymax - ymin)
- xrange = np.arange(xmin, xmax, xdelta)
- yrange = np.arange(ymin, ymax, ydelta)
- graphVars = np.meshgrid(xrange, yrange)
- function = getFunction(LHStok, RHStok, variables, graphVars, 2)
- return graphVars, function
- def plotIn3D(LHStok, RHStok, variables, axisRange):
- """Returns function for 3D plots
- Arguments:
- LHStok {list} -- expression tokens
- RHStok {list} -- expression tokens
- variables {list} -- variables in equation
- axisRange {list} -- axis limits
- Returns:
- graphVars {list} -- variables for plotting
- func {function} -- equation to be plotted in 3D
- """
- xmin = -axisRange[0]
- xmax = axisRange[0]
- ymin = -axisRange[1]
- ymax = axisRange[1]
- zmin = -axisRange[2]
- zmax = axisRange[2]
- meshLayers = axisRange[3]
- xrange = np.linspace(xmin, xmax, meshLayers)
- yrange = np.linspace(ymin, ymax, meshLayers)
- zrange = np.linspace(zmin, zmax, meshLayers)
- graphVars = [xrange, yrange, zrange]
- def func(x, y, z):
- graphVars = [x, y, z]
- return getFunction(LHStok, RHStok, variables, graphVars, 3)
- return graphVars, func
- def getFunction(LHStok, RHStok, eqnVars, graphVars, dim):
- """Returns function for plotting
- Arguments:
- LHStok {list} -- expression tokens
- RHStok {list} -- expression tokens
- eqnVars {list} -- variables in equation
- graphVars {list} -- variables for plotting
- dim {int} -- dimenion of plot
- Returns:
- (LHS - RHS) {numpy.array(2D)/function(3D)} -- equation converted to compatible data type for plotting
- """
- LHS = getFuncExpr(LHStok, eqnVars, graphVars)
- if len(eqnVars) == dim:
- RHS = getFuncExpr(RHStok, eqnVars, graphVars)
- elif len(eqnVars) == dim - 1:
- RHS = graphVars[-1]
- return LHS - RHS
- def getFuncExpr(exprTok, eqnVars, graphVars):
- """Allocates variables in equation to graph variables to give final function compatible for plotting
- Arguments:
- exprTok {list} -- expression tokens
- eqnVars {list} -- variables in equation
- graphVars {list} -- variables for plotting
- Returns:
- expr {numpy.array(2D)/function(3D)} -- expression converted to compatible data type for plotting
- """
- expr = 0
- coeff = 1
- for token in exprTok:
- if isinstance(token, Variable):
- varProduct = 1
- for value, power in zip(token.value, token.power):
- varProduct *= graphVars[eqnVars.index(value)]**power
- expr += coeff * token.coefficient * varProduct
- elif isinstance(token, Constant):
- expr += coeff * token.value
- elif isinstance(token, FuncOp):
- pass
- elif isinstance(token, Binary) and token.value == '-':
- coeff = -1
- elif isinstance(token, Binary) and token.value == '+':
- coeff = 1
- return expr
- #######
- # GUI #
- #######
- def plotFigure2D(workspace):
- """GUI layout for plot figure
- Arguments:
- workspace {QtWidgets.QWidget} -- main layout
- Returns:
- layout {QtWidgets.QVBoxLayout} -- contains matplot figure
- """
- workspace.figure2D = Figure()
- workspace.canvas2D = FigureCanvas(workspace.figure2D)
- # workspace.figure2D.patch.set_facecolor('white')
- class NavigationCustomToolbar(NavigationToolbar):
- toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ()]
- workspace.toolbar2D = NavigationCustomToolbar(workspace.canvas2D, workspace)
- layout = QVBoxLayout()
- layout.addWidget(workspace.canvas2D)
- layout.addWidget(workspace.toolbar2D)
- return layout
- def plotFigure3D(workspace):
- """GUI layout for plot figure
- Arguments:
- workspace {QtWidgets.QWidget} -- main layout
- Returns:
- layout {QtWidgets.QVBoxLayout} -- contains matplot figure
- """
- workspace.figure3D = Figure()
- workspace.canvas3D = FigureCanvas(workspace.figure3D)
- # workspace.figure3D.patch.set_facecolor('white')
- class NavigationCustomToolbar(NavigationToolbar):
- toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ()]
- workspace.toolbar3D = NavigationCustomToolbar(workspace.canvas3D, workspace)
- layout = QVBoxLayout()
- layout.addWidget(workspace.canvas3D)
- layout.addWidget(workspace.toolbar3D)
- return layout
- def renderPlot(workspace, graphVars, func, variables, tokens=None):
- """Renders plot for functions in 2D and 3D
- Maps points from the numpy arrays for variables in given equation on the 2D/3D plot figure
- Arguments:
- workspace {QtWidgets.QWidget} -- main layout
- graphVars {list} -- variables for plotting
- dim {int} -- dimenion of plot
- variables {list} -- variables in equation
- """
- if len(graphVars) == 2:
- X, Y = graphVars[0], graphVars[1]
- ax = workspace.figure2D.add_subplot(111)
- ax.clear()
- ax.contour(X, Y, func, [0])
- ax.grid()
- ax.set_xlabel(r'$' + variables[0] + '$')
- ax.set_ylabel(r'$' + variables[1] + '$')
- workspace.figure2D.set_tight_layout({"pad": 1}) # removes extra padding
- workspace.canvas2D.draw()
- workspace.tabPlot.setCurrentIndex(0)
- elif len(graphVars) == 3:
- xrange = graphVars[0]
- yrange = graphVars[1]
- zrange = graphVars[2]
- ax = Axes3D(workspace.figure3D)
- for z in zrange:
- X, Y = np.meshgrid(xrange, yrange)
- Z = func(X, Y, z)
- ax.contour(X, Y, Z + z, [z], zdir='z')
- for y in yrange:
- X, Z = np.meshgrid(xrange, zrange)
- Y = func(X, y, Z)
- ax.contour(X, Y + y, Z, [y], zdir='y')
- for x in xrange:
- Y, Z = np.meshgrid(yrange, zrange)
- X = func(x, Y, Z)
- ax.contour(X + x, Y, Z, [x], zdir='x')
- if tokens is None:
- axisRange = workspace.axisRange
- else:
- axisRange = [10, 10, 10, 30]
- xmin = -axisRange[0]
- xmax = axisRange[0]
- ymin = -axisRange[1]
- ymax = axisRange[1]
- zmin = -axisRange[2]
- zmax = axisRange[2]
- ax.set_xlim3d(xmin, xmax)
- ax.set_ylim3d(ymin, ymax)
- ax.set_zlim3d(zmin, zmax)
- ax.set_xlabel(r'$' + variables[0] + '$')
- ax.set_ylabel(r'$' + variables[1] + '$')
- ax.set_zlabel(r'$' + variables[2] + '$')
- workspace.canvas3D.draw()
- workspace.tabPlot.setCurrentIndex(1)
- def plot(workspace, tokens=None):
- """When called from window.py it initiates rendering of equations.
- Arguments:
- workspace {QtWidgets.QWidget} -- main layout
- """
- from visma.io.tokenize import tokenizer
- workspace.figure2D.clear()
- workspace.figure3D.clear()
- if tokens is None:
- tokens = workspace.eqToks[-1]
- eqType = getTokensType(tokens)
- LHStok, RHStok = getLHSandRHS(tokens)
- variables = sorted(getVariables(LHStok, RHStok))
- dim = len(variables)
- graphVars, func, variables = graphPlot(workspace, False, tokens)
- renderPlot(workspace, graphVars, func, variables, tokens)
- if (dim == 1):
- var2, var3 = selectAdditionalVariable(variables[0])
- if tokens is None:
- workspace.eqToks[-1] += tokenizer("0" + var2 + "+" + "0" + var3)
- else:
- tokens += tokenizer("0" + var2 + "+" + "0" + var3)
- if (((dim == 2) or (dim == 1)) & (eqType == 'equation')):
- graphVars, func, variables = graphPlot(workspace, True, tokens)
- renderPlot(workspace, graphVars, func, variables, tokens)
- def selectAdditionalVariable(var1):
- if var1 == 'z':
- var2 = 'a'
- var3 = 'b'
- return var2, var3
- if var1 == 'Z':
- var2 = 'A'
- var3 = 'B'
- return var2, var3
- var2 = chr(ord(var1) + 1)
- var3 = chr(ord(var1) + 2)
- return var2, var3
- def refreshPlot(workspace):
- if workspace.resultOut is True and workspace.showPlotter is True:
- plot(workspace)
- ###############
- # preferences #
- ###############
- # TODO: Add status tips, Fix docstrings
- def plotPref(workspace):
- prefLayout = QSplitter(Qt.Horizontal)
- workspace.xLimitValue = QLabel(
- "X-axis range: (-" + str(workspace.axisRange[0]) + ", " + str(workspace.axisRange[0]) + ")")
- workspace.yLimitValue = QLabel(
- "Y-axis range: (-" + str(workspace.axisRange[1]) + ", " + str(workspace.axisRange[1]) + ")")
- workspace.zLimitValue = QLabel(
- "Z-axis range: (-" + str(workspace.axisRange[2]) + ", " + str(workspace.axisRange[2]) + ")")
- def customSlider():
- limitSlider = QSlider(Qt.Horizontal)
- limitSlider.setMinimum(-3)
- limitSlider.setMaximum(3)
- limitSlider.setValue(1)
- limitSlider.setTickPosition(QSlider.TicksBothSides)
- limitSlider.setTickInterval(1)
- limitSlider.valueChanged.connect(lambda: valueChange(workspace))
- limitSlider.setStatusTip("Change axes limit")
- return limitSlider
- workspace.xLimitSlider = customSlider()
- workspace.yLimitSlider = customSlider()
- workspace.zLimitSlider = customSlider()
- workspace.meshDensityValue = QLabel(
- "Mesh Layers: " + str(workspace.axisRange[3]))
- workspace.meshDensityValue.setStatusTip("Increment for a denser mesh in 3D plot")
- workspace.meshDensity = QSpinBox()
- workspace.meshDensity.setFixedSize(200, 30)
- workspace.meshDensity.setRange(10, 75)
- workspace.meshDensity.setValue(30)
- workspace.meshDensity.valueChanged.connect(lambda: valueChange(workspace))
- workspace.meshDensity.setStatusTip("Incrementing mesh density may affect performance")
- refreshPlotterText = QLabel("Apply plotter settings")
- refreshPlotter = QPushButton('Apply')
- refreshPlotter.setFixedSize(200, 30)
- refreshPlotter.clicked.connect(lambda: refreshPlot(workspace))
- refreshPlotter.setStatusTip("Apply modified settings to plotter.")
- axisPref = QSplitter(Qt.Vertical)
- axisPref.addWidget(workspace.xLimitValue)
- axisPref.addWidget(workspace.xLimitSlider)
- axisPref.addWidget(workspace.yLimitValue)
- axisPref.addWidget(workspace.yLimitSlider)
- axisPref.addWidget(workspace.zLimitValue)
- axisPref.addWidget(workspace.zLimitSlider)
- plotSetPref = QSplitter(Qt.Vertical)
- plotSetPref.addWidget(workspace.meshDensityValue)
- plotSetPref.addWidget(workspace.meshDensity)
- plotSetPref.addWidget(refreshPlotterText)
- plotSetPref.addWidget(refreshPlotter)
- prefLayout.addWidget(plotSetPref)
- prefLayout.addWidget(axisPref)
- prefLayout.setFixedWidth(400)
- return prefLayout
- def valueChange(workspace):
- xlimit = 10**workspace.xLimitSlider.value()
- ylimit = 10**workspace.yLimitSlider.value()
- zlimit = 10**workspace.zLimitSlider.value()
- meshLayers = workspace.meshDensity.value()
- workspace.axisRange = [xlimit, ylimit, zlimit, meshLayers]
- workspace.xLimitValue.setText(
- "X-axis range: (-" + str(workspace.axisRange[0]) + ", " + str(workspace.axisRange[0]) + ")")
- workspace.yLimitValue.setText(
- "Y-axis range: (-" + str(workspace.axisRange[1]) + ", " + str(workspace.axisRange[1]) + ")")
- workspace.zLimitValue.setText(
- "Z-axis range: (-" + str(workspace.axisRange[2]) + ", " + str(workspace.axisRange[2]) + ")")
- workspace.meshDensityValue.setText(
- "Mesh Layers: " + str(workspace.axisRange[3]))
|