I'm having an issue exactly like this post, but slightly more frustrating.
I'm using matplotlib to read from a file that is being fed data from another application. For some reason, the ends of the data only connect after the animation (animation.FuncAnimation) has completed its first refresh. Here are some images:
This is before the refresh:
And this is after the refresh:
Any ideas as to why this could be happening? Here is my code:
import json
import itertools
import dateutil.parser
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
import scipy.signal as sig
import numpy as np
import pylab as plt
sensors = {}
data = []
lastLineReadNum = 0
class Sensor:
def __init__(self, name, points = 0, lastReading = 0):
self.points = points
self.lastReading = lastReading
self.name = name
self.x = []
self.y = []
class ScanResult:
def __init__(self, name, id, rssi, macs, time):
self.name = name
self.id = id
self.rssi = rssi
self.macs = macs
# Is not an integer, but a datetime.datetime
self.time = time
def readJSONFile(filepath):
with open(filepath, "r") as read_file:
global lastLineReadNum
# Load json results into an object that holds important info
for line in itertools.islice(read_file, lastLineReadNum, None):
temp = json.loads(line)
# Only reads the most recent points...
data.append(ScanResult(name = temp["dev_id"],
id = temp["hardware_serial"],
rssi = temp["payload_fields"]["rssis"],
macs = temp["payload_fields"]["macs"],
time = dateutil.parser.parse(temp["metadata"]["time"])))
lastLineReadNum += 1
return data
style.use('fivethirtyeight')
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
def smooth(y, box_pts):
box = np.ones(box_pts)/box_pts
y_smooth = np.convolve(y, box, mode='same')
return y_smooth
def determineClosestSensor():
global sensors
#sensors.append(Sensor(time = xs3, rssi = ys3))
def determineXAxisTime(scanresult):
return ((scanresult.time.hour * 3600) + (scanresult.time.minute * 60) + (scanresult.time.second)) / 1000.0
def animate(i):
data = readJSONFile(filepath = "C:/python_testing/rssi_logging_json.json")
for scan in data:
sensor = sensors.get(scan.name)
# First time seeing the sensor
if(sensor == None):
sensors[scan.name] = Sensor(scan.name)
sensor = sensors.get(scan.name)
sensor.name = scan.name
sensor.x.append(determineXAxisTime(scan))
sensor.y.append(scan.rssi)
else:
sensor.x.append(determineXAxisTime(scan))
sensor.y.append(scan.rssi)
ax1.clear()
#basic smoothing using nearby averages
#y_smooth3 = smooth(np.ndarray.flatten(np.asarray(sensors.get("sentrius_sensor_3").y)), 1)
for graphItem in sensors.itervalues():
smoothed = smooth(np.ndarray.flatten(np.asarray(graphItem.y)), 1)
ax1.plot(graphItem.x, smoothed, label = graphItem.name, linewidth = 2.0)
ax1.legend()
determineClosestSensor()
fig.suptitle("Live RSSI Graph from Sentrius Sensors", fontsize = 14)
def main():
ani = animation.FuncAnimation(fig, animate, interval = 15000)
plt.show()
if __name__ == "__main__":
main()
As far as I can tell you are regenerating your data in each animation step by appending to the existing datasets, but then this means that your last x point from the first step is followed by the first x point in the second step, leading to a rewind in the plot. This appears as the line connecting the last datapoint with the first one; the rest of the data is unchanged.
The relevant part of animate:
def animate(i):
data = readJSONFile(filepath = "C:/python_testing/rssi_logging_json.json")
for scan in data:
sensor = sensors.get(scan.name)
# First time seeing the sensor
if(sensor is None): # always check for None with `is`!
... # stuff here
else:
sensor.x.append(determineXAxisTime(scan)) # always append!
sensor.y.append(scan.rssi) # always append!
... # rest of the stuff here
So, in each animation step you
1. load the same JSON file
2. append the same data to an existing sensor identified by sensors.get(scan.name)
3. plot stuff without ever using i.
Firstly, your animate should naturally make use of the index i: you're trying to do something concerning step i. I can't see i being used anywhere.
Secondly, your animate should be as lightweigh as possible in order to get a smooth animation. Load your data once before plotting, and only handle the drawing differences in animate. This will involve slicing or manipulating your data as a function of i.
Of course if the file really does change from step to step, and this is the actual dynamics in the animation (i.e. i is a dummy variable that is never used), all you need to do is zero-initialize all the plotting data in each step. Start with a clean slate. Then you'll stop seeing the lines corresponding to these artificial jumps in the data. But again, if you want a lightweigh animate, you should look into manipulating the underlying data of existing plots rather than replotting everything all the time (especially since calls to ax1.plot will keep earlier points on the canvas, which is not what you usually want in an animation).
try changing :
ani = animation.FuncAnimation(fig, animate, interval = 15000)
to :
ani = animation.FuncAnimation(fig, animate, interval = 15000, repeat = False)
Related
I currently have a working python program that simultaneously animates one or more graphs each with an advancing window of real-time data. The program utilizes FuncAnimation and replots each graph using the axes plot routine. It is desired to show updates every second in the animation and the program is able to perform as expected when animating a few graphs. However, matplotlib cannot complete updates in a 1 second timeframe when attempting to animate several (>5) graphs.
Understanding that updating graphs in their entirety takes time, I am attempting to employ blitting to the animation process.
I have attempted to simplify and comment the code for an easier understanding. The data I work with is a binary stream from a file. Frames of data within the stream are identified and marked prior to running the code below. Within each frame of data resides electronic signal values that are to be plotted. Each electronic signal has one or more data points within a single binary data frame. The ability to see up to a dozen plotted signals at the same time is desired. The code follows and is commented.
I employ a python deque to mimic a data window of 10 seconds. For each call to the FuncAnimation routine, 1 second of data is placed into the deque and then the deque in processed to produce a xValues and yValues array of data points.
At the bottom of the code is the FuncAnimation routine that is called every 1 second (DisplayAnimatedData). Within that routine are 2 print statements that I have used to determine that the data within the xValues and yValues arrays from the deque are correct and that the plot set_xlim for each graph is correctly being changed in a way to advance the window of animated data.
The plotting is kind of working. However, the xaxis tick values are not updated after the initial set of tick values are applied correctly using calls to set_xlim. And, I expected the yaxis ylim to automatically scale to the data. But it does not. How do I get the xaxis tick values to advance as the data window advances? How to I get the yaxis tick values to display correctly?
Finally, you will notice that the code hides the xaxis of all graphs except the last one. I designed this thinking that although the set_xlim of each graph is called for each pass through the FuncAnimation, that no time is spent redrawing but one xaxis. I hope this will improve performance.
Your insight would be appreciated.
from matplotlib import animation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from collections import deque
from PyQt5 import QtCore, QtGui, QtWidgets
#PlotsUI is code created via Qt Designer
class PlotsUI(object):
def setupUi(self, PlotsUI):
PlotsUI.setObjectName("PlotsUI")
PlotsUI.setWindowModality(QtCore.Qt.NonModal)
PlotsUI.resize(1041, 799)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(PlotsUI.sizePolicy().hasHeightForWidth())
PlotsUI.setSizePolicy(sizePolicy)
self.gridLayout_2 = QtWidgets.QGridLayout(PlotsUI)
self.gridLayout_2.setObjectName("gridLayout_2")
self.plotLayout = QtWidgets.QVBoxLayout()
self.plotLayout.setObjectName("plotLayout")
self.gridLayout_2.addLayout(self.plotLayout, 0, 0, 1, 1)
self.retranslateUi(PlotsUI)
QtCore.QMetaObject.connectSlotsByName(PlotsUI)
def retranslateUi(self, PlotsUI):
_translate = QtCore.QCoreApplication.translate
PlotsUI.setWindowTitle(_translate("PlotsUI", "Plots"))
#DataSeriesMgr is given a collection of values for a user selected electronic signal
#found in the stream of binary data frames. One instance of this class is dedicated to
#manage the values of one electronic signal.
class DataSeriesMgr:
def __init__(self, frameMultiple, timeRange, dataSeries):
self._dataSeries = dataSeries
#frame multiple will typically be number of binary data frames required
#for 1 second of data (default 100 frames)
self._frameMultiple = frameMultiple
#create a data deque to support the windowing of animated data
#timeRange is the number of framesMultiples(seconds) of data stored in deque
self._dataDeque = deque(maxlen=timeRange)
self._timeRange = timeRange
#index into dataSeries
#keep track of what data has been processed
self._xValueIndex = 0 #byte number in buffer from binary file
self._dataSeriesSz = len(dataSeries)
#get the first available xvalue and yvalue arrays to help facilitate
#the calculation of x axis limits (by default 100 frames of data at a time)
self._nextXValues, self._nextYValues = self.XYDataSetsForAnimation()
if self._nextXValues is not None:
self._nextXLimits = (self._nextXValues[0], self._nextXValues[0] +
self._timeRange)
else:
self._nextXLimits = (None, None)
#property
def DataDeque(self):
return self._dataDeque
#property
def TimeRange(self):
return self._timeRange
#property
def NextXValues(self):
return self._nextXValues
def GetXYValueArrays(self):
allXValues = []
allYValues = []
#xyDataDeque is a collection of x values, y values tuples each 1 sec in duration
#convert what's in the deque to arrays of x and y values
xyDataArray = list(self._dataDeque)
for dataSet in xyDataArray:
for xval in dataSet[0]:
allXValues.append(xval)
for yval in dataSet[1]:
allYValues.append(yval)
#and set the data for the plot line
#print(f'{key}-NumOfX: {len(allXValues)}\n\r')
return allXValues,allYValues
def GatherFrameData(self, dataSubSet):
consolidatedXData = []
consolidatedYData = []
for frameData in dataSubSet: # each frame of data subset will have one or more data points
for dataPointTuple in frameData: # (unimportantValue, x, y) values
if dataPointTuple[0] is None: #no data in this frame
continue
consolidatedXData.append(dataPointTuple[1])
consolidatedYData.append(dataPointTuple[2])
return consolidatedXData,consolidatedYData
def XYDataSetsForAnimation(self):
index = self._xValueIndex #the current location in the data array for animation
nextIndex = index + self._frameMultiple
if nextIndex > self._dataSeriesSz: #we are beyond the number of frames
#there are no more data points to plot for this specific signal
return None, None
dataSubset = self._dataSeries[index:nextIndex]
self._xValueIndex = nextIndex #prepare index for next subset of data to be animated
#gather data points from data subset
xyDataSet = self.GatherFrameData(dataSubset)
#add it to the deque
# the deque holds a window of a number of seconds of data
self._dataDeque.append(xyDataSet)
#convert the deque to arrays of x and y values
xValues, yValues = self.GetXYValueArrays()
return xValues, yValues
def NextXYDataSets(self):
xValues = self._nextXValues
yValues = self._nextYValues
xlimits = self._nextXLimits
self._nextXValues, self._nextYValues = self.XYDataSetsForAnimation()
if self._nextXValues is not None:
self._nextXLimits = (self._nextXValues[0], self._nextXValues[0] +
self._timeRange)
else:
self._nextXLimits = (None, None)
return xValues, yValues, xlimits
class Graph:
def __init__(self, title, dataSeriesMgr):
self._title = title
self._ax = None
self._line2d = None
self._xlimits = None
self._dataSeriesMgr = dataSeriesMgr
#property
def DataSeriesMgr(self):
return self._dataSeriesMgr
#DataSeriesMgr.setter
def DataSeriesMgr(self, val):
self._dataSeriesMgr = val
#property
def AX(self):
return self._ax
#AX.setter
def AX(self, ax):
self._ax = ax
line2d, = self._ax.plot([], [], animated=True)
self._line2d = line2d
self._ax.set_title(self._title, fontweight='bold', size=10)
#property
def Line2D(self):
return self._line2d
#Line2D.setter
def Line2D(self,val):
self._line2d = val
#property
def Title(self):
return self._title
#property
def ShowXAxis(self):
return self._showXAxis
#ShowXAxis.setter
def ShowXAxis(self, val):
self._showXAxis = val
self._ax.xaxis.set_visible(val)
#property
def XLimits(self):
return self._xlimits
#XLimits.setter
def XLimits(self, tup):
self._xlimits = tup
self._ax.set_xlim(tup[0], tup[1])
class Plotter(QtWidgets.QDialog):
def __init__(self, parentWindow):
super(Plotter, self).__init__()
self._parentWindow = parentWindow
#Matplotlib Figure
self._figure = Figure()
self._frameMultiple = 100 #there are 100 frames of data per second
self._xaxisRange = 10 #make the graphs have a 10 second xaxis range
self._animationInterval = 1000 #one second
#PyQt5 UI
#add the canvas to the UI
self.ui = PlotsUI()
self.ui.setupUi(self)
self._canvas = FigureCanvas(self._figure)
self.ui.plotLayout.addWidget(self._canvas)
self.show()
def PlaceGraph(self,aGraph,rows,cols,pos):
ax = self._figure.add_subplot(rows,cols,pos)
aGraph.AX = ax
def Plot(self, dataSeriesDict):
self._dataSeriesDict = {}
self._graphs = {}
#for this example, simplify the structure of the data to be plotted
for binaryFileAlias, dataType, dataCode, dataSpec, dataTupleArray in dataSeriesDict.YieldAliasTypeCodeAndData():
self._dataSeriesDict[dataCode] = DataSeriesMgr(self._frameMultiple, self._xaxisRange, dataTupleArray)
self._numberOfGraphs = len(self._dataSeriesDict.keys())
#prepare for blitting
pos = 1
self._lines = []
lastKey = None
for k,v in self._dataSeriesDict.items():
#create a graph for each series of data
aGraph = Graph(k,v)
self._graphs[k] = aGraph
#the last graph will show animated x axis
lastKey = k
#and place it in the layout
self.PlaceGraph(aGraph, self._numberOfGraphs, 1, pos)
aGraph.ShowXAxis = False
#collect lines from graphs
self._lines.append(aGraph.Line2D)
pos += 1
#show the x axis of the last graph
lastGraph = self._graphs[lastKey]
lastGraph.ShowXAxis = True
#Animate
self._animation = animation.FuncAnimation(self._figure, self.DisplayAnimatedData,
None, interval=self._animationInterval, blit=True)
def DisplayAnimatedData(self,i):
indx = 0
haveData = False
for key, graph in self._graphs.items():
allXValues, allYValues, xlimits = graph.DataSeriesMgr.NextXYDataSets()
if allXValues is None: #no more data
continue
# print(f'{key}-NumOfX:{len(allXValues)}')
# print(f'{key}-XLimits: {xlimits[0]}, {xlimits[1]}')
self._lines[indx].set_data(allXValues, allYValues)
#call set_xlim on the graph.
graph.XLimits = xlimits
haveData = True
indx += 1
if not haveData: #no data ??
self._animation.event_source.stop()
return self._lines
By updating the xaxis limits prior to updating the lines in the FuncAnimation method DisplayAnimatedData, the advancing of a window of data seems to work as expected. However, when attempting to animate 10 individual graphs in one second increments, the advance of the xaxis and the update of the plots takes nearly twice as long (about 2 seconds) even when implementing blitting where only one x axis is redrawn and the y axis is only drawn once.
In Short:
I want to change the color of blue marker in the graph. So that I can do comparison with other plots easily.
You can download the data files and script from this link
Problem Explanation
I have two data files, full.dat and part.dat(Note: part.dat is also there in full.dat).
I got the plotting scripts from the internet, and it is working very well. But as a noob in Python and Matplotlib, I am facing difficulties in changing the color of part.dat.
Please see the graph first, then the following scripts.
Script-1: Function and definitions: let's say: "func.py"
# This was written by Levi Lentz for the Kolpak Group at MIT
import numpy as np
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import matplotlib.gridspec as gs
import sys
#This function extracts the high symmetry points from the output of bandx.out
def Symmetries(fstring):
f = open(fstring,'r')
x = np.zeros(0)
for i in f:
if "high-symmetry" in i:
x = np.append(x,float(i.split()[-1]))
f.close()
return x
# This function takes in the datafile, the fermi energy, the symmetry file, a subplot, and the label
# It then extracts the band data, and plots the bands, the fermi energy in red, and the high symmetry points
def bndplot(datafile_full,datafile,fermi,symmetryfile,subplot,**kwargs):
if 'shift_fermi' in kwargs:
bool_shift_efermi = kwargs['shift_fermi']
else:
bool_shift_efermi = 0
if 'color' in kwargs:
color_bnd=kwargs['color']
else:
color_bnd='black'
if 'linestyle' in kwargs:
line_bnd=kwargs['linestyle']
else:
line_bnd='solid'
z = np.loadtxt(datafile_full) #This loads the full.dat file
x = np.unique(z[:,0]) #This is all the unique x-points
[a,b,w]=np.loadtxt(datafile,unpack=True) #Weight
bands = []
bndl = len(z[z[:,0]==x[1]]) #This gives the number of bands in the calculation
Fermi = float(fermi)
if bool_shift_efermi:
fermi_shift=Fermi
else:
fermi_shift=0
axis = [min(x),max(x)]
for i in range(0,bndl):
bands.append(np.zeros([len(x),2])) #This is where we storre the bands
for i in range(0,len(x)):
sel = z[z[:,0] == x[i]] #Here is the energies for a given x
test = []
for j in range(0,bndl): #This separates it out into a single band
bands[j][i][0] = x[i]
#bands[j][i][1] = np.multiply(sel[j][1],13.605698066)
bands[j][i][1] = sel[j][1]
#Here we plots the bands
for i in bands:
subplot.plot(i[:,0],i[:,1]-fermi_shift,color=color_bnd,linestyle=line_bnd, linewidth=0.7,alpha=0.5)
# plt.scatter(a,b-fermi_shift,c=w,cmap='viridis',alpha=0.5)
# plt.colorbar()
if 'legend' in kwargs:
#empty plot to generate legend
subplot.plot([None],[None],color=color_bnd,linestyle=line_bnd,label=kwargs['legend'])
temp = Symmetries(symmetryfile)
for j in temp: #This is the high symmetry lines
x1 = [j,j]
subplot.axvline(x=j,linestyle='dashed',color='black',alpha=0.75)
subplot.plot([min(x),max(x)],[Fermi-fermi_shift,Fermi-fermi_shift],color='red',linestyle='dotted')
subplot.set_xticks(temp)
subplot.set_xticklabels([])
if 'name_k_points' in kwargs:
if len(kwargs['name_k_points'])==len(temp):
subplot.set_xticklabels(kwargs['name_k_points'])
if 'range' in kwargs:
range_plot=kwargs['range']
subplot.set_ylim([range_plot[0],range_plot[1]])
subplot.set_xlim([axis[0],axis[1]])
subplot.set_xlabel('k')
subplot.set_ylabel('E-E$_f$')
plt.scatter(a,b-fermi_shift,s=70*np.array(w))
if 'legend' in kwargs:
plt.legend()
script-2 Plotting script: let's say: "plot.py"
#!/usr/bin/python3
from func import *
El='el'
orb='orb'
plt.rcParams["figure.figsize"]=(4,15)
datafile_full='bands.dat.gnu'
#datafile=El+'_'+orb+'.dat.all'
datafile=El+'_'+orb+'.dat.all'
fermi = 10.2382
symmetryfile='band.out'
bool_shift_efermi= True
fig, ax = plt.subplots()
#bndplot(datafile,fermi,symmetryfile,ax)
bndplot(datafile_full,datafile,fermi,symmetryfile,ax,shift_fermi=1,color='black',linestyle='solid',name_k_points=['K','G','M','K','H','A','L','H'], legend=El+', '+orb+'-orbital')
#ax.set_ylim(-5,5)
ax.set_ylim(-10,12)
fig.set_figheight(6)
fig.set_figwidth(4)
plt.rcParams.update({'font.size': 22})
fig.savefig("el-orb.eps")
plt.show()
In script-2, there is an option to change the color, however I want to change the color of blue marker/solid-circles(please see the graph) so that I can compare with other graphs.
Whenever I change the color, it changes the line color only.
Please help me out I am trying to understand Matplotlib uses and examples from past few hrs However as a noob I was not able to figure out how to do.
I am working on a Python application where I am collecting data from a device, and attempting to plot it in an excel file by using the Openpyxl library. I am successfully able to do everything including plotting the data, and formatting the scatter plot that I made, but I am having some trouble in adding minor gridlines to the plot.
I feel like this is definitely possible because in the API, I can see under the openpyxl.chart.axis module, there is a “minorGridlines” attribute, but it is not a boolean input (ON/OFF), rather it takes a Chartlines class. I tried going a bit down the rabbit-hole of seeing how I would do this, but I am wondering what the most straightforward way of adding the minor-gridlines would be? Do you have to construct chart lines manually, or is there a simple way of doing this?
I would really appreciate any help!
I think I answered my own question, but I will post it here if anybody else needs this (as I don’t see any other answers to this question on the forum).
Example Code (see lines 4, 38):
# Imports for script
from openpyxl import Workbook # For plotting things in excel
from openpyxl.chart import ScatterChart, Reference, Series
from openpyxl.chart.axis import ChartLines
from math import log10
# Variables for script
fileName = 'testFile.xlsx'
dataPoints = 100
# Generating a workbook to test with
wb = Workbook()
ws = wb.active # Fill data into the first sheet
ws_name = ws.title
# We will just generate a logarithmic plot, and scale the axis logarithmically (will look linear)
x_data = []
y_data = []
for i in range(dataPoints):
x_data.append(i + 1)
y_data.append(log10(i + 1))
# Go back through the data, and place the data into the sheet
ws['A1'] = 'x_data'
ws['B1'] = 'y_data'
for i in range(dataPoints):
ws['A%d' % (i + 2)] = x_data[i]
ws['B%d' % (i + 2)] = y_data[i]
# Generate a reference to the cells that we can plot
x_axis = Reference(ws, range_string='%s!A2:A%d' % (ws_name, dataPoints + 1))
y_axis = Reference(ws, range_string='%s!B2:B%d' % (ws_name, dataPoints + 1))
function = Series(xvalues=x_axis, values=y_axis)
# Actually create the scatter plot, and append all of the plots to it
ScatterPlot = ScatterChart()
ScatterPlot.x_axis.minorGridlines = ChartLines()
ScatterPlot.x_axis.scaling.logBase = 10
ScatterPlot.series.append(function)
ScatterPlot.x_axis.title = 'X_Data'
ScatterPlot.y_axis.title = 'Y_Data'
ScatterPlot.title = 'Openpyxl Plotting Test'
ws.add_chart(ScatterPlot, 'D2')
# Save the file at the end to output it
wb.save(fileName)
Background on solution:
I looked at how the code for Openpyxl generates the Major axis gridlines, which seems to follow a similar convention as the Minor axis gridlines, and I found that in the ‘NumericAxis’ class, they generated the major gridlines with the following line (labeled ‘##### This Line #####’ which is originally copied from the ‘openpyxl->chart->axis’ file):
class NumericAxis(_BaseAxis):
tagname = "valAx"
axId = _BaseAxis.axId
scaling = _BaseAxis.scaling
delete = _BaseAxis.delete
axPos = _BaseAxis.axPos
majorGridlines = _BaseAxis.majorGridlines
minorGridlines = _BaseAxis.minorGridlines
title = _BaseAxis.title
numFmt = _BaseAxis.numFmt
majorTickMark = _BaseAxis.majorTickMark
minorTickMark = _BaseAxis.minorTickMark
tickLblPos = _BaseAxis.tickLblPos
spPr = _BaseAxis.spPr
txPr = _BaseAxis.txPr
crossAx = _BaseAxis.crossAx
crosses = _BaseAxis.crosses
crossesAt = _BaseAxis.crossesAt
crossBetween = NestedNoneSet(values=(['between', 'midCat']))
majorUnit = NestedFloat(allow_none=True)
minorUnit = NestedFloat(allow_none=True)
dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
'minorUnit', 'dispUnits',)
def __init__(self,
crossBetween=None,
majorUnit=None,
minorUnit=None,
dispUnits=None,
extLst=None,
**kw
):
self.crossBetween = crossBetween
self.majorUnit = majorUnit
self.minorUnit = minorUnit
self.dispUnits = dispUnits
kw.setdefault('majorGridlines', ChartLines()) ######## THIS Line #######
kw.setdefault('axId', 100)
kw.setdefault('crossAx', 10)
super(NumericAxis, self).__init__(**kw)
#classmethod
def from_tree(cls, node):
"""
Special case value axes with no gridlines
"""
self = super(NumericAxis, cls).from_tree(node)
gridlines = node.find("{%s}majorGridlines" % CHART_NS)
if gridlines is None:
self.majorGridlines = None
return self
I took a stab, and after importing the ‘Chartlines’ class like so:
from openpyxl.chart.axis import ChartLines
I was able to add minor gridlines to the x-axis like so:
ScatterPlot.x_axis.minorGridlines = ChartLines()
As far as formatting the minor gridlines, I’m at a bit of a loss, and personally have no need, but this at least is a good start.
I am currently writing a program where I can project a hologram video on my computer screen, I had written the code below and I do not know how to specifically rotate a subplot, I had created a 3*3 subplot and I need to rotate subplot 4 by 270 clockwise, subplot 6 by 90 clockwise and subplot 8 by 180.
Second question is how to get rid of all of the axis label... So that the hologram projected will be nice and neatly....
import pandas as pd
import serial
import numpy as np
import matplotlib.pyplot as plt
ser = serial.Serial("COM5", 115200) # define the serial port that we are communicating to and also the baud rate
plt.style.use('dark_background') #define the black background
plt.ion() # tell pyplot we need live data
fig,[[ax1,ax2,ax3],[ax4,ax5,ax6],[ax7,ax8,ax9]] = plt.subplots(3,3) # plotting a figure with 9 subplot
Xplot = []
Yplot = []
Zplot = []
blankx = []
blanky = []
fig = [ax1,ax2,ax3,ax4,ax5,ax6,ax7,ax8,ax9]
while True: #always looping this sequence
while(ser.inWaiting()==0): #if no input from the serial, wait and do nothing
pass
data = ser.readline() #obtain the input from COM 5
data_processed = data.decode('utf-8') #to get rid of the unnecessary string part
data_split = data_processed.split(",") # split the incoming string into a list
x = float(data_split[0]) #to obtain seperate float values for x,y,z
y = float(data_split[1])
z = float(data_split[2])
reset = int(data_split[3]) # reset will output 1
draw = int(data_split[4]) # draw will output 2
if(draw == 2):
Xplot.append(x) #if draw is given instruction, add the x,y,z value into the list to be plot on the graph
Yplot.append(y)
Zplot.append(z)
ax1.plot(blankx,blanky) # subplotting
ax2.plot(Xplot,Yplot,"ro")
ax3.plot(blankx,blank)
ax4.plot(Xplot,Yplot,"ro")
ax5.plot(blankx,blank)
ax6.plot(Xplot,Yplot,"ro")
ax7.plot(blankx,blanky)
ax8.plot(Xplot,Yplot,"ro")
ax9.plot(blankx,blanky)
if(reset == 1):
for f in fig: #if reset is given instruction, clear all figure and clear the elements in the plotting list
f.clear()
Xplot = []
Yplot = []
Zplot = []
plt.pause(.000001)
I might have found a solution, but not a perfect one, I use math instead of code to rotate the plotting, just multiple it by negative value to flip at x and y axis, I have also added a denoiser function to lower the deviation, here is the code that I use, if anyone had any idea about how to rotate a subplot freely, please enlight me.
import pandas as pd
import serial
import matplotlib.pyplot as plt
ser = serial.Serial("COM5", 115200) # define the serial port that we are communicating to and also the baud rate
plt.style.use('dark_background') #define the black background
plt.ion() # tell pyplot we need live data
fig,[[ax1,ax2,ax3],[ax4,ax5,ax6],[ax7,ax8,ax9]] = plt.subplots(3,3) # plotting a figure with 9 subplot
rx = [0]
ry = [0]
rz = [0]
Xplot2 = []
Xplot4 = []
Xplot6 = []
Xplot8 = []
Zplot2 = []
Zplot4 = []
Zplot6 = []
Zplot8 = []
blankx = []
blankz = []
fig = [ax1,ax2,ax3,ax4,ax5,ax6,ax7,ax8,ax9]
def switch(x):
return x*-1
def denoiser(x):
return (x[-1] +x[-2])/4
while True: #always looping this sequence
while(ser.inWaiting()==0): #if no input from the serial, wait and do nothing
pass
data = ser.readline() #obtain the input from COM 5
data_processed = data.decode('utf-8') #to get rid of the unnecessary string part
data_split = data_processed.split(",") # split the incoming string into a list
rx.append(float(data_split[0])) #to obtain seperate float values for x,y,z
ry.append(float(data_split[1]))
rz.append(float(data_split[2]))
reset = int(data_split[3]) # reset will output 1
draw = int(data_split[4]) # draw will output 2
x = denoiser(rx)
y = denoiser(ry)
z = denoiser(rz)
if(draw == 2):
Xplot8.append(x) #if draw is given instruction, add the x,y,z value into the list to be plot on the graph
Zplot8.append(z)
Xplot2.append(switch(x))
Zplot2.append(switch(z))
Xplot4.append(x)
Zplot4.append(switch(z))
Xplot6.append(switch(x))
Zplot6.append(z)
ax1.plot(blankx,blankz) # subplotting
ax1.axis("off")
ax2.plot(Xplot2,Zplot2,"ro")
ax2.axis("off")
ax3.plot(blankx,blankz)
ax3.axis("off")
ax4.plot(Xplot4,Zplot4,"ro")
ax4.axis("off")
ax5.plot(blankx,blankz)
ax5.axis("off")
ax6.plot(Xplot6,Zplot6,"ro")
ax6.axis("off")
ax7.plot(blankx,blankz)
ax7.axis("off")
ax8.plot(Xplot8,Zplot8,"ro")
ax8.axis("off")
ax9.plot(blankx,blankz)
ax9.axis("off")
if(reset == 1):
for f in fig: #if reset is given instruction, clear all figure and clear the elements in the plotting list
f.clear()
Xplot2 = []
Xplot4 = []
Xplot6 = []
Xplot8 = []
Zplot2 = []
Zplot4 = []
Zplot6 = []
Zplot8 = []
plt.pause(.000001)
I have this "flask app" with two links, each mapping to different matplotlib visualizations, for example: localhost:5000/line_chart and localhost:5000/bar_chart.
When I start the server, and click the a route (any of them), I see what I expect.
localhost:5000/bar_chart
When I go back and view the other link, both graphs break.
localhost:5000/line_chart
localhost:5000/bar_chart
I can reproduce this every time by closing the server then running the "run.py" script again. Seems to be an overwriting conflict with the in-memory buffer. Has anyone had this issue before?
app/views.py
import matplotlib
matplotlib.use('Agg') # this allows PNG plotting
import matplotlib.pyplot as plt
import base64
from flask import render_template
from app import app
from io import BytesIO
#app.route('/')
#app.route('/index')
def index():
res = ''
navigation = [['Line Chart','line_chart'],['Bar Chart','bar_chart']]
res = res + '<h1>Matplotlib Chart Examples</h1>'
res = res + '<ul>'
for item in navigation:
name = item[0]
link = item[1]
res = res + '<li>'+ name +'</li>'
res = res +'</ul>'
return res
#app.route('/bar_chart')
def bar_chart():
movies = ["Annie Hall", "Ben-Hur", "Casablanca", "Gandhi", "West Side Story"]
num_oscars = [5, 11, 3, 8, 10]
# bars are by default width 0.8, so we'll add 0.1 to the left coordinates
# so that each bar is centered
xs = [i + 0.1 for i, _ in enumerate(movies)]
# plot bars with left x-coordinates [xs], heights [num_oscars]
plt.bar(xs, num_oscars)
plt.ylabel("# of Academy Awards")
plt.title("My Favorite Movies")
# label x-axis with movie names at bar centers
plt.xticks([i + 0.5 for i, _ in enumerate(movies)], movies)
return compute(plt)
#app.route('/line_chart')
def line_chart():
years = [1950, 1960, 1970, 1980, 1990, 2000, 2010]
gdp = [300.2, 543.3, 1075.9, 2862.5, 5979.6, 10289.7, 14958.3]
# create a line chart, years on x-axis, gdp on y-axis
plt.plot(years, gdp, color='green', marker='o', linestyle='solid')
# add a title
plt.title("Nominal GDP")
# add a label to the y-axis
plt.ylabel("Billions of $")
return compute(plt)
def compute(plt):
# run plt.plot, plt.title, etc.
figfile = BytesIO()
plt.savefig(figfile, format='png')
figfile.seek(0) # rewind to beginning of file
#figfile.getvalue() extracts string (stream of bytes)
figdata_png = base64.b64encode(figfile.getvalue())
return render_template('index.html',
title='matplotlib chart',
results=figdata_png)
Thank you for your time.
I guess you need two figures, test this code and tell what happened:
#app.route('/bar_chart')
def bar_chart():
movies = ["Annie Hall", "Ben-Hur", "Casablanca", "Gandhi", "West Side Story"]
num_oscars = [5, 11, 3, 8, 10]
# bars are by default width 0.8, so we'll add 0.1 to the left coordinates
# so that each bar is centered
xs = [i + 0.1 for i, _ in enumerate(movies)]
# plot bars with left x-coordinates [xs], heights [num_oscars]
plt.figure(1)
plt.bar(xs, num_oscars)
plt.ylabel("# of Academy Awards")
plt.title("My Favorite Movies")
# label x-axis with movie names at bar centers
plt.xticks([i + 0.5 for i, _ in enumerate(movies)], movies)
return compute(plt, 1)
#app.route('/line_chart')
def line_chart():
years = [1950, 1960, 1970, 1980, 1990, 2000, 2010]
gdp = [300.2, 543.3, 1075.9, 2862.5, 5979.6, 10289.7, 14958.3]
# create a line chart, years on x-axis, gdp on y-axis
plt.figure(2)
plt.plot(years, gdp, color='green', marker='o', linestyle='solid')
# add a title
plt.title("Nominal GDP")
# add a label to the y-axis
plt.ylabel("Billions of $")
return compute(plt,2)
def compute(plt, fignum):
# run plt.plot, plt.title, etc.
plt.figure(fignum)
figfile = BytesIO()
plt.savefig(figfile, format='png')
figfile.seek(0) # rewind to beginning of file
#figfile.getvalue() extracts string (stream of bytes)
figdata_png = base64.b64encode(figfile.getvalue())
return render_template('index.html',
title='matplotlib chart',
results=figdata_png)
In my case, that solution didn't work. It seems that there is a race condition when trying to access plot. I first tried to use a lock from a library, but that didn't work, so instead I sort of engineered out a lock. In my case, I wanted to create n images using the same function on the same view, so I started by creating a list in the following way:
queue = [False for i in range(n)]
Then, my flask app look something like this:
#app.route('/vis/<j>')
def vis(j):
global queue
# We check that it's image's #j turn, as if it was single threaded
j = int(j)
if j == 0:
for i in range(len(queue)):
queue[i] = False
else:
while not queue[j-1]:
# If it's not, we sleep for a short time (from time import sleep)
sleep(0.5)
# This is not important, it's how I was plotting some random figures
# (from random import seed) (from datetime import datetime)
seed(datetime.now())
n = 10
p1 = [randint(0, 10) for _ in range(n)]
p2 = [randint(0, 10) for _ in range(n)]
t = [i for i in range(n)]
fig = plt.figure(j)
plt.clf()
plt.plot(t, p1, color='blue')
plt.plot(t, p2, color='orange')
plt.xlabel('Time')
plt.ylabel('Value')
# Save the plot
img = BytesIO()
fig.savefig(img, dpi=128)
img.seek(0)
# We finished using everything related to plot, so we free the "lock"
queue[j] = True
# Return the object as a file that can be accessed
return send_file(img, mimetype='image/png')
Finally, when wanting to display this in my flask app, all I had to do was using this <img src="/vis/1"> in my html file.
Edit: I forgot one of the most important part! For some reason, this would still create some unrelated thread issue. I looked it up and that's when I came with the full solution. The threading issue was solved by adding at the beginning of the file:
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Agg')
For some reason, using that Agg backend solved the second threading I was having. I don't really have a good explanation for that, but it does work, so it's enough for me.
Alternatively, what also worked was running the app disabling threads by adding:
if __name__ == '__main__':
app.run(threading=False, debug=True)
I don't know however, at the moment, whether this works in production, so I preferred the other solution. :)
I hope this helps if you had the same issue!