There are a lot of good charting packages available for IoT and Rasperry Pi projects.
The PySimpleGUI python libary stands out in its ability to have the same code for both a local GUI and a Web interface. PySimpleGUI isn’t focused as a charting package but it has the canvas and graph elements that allow you to create real time bar charts and real time trend charts.
Getting Started with Graph Elements
For some background on PySimpleGUI see: PySimpleGUI – quick and easy interfaces
The graph element can have different co-ordinate orientations, for example the center can be (0,0) or the bottom left can be (0,0). A graph element is created with the syntax:
Graph(canvas_size, graph_bottom_left, graph_top_right …)
An example with 2 different co-ordinate orientations would be:
# A basic PySimpleGUI graph example
import PySimpleGUI as sg
bcols = ['blue','red','green']
myfont = "Ariel 18"
gtop = sg.Graph((200,200), (0,0),(200,200),background_color="white")
gcenter = sg.Graph((200,200), (-100,-100), (100,100),background_color="white")
layout = [[sg.Text('Graph: 0,0 at bottom left',font=myfont)],
[gtop],
[sg.Text('Graph: 0,0 at center',font=myfont)],
[gcenter],
[sg.Exit()]]
window = sg.Window('Graph Example', layout)
# Write text and lines
event, values = window.read(timeout=0)
gtop.draw_text(text="(0,0)", location=(0,0))
gcenter.draw_text(text="(0,0)", location=(0,0))
gtop.draw_text(text="(50,50)", location=(50,50))
gcenter.draw_text(text="(50,50)", location=(50,50))
gtop.draw_line((0,0),(50,50))
gcenter.draw_line((0,0),(50,50))
# Wait for a key to exit
window.read()
window.close()

Bar Charts
Bar charts can be created using the graph.draw_rectangle() method. Below is an example that takes a command line input to toggle between a tkinter local interface and a web interface. This example has 3 input points that are scanned every 2 seconds.
import sys
import random
# Pass any command line argument for Web use
if len(sys.argv) > 1: # if there is use the Web Interface
import PySimpleGUIWeb as sg
mode = "web"
mysize = (20,2)
else: # default uses the tkinter GUI
import PySimpleGUI as sg
mode = "tkinter"
mysize = (12,1)
BAR_WIDTH = 150
BAR_SPACING = 175
EDGE_OFFSET = 3
GRAPH_SIZE = (500,500)
DATA_SIZE = (500,500)
bcols = ['blue','red','green']
myfont = "Ariel 18"
#update with your ip
myip = '192.168.0.107'
graph = sg.Graph(GRAPH_SIZE, (0,0), DATA_SIZE)
layout = [[sg.Text('Pi Sensor Values',font=myfont)],
[graph],
[sg.Text('PI Tag 1',text_color=bcols[0],font=myfont,size= mysize ),
sg.Text('PI Tag 2',text_color=bcols[1],font=myfont,size= mysize ),
sg.Text('PI Tag 3',text_color=bcols[2],font=myfont,size= mysize)],
[sg.Exit()]]
if mode == "web":
window = sg.Window('Real Time Charts', layout,web_ip=myip, web_port = 8888, web_start_browser=False)
else:
window = sg.Window('Real Time Charts', layout)
while True:
event, values = window.read(timeout=2000)
if event in (None, 'Exit'):
break
graph.erase()
for i in range(3):
# Random value are used. Add interface to Pi sensors here:
graph_value = random.randint(0, 400)
graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color=bcols[i])
<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>graph.draw_text(text=str(graph_value), location=(i*BAR_SPACING+EDGE_OFFSET+15, graph_value+10),color=bcols[i],font=myfont)
window.close()
The presentation between the tkinter and Web interface is almost identical, but not 100% some tweeking on text sizing is required.
Real Time Trend Charts
It is important to note that the PySimpleGUIWeb is still in development so there may be some compatibility issues when trying to toggle between the tkinter and Web versions.
Below is an example that will create a realtime chart.

#!/usr/bin/env python
import array
from datetime import datetime
import sys
import random
# Pass any command line argument for Web use
if len(sys.argv) > 1: # if there is use the Web Interface
import PySimpleGUIWeb as sg
mode = "web"
mysize = (20,2)
else: # default uses the tkinter GUI
import PySimpleGUI as sg
mode = "tkinter"
mysize = (12,1)
from threading import Thread
STEP_SIZE = 1 # can adjust for more data saved than shown
SAMPLES = 100 # number of point shown on the chart
SAMPLE_MAX = 100 # high limit of data points
CANVAS_SIZE = (1000, 600)
LABEL_SIZE = (1000,20)
# create an array of time and data value
pt_values = []
pt_times = []
for i in range(SAMPLES+1):
pt_values.append("")
pt_times.append("")
def main():
timebar = sg.Graph(LABEL_SIZE, (0, 0),(SAMPLES, 20), background_color='white', key='times')
graph = sg.Graph(CANVAS_SIZE, (0, 0), (SAMPLES, SAMPLE_MAX),
background_color='black', key='graph')
layout = [
[sg.Quit(button_color=('white', 'red')),sg.Button(button_text="Print Log", button_color=('white', 'green'),key="log"),
sg.Text(' ', key='output')],
[graph],
[timebar],
]
if mode == 'web':
window = sg.Window('Pi Trend Chart', layout,
web_ip='192.168.0.107', web_port = 8888, web_start_browser=False)
else:
window = sg.Window('Pi Trend Chart', layout)
graph = window['graph']
output = window['output']
i = 0
prev_x, prev_y = 0, 0
while True: # the Event Loop
event, values = window.read(timeout=1000)
if event in ('Quit', None): # always give ths user a way out
break
if event in ('log'): # print the recorded time/data arrays
print("\nReal Time Data\n")
for j in range(SAMPLES+1):
if pt_times[j] == "": #only print updated info
break
print (pt_times[j], pt_values[j])
# Get data point and time
data_pt = random.randint(0, 100)
now = datetime.now()
now_time = now.strftime("%H:%M:%S")
# update the point arrays
pt_values[i] = data_pt
pt_times[i] = str(now_time)
window['output'].update(data_pt)
if data_pt > SAMPLE_MAX:
data_pt = SAMPLE_MAX
new_x, new_y = i, data_pt
if i >= SAMPLES:
# shift graph over if full of data
graph.move(-STEP_SIZE, 0)
prev_x = prev_x - STEP_SIZE
# shift the array data points
for i in range(SAMPLES):
pt_values[i] = pt_values[i+1]
pt_times[i] = pt_times[i+1]
graph.draw_line((prev_x, prev_y), (new_x, new_y), color='red')
prev_x, prev_y = new_x, new_y
i += STEP_SIZE if i < SAMPLES else 0
timebar.erase()
# add a scrolling time value
time_x = i
timebar.draw_text(text=str(now_time), location=((time_x - 2),7) )
# add some extra times
if i >= SAMPLES:
timebar.draw_text(text=pt_times[int(SAMPLES * 0.25)], location=((int(time_x * 0.25) - 2),7) )
timebar.draw_text(text=pt_times[int(SAMPLES * 0.5)], location=((int(time_x * 0.5) - 2),7) )
timebar.draw_text(text=pt_times[int(SAMPLES * 0.75)], location=((int(time_x *0.75) - 2),7) )
if i > 10:
timebar.draw_text(text=pt_times[1], location=( 2,7) )
window.close()
if __name__ == '__main__':
main()
Final Comment
If you are looking at doing some charting and you want to have both a local and a web interface then PySimpleGUI and PySimpleGUIWeb will be something that you should take a look at.