[Question] Is there a way to blur the rectangle that contains a Matplotlib plot?
Minoslo opened this issue · comments
Type of Issue (Enhancement, Error, Bug, Question)
Question
Operating System
Windows 11
PySimpleGUI Port (tkinter, Qt, Wx, Web)
tkinter
Versions
Version information can be obtained by calling sg.main_get_debug_data()
Or you can print each version shown in ()
Python version (sg.sys.version
)
3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]
PySimpleGUI Version (sg.__version__
)
4.60.5
GUI Version (tkinter (sg.tclversion_detailed
), PySide2, WxPython, Remi)
8.6.13
Your Experience In Months or Years (optional)
Have used another Python GUI Framework? (tkinter, Qt, etc) (yes/no is fine)
No
Anything else you think would be helpful?
I'm pretty much a Python begginer
Troubleshooting
These items may solve your problem. Please check those you've done by changing - [ ] to - [X]
- Searched main docs for your problem PySimpleGUI Documenation
- Looked for Demo Programs that are similar to your goal. It is recommend you use the Demo Browser! Demo Programs
- None of your GUI code was generated by an AI algorithm like GPT
- If not tkinter - looked for Demo Programs for specific port
- For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
- Run your program outside of your debugger (from a command line)
- Searched through Issues (open and closed) to see if already reported Issues.PySimpleGUI.com
- Have upgraded to the latest release of PySimpleGUI on PyPI (lastest official version)
- Tried running the Development Build. Your problem may have already been fixed but not released. Check Home Window for release notes and upgrading capability
- For licensing questions please email license@PySimpleGUI.com
Detailed Description
The graphic rectangle containing the chart is too noticeable. I'd like it to be more integrated into the design, for example, blurring the edges or something like that.
I'm not sure if there's a solution and I'm not sure if the solution is about PySimpleGUI or Matplotlib, so I'm sorry in advance if this is not the correct place to ask my question.
I hope someone can help me to keep learning about PySimpleGUI.
Thanks.
Edited: Include a short video rather than an image
Code To Duplicate
A short program that isolates and demonstrates the problem (Do not paste your massive program, but instead 10-20 lines that clearly show the problem)
This pre-formatted code block is all set for you to paste in your bit of code:
# Paste your code here
# ----- IMPORT OF PACKAGES -----
import PySimpleGUI as sg
import threading
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
matplotlib.use('TkAgg')
# ----- DEFINITION OF CLIENTS, DATATIME ELEMENTS, THREAD EVENTS AND CONSOLE COMMANDS -----
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_figure_agg(figure_agg):
figure_agg.get_tk_widget().forget()
plt.close('all')
# ----- DEFINITION OF FUNCTIONS TO BE EXECUTED AS THREADS -----
graphics1_done = threading.Event()
graphics1_done.set()
def generate_data1():
global xSolar, ySolar
while True:
graphics1_done.wait()
new_y_value = np.random.normal(0.01, 1)
ySolar = abs(np.append(ySolar, ySolar[-1] + new_y_value))
new_x = xSolar[-1] + (xSolar[1] - xSolar[0])
xSolar = np.append(xSolar, new_x)
if len(xSolar) > 200:
xSolar = xSolar[-200:]
ySolar = ySolar[-200:]
# Update plot
axSolar.cla()
axSolar.axis('off')
axSolar.plot(xSolar, ySolar, color='black', linewidth=0.5)
# Draw y_mean line
axSolar.plot([xSolar[0], xSolar[-1]], [ySolar.mean(), ySolar.mean()], linestyle='--', color='white',
linewidth=1)
# Add text
text_offset = 0.05 * (axSolar.get_ylim()[1] - axSolar.get_ylim()[0])
axSolar.text(xSolar[-1], ySolar.mean() - text_offset, f'mean: {ySolar.mean():.2f}', color='white',
fontsize=10, verticalalignment='top', horizontalalignment='right')
# Gradients
grad1 = axSolar.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap='Blues', vmin=-0.5, aspect='auto',
extent=[xSolar.min(), xSolar.max(), ySolar.mean(), ySolar.max()], origin='lower')
poly_pos = axSolar.fill_between(xSolar, ySolar.min(), ySolar, alpha=0.1)
grad1.set_clip_path(poly_pos.get_paths()[0], transform=axSolar.transData)
poly_pos.remove()
grad2 = axSolar.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap='Oranges', vmin=-0.5, aspect='auto',
extent=[xSolar.min(), xSolar.max(), ySolar.min(), ySolar.mean()], origin='upper')
poly_neg = axSolar.fill_between(xSolar, ySolar.min(), ySolar, alpha=0.1)
grad2.set_clip_path(poly_neg.get_paths()[0], transform=axSolar.transData)
poly_neg.remove()
axSolar.set_ylim(ySolar.min(), ySolar.max())
# Ajusta el tiempo de espera según sea necesario
graphics1_done.clear()
# ----- GUI SET UP -----
theme_dict = {'BACKGROUND': '#2B475D',
'TEXT': '#FFFFFF',
'INPUT': '#F2EFE8',
'TEXT_INPUT': '#000000',
'SCROLL': '#F2EFE8',
'BUTTON': ('#000000', '#C2D4D8'),
'PROGRESS': ('#FFFFFF', '#C7D5E0'),
'BORDER': 1, 'SLIDER_DEPTH': 0, 'PROGRESS_DEPTH': 10}
sg.LOOK_AND_FEEL_TABLE['Dashboard'] = theme_dict
sg.theme('Dashboard')
BORDER_COLOR = '#C7D5E0'
DARK_HEADER_COLOR = '#163C6B'
LAYOUT_COLOR = '#C88200'
layout0o0 = [[sg.HSep()],
[sg.VPush(background_color='black')],
[sg.Push(background_color='black'),
sg.Column([[sg.Graph((328, 164), (-328, -164), (-328, -164),
background_color='black', pad=(0, 0),
key='-SolarGraph-')],
[sg.Graph((328, 164), (-328, -164), (-328, -164),
background_color='black', pad=(0, 0),
key='-LoadGraph-')]],
background_color='black'),
sg.Push(background_color='black')],
[sg.VPush(background_color='black')],
[sg.HSep()]]
layout = [[sg.Column(layout0o0, expand_x=True, expand_y=True, pad=(0, 0), background_color='black',
element_justification='c', key='-LAYOUT0o0-')]]
window = sg.Window("INTERFAZ 0.1", layout, margins=(0, 0), size=(1024, 600), background_color=BORDER_COLOR,
no_titlebar=False, grab_anywhere_using_control=True, location=(0, 0), finalize=True)
# Graphs from main window
np.random.seed(123)
xSolar = np.linspace(0, 10, 200)
ySolar = abs(np.random.normal(0.01, 1, 200).cumsum())
figSolar = plt.figure(num=1, figsize=(3.4166666667, 1.7083333333), facecolor='black', dpi=96, clear=True)
axSolar = figSolar.add_axes([0, 0, 1, 1]) # La gráfica ocupa la figura completa
axSolar.axis('off') # Elimina los ejes
axSolar.plot(xSolar, ySolar, color='black', linewidth=0.5)
ylimSolar = axSolar.get_ylim()
grad1Solar = axSolar.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap='Blues', vmin=-0.5, aspect='auto',
extent=[xSolar.min(), xSolar.max(), 0, ySolar.max()], origin='lower')
poly_posSolar = axSolar.fill_between(xSolar, ySolar.min(), ySolar, alpha=0.1)
grad1Solar.set_clip_path(poly_posSolar.get_paths()[0], transform=axSolar.transData)
poly_posSolar.remove()
grad2 = axSolar.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap='Oranges', vmin=-0.5, aspect='auto',
extent=[xSolar.min(), xSolar.max(), ySolar.min(), 0], origin='upper')
poly_negSolar = axSolar.fill_between(xSolar, ySolar, ySolar.max(), alpha=0.1)
grad2.set_clip_path(poly_negSolar.get_paths()[0], transform=axSolar.transData)
poly_negSolar.remove()
axSolar.set_ylim(ylimSolar)
figSolar = plt.gcf()
fig_canvas_aggSolar = draw_figure(window['-SolarGraph-'].TKCanvas, figSolar)
data1_thread = threading.Thread(target=generate_data1)
data1_thread.daemon = True
data1_thread.start()
# Bunch of variables needed for GUI
shown_layout = '0o0' # Current layout
# ----- MAIN LOOP -----
# Create an event loop
while True:
event, values = window.read(timeout=16, timeout_key="-refresh-") # timeout = 16 --> 62,5 Hz
# print(event, values)
if event == "-refresh-":
window.refresh()
if shown_layout == '0o0':
if not graphics1_done.is_set():
fig_canvas_aggSolar.draw()
graphics1_done.set()
if event == sg.WINDOW_CLOSED:
break
window.close()
Screenshot, Sketch, or Drawing
Grabacion.2024-03-22.141951.mp4
Watcha Makin?
Hi everyone 😊,
I've been learning about PySimpleGUI because I wanted to make an interface for myself since I have solar installation in my house.
PySimpleGUI was my best choice to get started with Python GUIs and so I did.
I was able to render all the data I wanted in multiple layouts in the same window thanks to all the demos that exist on the web. My way forward was to copy the code from the demos, try to understand how everything works by changing things and, once understood, try to modify it to be the way I wanted it to be.
Now I was trying to include a graph in the GUI using Matplotlib and I think I did a really good job (and I say THINK, don't judge me too much). This graph is going to represent how much solar energy is being produced overall. Gradients are also great because at the end (now that I know how it works), it will represent in blue how much energy my load is consuming out of the total and in orange how much energy is free to go to my batteries or the grid.
Not sure what the requirement you want, if possible, can you give me an exact image for your target.
Hi Jason,
Thank you so much for being so fast. What I'm trying to accomplish is something I saw in modern solar software. I'm attaching an image.
I think I can get the top-down blur effect by researching more on how to make my own color map in Matplotlib. Instead of starting with the orange color and blur it into white, I can blur into black and maybe that solves half the problem.
But blurring the horizontal edges, I don't know how to do it, really. I tried to investigate to see if I could modify the alpha channel of the sg object Graph(), but it's not possible or I haven't been able to do it, and it may not even be the solution.
Greetings and thank you in advance whether or not there is a solution to the problem.
Pd: Thank you so much for your posts, I saw the circular progress bar one and I included it in my project after spending so many hours trying to realize how the code works. For me it was fascinating how did you use the pillow package for solving that.
Not sure how it can be done or not by using matplotlib, but it cannot be done by using PySimpleGUI/tkinter.
For my knowledge, if the target is correct for you, I will do it by using Pillow library and PySimpleGUI Image element.
import io
import time
import random
import threading
from PIL import Image, ImageDraw, ImageFilter
import PySimpleGUI as sg
class GUI():
def __init__(self, size=(400, 300)):
self.w, self.h = self.size = size
sg.theme('DarkBlue3')
sg.set_options(font=("Courier New", 16))
layout = [
[sg.Image(size=self.size, background_color='black', key='IMAGE')],
[sg.Push(), sg.Button("BLUR"), sg.Push()]
]
self.window = sg.Window(
'Title', layout=layout, enable_close_attempted_event=True,
use_default_focus=False, margins=(0, 0), finalize=True)
self.window["BLUR"].block_focus()
self.blur = False
self.running = True
threading.Thread(target=self.get_data, daemon=True).start()
self.start()
def get_data(self, fg=(255, 255, 255, 255), bg=(0, 0, 0, 255)):
y = self.h//2
while self.running:
self.data = [(i, random.randint(-y, y) + y) for i in range(self.w)]
im = Image.new("RGBA", self.size, color=bg)
draw = ImageDraw.Draw(im, mode="RGBA")
draw.line(self.data, fill=fg, width=1)
draw.line([(0, y), (self.w, y)], fill=fg, width=1)
if self.blur:
im = im.filter(ImageFilter.BLUR)
data = self.image_to_data(im)
self.window.write_event_value("Update", data)
time.sleep(0.1) # time delay to reduce the CPU loading
def image_to_data(self, im):
with io.BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
return data
def start(self):
while True:
event, values = self.window.read()
if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT:
break
elif event == 'Update':
data = values[event]
self.window['IMAGE'].update(data)
elif event == 'BLUR':
self.blur = not self.blur
self.running = False
time.sleep(0.2) # wait thread stop
self.window.close()
GUI()
Hi Jason,
First of all, I'm sorry I didn't answer in these two days. It's been Holy Week in my city and I've been a bit absent.
The solution you show me is great man! But I'd like to know if it can be fine-tuned it a little bit more and just blur the edges.
Best regards and thank you very much!
just blur the edges.
What the edges are ? No exact definition for the edges may get nothing returned.
Firstly, I created an edge-spur mask by PIL.Image
Then using Image.alpha_composite
with both images, main image and the mask.
import io
import time
import random
import threading
from PIL import Image, ImageDraw, ImageFilter
import PySimpleGUI as sg
class GUI():
def __init__(self, size):
self.w, self.h = self.size = size
sg.theme('DarkBlue3')
sg.set_options(font=("Courier New", 16))
layout = [
[sg.Image(size=self.size, background_color='black', key='IMAGE')],
[sg.Push(), sg.Button("BLUR"), sg.Push()]
]
self.window = sg.Window(
'Title', layout=layout, enable_close_attempted_event=True,
use_default_focus=False, margins=(0, 0), finalize=True)
self.window["BLUR"].block_focus()
self.blur = False
self.running = True
threading.Thread(target=self.get_data, daemon=True).start()
self.start()
def get_data(self, fg=(255, 255, 255, 255), bg=(0, 0, 0, 255)):
y = self.h//2
while self.running:
self.data = [(i, random.randint(-y, y) + y) for i in range(self.w)]
im = Image.new("RGBA", self.size, color=bg)
draw = ImageDraw.Draw(im, mode="RGBA")
draw.line(self.data, fill=fg, width=1)
draw.line([(0, y), (self.w, y)], fill=fg, width=1)
if self.blur:
im = Image.alpha_composite(im, mask)
data = self.image_to_data(im)
self.window.write_event_value("Update", data)
time.sleep(0.1) # time delay to reduce the CPU loading
def image_to_data(self, im):
with io.BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
return data
def start(self):
while True:
event, values = self.window.read()
if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT:
break
elif event == 'Update':
data = values[event]
self.window['IMAGE'].update(data)
elif event == 'BLUR':
self.blur = not self.blur
self.running = False
time.sleep(0.2) # wait thread stop
self.window.close()
# Create an edge-spur mask
size = w, h = (400, 300)
mask = Image.new("RGBA", size, color=(0, 0, 0, 0)) # Black
width = 100
for x in range(w):
for y in range(h):
r, g, b, a = mask.getpixel((x, y))
alpha1, alpha2 = 0, 0
if x < width:
alpha1 = int((width - x)/width * 255)
elif x > w - width:
alpha1 = int((x - w + width)/width * 255)
if y < width:
alpha2 = int((width - y)/width * 255)
elif y > h - width:
alpha2 = int((y - h + width)/width * 255)
alpha = max(alpha1, alpha2)
mask.putpixel((x, y), (r, g, g, alpha))
GUI(size)
THAT'S EXACTLY what I wanted man! You are awesome Jason, thank you very much.
I'll try to apply this one, after be able to know how did you do that hehe, to my graph.
Thank you man.
Should I close this thread, Jason?