I'm working on a report generator with Python. So far everything is going well, except for a single detail: I need to add a custom footer to the report. Since I'm also addingg more elements, I need to use a custom Canvas object to build the report, and I think it should be possible to add this custom footer to the canvas class... but so far I've had no success.
Here's the code I've been writing:
Imports:
from reportlab.pdfgen import canvas
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.pagesizes import letter
from reportlab.lib.enums import TA_JUSTIFY
Custom Canvas class:
class CustomCanvas(canvas.Canvas):
"""
Adapted from http://code.activestate.com/recipes/576832/
"""
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
# I'd like to pass the custom footer text to the constructor
# to draw it at the footer for each page. This is a fragment of
# text that will be read from a database
if 'customFooterText' in kwargs:
self.customFooter = kwargs['customFooterText']
else:
self.customFooter = 'Missing custom footer text :('
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def drawPageNumber(self, page_count):
self.setFont('Helvetica', 8)
self.drawRightString(21 * cm, 1 * cm,
'%s / %s' % (self._pageNumber, page_count))
def drawCustomFooter(self):
"""
Here is where I'd like to draw the custom footer.
"""
self.setFont('Helvetica', 8)
self.drawString(1 * cm, 1 * cm, self.customFooter)
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.drawPageNumber(num_pages)
self.drawCustomFooter()
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
My Report builder:
class MyReport:
def __init__(self):
self.filename = 'test.pdf'
self.doc = SimpleDocTemplate(self.filename, pagesize=letter,
topMargin = 1 * cm, bottomMargin = 2 * cm,
leftMargin = 1 * cm, rightMargin = 1 * cm)
self.Story = []
def generateReport(self):
self.reportContent()
self.doc.build(self.Story, canvasmaker=CustomCanvas)
# Here is the issue: How to pass the kwarg to the CustomCanvas
# class so it can be drawn in each page?
def reportContent(self):
# In the "real life", this content will be generated with data from
# a database.
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))
justify = styles['Justify']
txt = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas tristique cursus enim at luctus. Proin tincidunt,
arcu vitae mattis pretium, lorem eros semper lectus,
venenatis luctus orci odio rhoncus nunc. Nam nulla arcu,
hendrerit at lacinia eget, gravida aliquam quam. Mauris
finibus ipsum at leo ullamcorper, ut faucibus est eleifend.
Maecenas vehicula malesuada tempor. Nulla et augue a purus
luctus tincidunt. Nam consectetur ut diam sit amet efficitur.
Morbi a volutpat orci. Donec id ipsum ut quam hendrerit gravida.
Nulla gravida, ante non euismod fermentum, metus nulla ullamcorper
sem, ut feugiat ipsum felis sit amet lectus.
"""
for i in range(15):
self.Story.append(Paragraph(txt, justify))
Test
report = MyReport()
report.generateReport()
When creating the report, the first page looks something like this:
So, the question is: How to add that string to each page using a kwarg in the CustomCanvas class?
I found a way to do what I need... I left alone the custom canvas and used the onFirstPage and onLaterPages parameters to include my custom footer (I don't care where the custom footer string is specified, as long as I can specify it at runtime).
My solution:
Imports:
from reportlab.pdfgen import canvas
from reportlab.platypus import SimpleDocTemplate, Paragraph
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.pagesizes import letter
from reportlab.lib.enums import TA_JUSTIFY
Custom Canvas class:
I left alone the Custom Canvas class. It does what it needs to do:
class CustomCanvas(canvas.Canvas):
"""
Adapted from http://code.activestate.com/recipes/576832/
"""
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def drawPageNumber(self, page_count):
self.setFont('Helvetica', 8)
self.drawRightString(21 * cm, 1 * cm,
'%s / %s' % (self._pageNumber, page_count))
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.drawPageNumber(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
My Report builder:
Here I added my **kwargs parameter, so I can pass a custom argument with the footer text:
class MyReport:
def __init__(self, *args, **kwargs):
self.filename = 'test.pdf'
self.doc = SimpleDocTemplate(self.filename, pagesize=letter,
topMargin = 1 * cm, bottomMargin = 2 * cm,
leftMargin = 1 * cm, rightMargin = 1 * cm)
if 'left_footer' in kwargs:
self.left_footer = kwargs['left_footer']
else:
self.left_footer = None
self.Story = []
def onMyFirstPage(self, canvas, doc):
# If the left_footer attribute is not None, then add it to the page
canvas.saveState()
if self.left_footer is not None:
canvas.setFont('Helvetica', 8)
canvas.drawString(1 * cm, 1 * cm, self.left_footer)
canvas.restoreState()
def onMyLaterPages(self, canvas, doc):
# If the left_footer attribute is not None, then add it to the page
canvas.saveState()
if self.left_footer is not None:
canvas.setFont('Helvetica', 8)
canvas.drawString(1 * cm, 1 * cm, self.left_footer)
canvas.restoreState()
def generateReport(self):
self.reportContent()
self.doc.build(self.Story, canvasmaker=CustomCanvas,
onFirstPage=self.onMyFirstPage,
onLaterPages=self.onMyLaterPages)
def reportContent(self):
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY))
justify = styles['Justify']
txt = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Maecenas tristique cursus enim at luctus. Proin tincidunt,
arcu vitae mattis pretium, lorem eros semper lectus,
venenatis luctus orci odio rhoncus nunc. Nam nulla arcu,
hendrerit at lacinia eget, gravida aliquam quam. Mauris
finibus ipsum at leo ullamcorper, ut faucibus est eleifend.
Maecenas vehicula malesuada tempor. Nulla et augue a purus
luctus tincidunt. Nam consectetur ut diam sit amet efficitur.
Morbi a volutpat orci. Donec id ipsum ut quam hendrerit gravida.
Nulla gravida, ante non euismod fermentum, metus nulla ullamcorper
sem, ut feugiat ipsum felis sit amet lectus.
"""
for i in range(15):
self.Story.append(Paragraph(txt, justify))
Test
report = MyReport(left_footer='A custom test footer for my pages')
# I can now specify my custom footer in runtime!
report.generateReport()
I feel kinda dumb now that I've found that it was so easy.
But I leave this to posterity, in case someone needs to do something similar.
Anyway, if anybody else has a better solution, please add your answer.
Related
Here's the code for defining a Kivy ScrollView Label whichi can be scrolled both vertically and horizontally:
from kivy.app import App
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
class ScrollablePopup(Popup):
contentBox = ObjectProperty()
scrollView = ObjectProperty()
def scrollToTop(self):
self.scrollView.scroll_y = 1 # force scrolling to top
def scrollToBottom(self):
self.scrollView.scroll_y = 0 # force scrolling to bottom
def initText(self, text):
self.contentBox.content.text = text
class ScrollPopup(BoxLayout):
popup = None
def openPopup(self):
self.popup = ScrollablePopup(title="Scrollable popup")
text = u"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus odio nisi, pellentesque molestie adipiscing vitae, aliquam at tellus.\nFusce quis est ornare erat pulvinar elementum ut sed felis. Donec vel neque mauris. In sit amet nunc sit amet diam dapibus lacinia.\nIn sodales placerat mauris, ut euismod augue laoreet at. Integer in neque non odio fermentum volutpat nec nec nulla.\nDonec et risus non mi viverra posuere. Phasellus cursus augue purus, eget volutpat leo. Phasellus sed dui vitae ipsum mattis facilisis vehicula eu justo.\nQuisque neque dolor, egestas sed venenatis eget, porta id ipsum. Ut faucibus, massa vitae imperdiet rutrum, sem dolor rhoncus magna, non lacinia nulla risus non dui.\nNulla sit amet risus orci. Nunc libero justo, interdum eu pulvinar vel, pulvinar et lectus. Phasellus sed luctus diam. Pellentesque non feugiat dolor.\nCras at dolor velit, gravida congue velit. Aliquam erat volutpat. Nullam eu nunc dui, quis sagittis dolor. Ut nec dui eget odio pulvinar placerat.\nPellentesque mi metus, tristique et placerat ac, pulvinar vel quam. Nam blandit magna a urna imperdiet molestie. Nullam ut nisi eget enim laoreet sodales sit amet a felis.\n"
for i in range(0, 4):
text += "\n\n" + text
self.popup.initText(text)
self.popup.open()
class ScrollPopupVertHorzApp(App):
def build(self): # implicitly looks for a kv file of name kivylistview1111.kv which is
# class name without App, in lowercases
Config.set('graphics', 'width', '400')
Config.set('graphics', 'height', '500')
Config.write()
return ScrollPopup()
def on_pause(self):
# Here you can save data if needed
return True
def on_resume(self):
# Here you can check if any data needs replacing (usually nothing)
pass
if __name__ == '__main__':
ScrollPopupVertHorzApp().run()
scrollpopupverthorz,kv
<ScrollPopup>:
orientation: "vertical"
popupButton: popup_button
BoxLayout:
size_hint_y: None
Button:
id: popup_button
text: "Open scrollable popup"
size_hint_y: None
height: "40dp"
on_press: root.openPopup()
<ScrollablePopup>:
id: scr_popup
auto_dismiss: False
contentBox: content_box
scrollView: scroll_view
padding:10, 10
BoxLayout:
id: content_box
orientation: "vertical"
content: content_text
ScrollView:
id: scroll_view
effect_cls: "ScrollEffect" # prevents overscrolling
do_scroll_x: True # enabling horizontal scrolling
Label:
id: content_text
size_hint_x: None # required for horizontal scrolling to work. This
# comment is wrong. See the answer below !
size_hint_y: None # required for horizontal scrolling to work
height: self.texture_size[1] # required for vertical scrolling to work
width: self.texture_size[0] # Set the Label width to the text width.
# Requnred for horizontal scrolling to work
line_height: 1.5
valign: "top"
Button:
text: "Scroll to top"
size_hint_y: None
height: "40dp"
on_press: scr_popup.scrollToTop()
Button:
text: "Scroll to bottom"
size_hint_y: None
height: "40dp"
on_press: scr_popup.scrollToBottom()
Button:
text: "Close"
size_hint_y: None
height: "40dp"
on_press: root.dismiss()
My question is: how can I set the Label width to a smaller or bigger value without, respectively, cut part of the text lines, or add spaces to the left of each text line ?
The size_hint does not actually need to be None for scrolling to work, you just need the final height or width to be bigger than the ScrollView height or width. So, you can set size_hint_x to anything greater than 1, and you will still get horizontal scrolling:
Label:
id: content_text
size_hint_x: 1.5 # must be greater than 1 for horizontal scrolling
size_hint_y: None # requnred for vertical scrolling to work
height: self.texture_size[1] # required for vertical scrolling to work
text_size: self.width, None
line_height: 1.5
valign: "top"
The key is setting the text_size, so that the text is constrained horizontally.
Note that you can use size_hint_x: None and set width to some specific value. If that width is greater than the ScrollView width, then you will get horizontal scrolling.
Usíng a scrollable TextInput instead of a scrollable Label does not solve the problem.
Here's the code.
Python file:
import os
import pandas as pd
from kivy.app import App
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
OWNER = 'OWNER'
CAPITAL_USD = 'CAPITAL USD'
YIELD_USD = 'YIELD USD'
CAPITAL_CHF = 'CAPITAL CHF'
YIELD_CHF = 'YIELD CHF'
CAPITAL_EUR = 'CAPITAL EUR'
YIELD_EUR = 'YIELD EUR'
CAPITAL_DM = 'CAPITAL DM'
YIELD_DM = 'YIELD DM'
class PandasTextInputScrolling(BoxLayout):
textOutput = ObjectProperty()
def initTextOutput(self):
self.textOutput.text = ''
self.textOutput.text = self.createDataframe()
def clearAll(self):
self.textOutput.text = ''
def createDataframe(self):
df = pd.DataFrame({OWNER: ['John', 'John', 'John', 'John', 'John', 'John', 'Rob', 'Rob', 'Rob', 'Rob', 'Rob',
'Rob', 'Rob', 'Rob', 'Tom', 'Tom', 'Tom', 'Tom', 'Tom', 'Tom', 'Bob', 'Bob', 'Bob',
'Bob', 'Bob'] * 3,
CAPITAL_USD: [10000, 5000, 20000, 4000, 3000] * 15,
YIELD_USD: [1000, 500, 2000, 400, 300] * 15,
CAPITAL_CHF: [10000, 5000, 20000, 4000, 3000] * 15,
YIELD_CHF: [1000, 500, 2000, 400, 300] * 15,
CAPITAL_EUR: [10000, 5000, 20000, 4000, 3000] * 15,
YIELD_EUR: [1000, 500, 2000, 400, 300] * 15,
CAPITAL_DM: [10000, 5000, 20000, 4000, 3000] * 15,
YIELD_DM: [1000, 500, 2000, 400, 300] * 15})
# adding OWNER total rows
dfGroupOwnerTotal = df.groupby([OWNER]).agg({CAPITAL_USD: 'sum',
YIELD_USD: 'sum',
CAPITAL_CHF: 'sum',
YIELD_CHF: 'sum',
CAPITAL_EUR: 'sum',
YIELD_EUR: 'sum',
CAPITAL_DM: 'sum',
YIELD_DM: 'sum'})
totalDf = pd.DataFrame(columns=[OWNER,
CAPITAL_USD,
YIELD_USD,
CAPITAL_CHF,
YIELD_CHF,
CAPITAL_EUR,
YIELD_EUR,
CAPITAL_DM,
YIELD_DM])
currentOwner = df.loc[1, OWNER]
totalDfIndex = 0
# deactivating SettingWithCopyWarning caueed by totalRow[OWNER] += ' total'
pd.set_option('mode.chained_assignment', None)
for index, row in df.iterrows():
if currentOwner == row[OWNER]:
totalDf = totalDf.append({OWNER: row[OWNER],
CAPITAL_USD: row[CAPITAL_USD],
YIELD_USD: row[YIELD_USD],
CAPITAL_CHF: row[CAPITAL_CHF],
YIELD_CHF: row[YIELD_CHF],
CAPITAL_EUR: row[CAPITAL_EUR],
YIELD_EUR: row[YIELD_EUR],
CAPITAL_DM: row[CAPITAL_DM],
YIELD_DM: row[YIELD_DM]}, ignore_index=True)
else:
totalRow = dfGroupOwnerTotal.loc[currentOwner]
totalDf = totalDf.append(totalRow, ignore_index=True)
totalDf.iloc[totalDfIndex][OWNER] = currentOwner + ' total'
totalDfIndex += 1
totalDf = totalDf.append({OWNER: row[OWNER],
CAPITAL_USD: row[CAPITAL_USD],
YIELD_USD: row[YIELD_USD],
CAPITAL_CHF: row[CAPITAL_CHF],
YIELD_CHF: row[YIELD_CHF],
CAPITAL_EUR: row[CAPITAL_EUR],
YIELD_EUR: row[YIELD_EUR],
CAPITAL_DM: row[CAPITAL_DM],
YIELD_DM: row[YIELD_DM]}, ignore_index=True)
currentOwner = row[OWNER]
totalDfIndex += 1
totalRow = dfGroupOwnerTotal.loc[currentOwner]
totalDf = totalDf.append(totalRow, ignore_index=True)
totalDf.iloc[totalDfIndex][OWNER] = currentOwner + ' total'
return totalDf.to_string()
class PandasTextInputScrollingApp(App):
def build(self):
if os.name != 'posix':
# running app om Windows
Config.set('graphics', 'width', '400')
Config.set('graphics', 'height', '300')
Config.write()
self.gui = PandasTextInputScrolling()
self.gui.initTextOutput()
return self.gui
PandasTextInputScrollingApp().run()
KV file:
<PandasTextInputScrolling>:
textOutput: txt_output
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
size_hint_y: 0.9
ScrollView:
id: scrlv
size_hint_y: 0.9
effect_cls: "ScrollEffect" # prevents overscrolling
TextInput:
id: txt_output
readonly: True
size_hint_x: 1.5 # any value greater than 1 can be set to enable
# scrolling, with the risk of cutting part of the
# text width !
size_hint_y: None # required for horizontal scrolling to work
height: self.texture_size[1] # required for vertical scrolling to work
line_height: 1.5
height: max( (len(self._lines)+1) * self.line_height, scrlv.height)
Button:
id: clear_output_Button
text: 'Clear'
size_hint_y: 0.1
on_press: root.clearAll()
Button:
id: fill_output_Button
text: 'Show Pandas data frame'
size_hint_y: 0.1
on_press: root.initTextOutput()
I do have a class with multiple methods in which I'd like to add constant values of instances of this class, to keep common used combinations handy.
class Text:
GREETING = Text('Hello')
LOREM = Text('Lorem ipsum dolor sit amen')
def __init__(self, text):
self.text = text
def double(self):
return f'{self.text}, {self.text}'
This doesn't work in that way (Undefined name 'Text'). I could declare the constants later on:
Text.GREETING = Text('Hello')
Text.LOREM = Text('Lorem ipsum dolor sit amen')
But that doesn't seem quite nice to me. I'd like to have it all inside of the class definition to be able to document everything nicely. Is there any other way possible?
You might make use of class- or static methods, if you don't mind the extra paranthesis and construction at call site:
class Text:
#staticmethod
def lorem():
return Text("Lorem ipsum")
# or
#classmethod
def greeting(cls):
return cls("Hello")
Does this look better to you?
class Text:
def __init__(self, text):
self.text = text
#classmethod
def register(cls, name, text):
setattr(cls, name, text)
Text.register('GREETING', 'hello')
Text.register('LOREM', 'Lorem ipsum dolor sit amen')
EDIT:
In case you need this pattern for multiple classes, it can be easily extracted to a separate class and use it as a Mixin:
class Registry:
#classmethod
def register(cls, name, text):
setattr(cls, name, text)
class Text(Registry):
def __init__(self, text):
self.text = text
Text.register('GREETING', 'hello')
Text.register('LOREM', 'Lorem ipsum dolor sit amen')
I'm coding an CMQ application like in kivy. There is a probleme with my layout display : some multi-line buttons don't appear. I want that each line fit to the content.
It's my fist post, so please feel free to say if the way to ask question is wrong.
I have try with Grid, Box, StackLayouts with many .bind(minimum_height) and so on.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.core.window import Window
questions = []
questions.append({'question' : 'Lorem ipsum dolor Lorem ipsum dolor sit amet sit amet',
'items':['Lorem', 'ipsum dolor nec sapien sit amet libero ', 'sit amet']})
questions.append({'question' : 'Curabitursit amet libero nec sapien nec .',
'items':['Lorem', 'ipsum dolor sapien nec libero',
'sit amet libero egestas dictum eu eget neque']})
questions.append({'question' : 'Curabitur nec sapien nec .',
'items':['Lorem', 'ipsum dolor sapien nec libero',
'sit amet libero egestas dictum eu eget neque']})
class MultiLineLabel(Button):
def __init__(self, **kwargs):
super(MultiLineLabel, self).__init__(**kwargs)
self.text_size = self.size
self.bind(size=self.on_size)
self.bind(text=self.on_text_changed)
self.size_hint_y = None # Not needed here
self.padding_y = 7
self.halign = 'center'
self.valign = 'middle'
# self.give_height = self.texture_size[1]
# print('init', self.give_height)
def on_size(self, widget, size):
self.text_size = size[0], None
self.texture_update()
# self.give_height = self.texture_size[1]
# print('dans l\'ombjet', self.texture_size[1])
if self.size_hint_y == None and self.size_hint_x != None:
self.height = max(self.texture_size[1], self.line_height)
elif self.size_hint_x == None and self.size_hint_y != None:
self.width = self.texture_size[0]
def on_text_changed(self, widget, text):
self.on_size(self, self.size)
class QuestionApplication(GridLayout):
def __init__(self, *args):
super(QuestionApplication, self).__init__(*args)
self.size_hint_y = None
self.cols = 2
self.spacing =10
for ask in questions:
label = Label(text=ask['question'],halign='center',
valign='top', size_hint=(0.6, None),
text_size=(self.width, None))
label.bind(size=label.setter('text_size'))
self.add_widget(label)
vbox = GridLayout(size_hint_y=None, cols=1,
size_hint_x=0.4)
for item in ask['items']:
button = MultiLineLabel(text=item)
vbox.add_widget(button)
self.add_widget(vbox)
self.bind(minimum_height=self.setter('height'))
class ExamApp(App):
def build(self):
return QuestionApplication()
if __name__ == '__main__':
Window.size = (500, 400)
ExamApp().run()
You are taking into account the sizes of your QuestionApplication children with the line:
self.bind(minimum_height=self.setter('height'))
but the vbox size is not accounting for changes in its children.
In your QuestionApplication __init__() method, you need to add
vbox.bind(minimum_height = vbox.setter('height'))
after you create the vbox.
I have questions about code when I plot figure with imshow matplotlib I have a plot with x y and z= value of pixel like picture :
code :
import netCDF4
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
path = 'D/data/'
fic = 'air.departure.sig995.2012.nc'
nc = netCDF4.Dataset(path+fic,'r')
var = nc.variables.keys()
lats = nc.variables['lat'][:] # extract/copy the data
lons = nc.variables['lon'][:]
air_dep = nc.variables['air_dep'][:,:,:]
air_dep = air_dep[0,:,:]
plt.imshow(air_dep)
plt.show()
I have an app with wxpython I can show in other panel the cordinates cursor x y but I need to show the value of pixel z how can I do that?
This is the code for application:
import wx
import numpy as np
import netCDF4
from netCDF4 import Dataset
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.patches as patches
import matplotlib
class Window(wx.Frame):
""" Fenêtre principale de l'application """
def __init__(self, **kwargs):
super().__init__(None, **kwargs)
RootPanel(self)
class RootPanel(wx.Panel):
""" Panel contenant tous les autres widgets de l'application """
def __init__(self, parent):
super().__init__(parent)
panel_buttons = wx.Panel(self)
panel_buttons_sizer = wx.GridSizer(1, 2, 0, 0)
self.canvas_panel = CanvasPanel(self)
self.panel_two = PanelTwo(parent=self)
select_button = PickButton(
panel_buttons,
"netCDF4 files (nc)|*.nc",
self.canvas_panel.load_from_file,
label="Show on this window (nc)",
)
toplevel_select_button = TopLevelPickButton(
panel_buttons,
"Text files (txt)|*.txt|All files|*.*",
label="Show on separate window (txt)",
)
panel_buttons_sizer.Add(select_button)
panel_buttons_sizer.Add(toplevel_select_button)
panel_buttons.SetSizer(panel_buttons_sizer)
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.panel_two,1,wx.EXPAND)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel_buttons)
sizer.Add(canvas_sizer)
self.SetSizerAndFit(sizer)
self.Show()
class PickButton(wx.Button):
""" Bouton permettant de choisir un fichier """
def __init__(self, parent, wildcard, func, **kwargs):
# func est la méthode à laquelle devra être foruni le fichier sélectionné
super().__init__(parent, **kwargs)
self.wildcard = wildcard
self.func = func
self.Bind(wx.EVT_BUTTON, self.pick_file)
def pick_file(self, evt):
style = style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE
with wx.FileDialog(
self, "Pick files", wildcard=self.wildcard, style=style
) as fileDialog:
if fileDialog.ShowModal() != wx.ID_CANCEL:
chosen_file = fileDialog.GetPath()
self.func(chosen_file)
class TopLevelPickButton(PickButton):
""" Permet de choisir un fichier et d'ouvrir une toplevel """
def __init__(self, parent, wildcard, **kwargs):
super().__init__(parent, wildcard, self.create_toplevel, **kwargs)
def create_toplevel(self, file_name):
""" Ouvre une toplevel et affiche le graphique """
self.win = TopLevelCanvas(self.Parent)
self.win.canvas_panel.load_from_file(file_name)
self.win.Show()
class CanvasPanel(wx.Panel):
""" Panel du graphique matplotlib """
def __init__(self, parent , size=(200,250)):
super().__init__(parent)
self.figure = Figure(figsize =(5,5))
self.canvas = FigureCanvas(self, -1, self.figure)
self.Size = self.canvas.Size
self.parent = parent
def load_from_file(self, file_name):
"""
Méthode effectuant l'intermédiaire pour charger le fichier selon
son type
"""
self.axes = self.figure.add_subplot(111)
if file_name.endswith(".nc"):
self._load_nc(file_name)
else:
self._load_txt(file_name)
self.canvas.draw()
def _load_txt(self, file_name):
self._load_nc(file_name)
def _load_nc(self, file_name):
""" Simule le chargement et affichage à partir d'un fichier nc """
fic='air.departure.sig995.2012.nc'
path='D:/data/'
nc = netCDF4.Dataset(path+fic,'r')
lons = nc.variables['lon'][:]
lats = nc.variables['lat'][:]
air_dep = nc.variables['air_dep'][:,:,:]
air_dep = air_dep[0,:,:]
self.axes.imshow(air_dep)
self.canvas.mpl_connect('button_press_event', self.on_press)
x = y = 1
self.rect = patches.Rectangle((x, y), 5,5,edgecolor='r', alpha=1, fill=None, label='Label')
self.axes.add_patch(self.rect)
self.axes.plot()
self.Show()
def on_press(self, click):
x1, y1 = click.xdata, click.ydata
self.parent.panel_two.Update(x1,y1)
zx1 = x1 - 2.5
zy1 = y1 - 2.5
zx2 = x1 + 2.5
zy2 = y1 + 2.5
self.rect.set_x(x1 - 2.5) #Move the rectangle and centre it on the X click point
self.rect.set_y(y1 - 2.5) #Move the rectangle and centre it on the Y click point
self.axes.plot()
self.canvas.draw()
class PanelTwo(wx.Panel): #here when i need to visualize pixel and coordinator cursor
def __init__(self,parent):
wx.Panel.__init__(self,parent,size=(300,250))
self.text_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2, size=(200,170))
lbl = wx.StaticText(self,label="Coordinato cursor & Pixel ")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl,0, wx.ALIGN_CENTRE,10)
sizer.Add(self.text_ctrl,0, wx.ALIGN_CENTRE,10)
self.SetSizer(sizer)
def Update(self,x1,y1):
self.text_ctrl.SetValue("Mouse click at;\nX "+str(x1)+"\nY "+str(y1))
class TopLevelCanvas(wx.Frame):
""" Fenêtre affichant uniquement un graph matplotlib """
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.canvas_panel = CanvasPanel(self)
self.zoom_panel = Zoom(parent=self)
self.Size = self.canvas_panel.Size
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.zoom_panel,1,wx.EXPAND)
self.SetSizerAndFit(canvas_sizer)
self.Show()
class App(wx.App):
def OnInit(self):
win = Window(title="A test dialog", size=(1000, 800))
win.Show()
return True
if __name__ == "__main__":
app = App()
app.MainLoop()
How can I show the value of pixel in the paneltwo like x y coordinates?
AND ALL VALUES OF AIR_DEP IN WINDOWS 5X5 like this example : IR_108 is other variables value
thank you
When you say "pixel z" do you mean the air_dep value?
import wx
import numpy as np
import netCDF4
from netCDF4 import Dataset
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.patches as patches
import matplotlib
class Window(wx.Frame):
""" Fenêtre principale de l'application """
def __init__(self, **kwargs):
super().__init__(None, **kwargs)
RootPanel(self)
class RootPanel(wx.Panel):
""" Panel contenant tous les autres widgets de l'application """
def __init__(self, parent):
super().__init__(parent)
panel_buttons = wx.Panel(self)
panel_buttons_sizer = wx.GridSizer(1, 2, 0, 0)
self.canvas_panel = CanvasPanel(self)
self.panel_two = PanelTwo(parent=self)
select_button = PickButton(
panel_buttons,
"netCDF4 files (nc)|*.nc",
self.canvas_panel.load_from_file,
label="Show on this window (nc)",
)
toplevel_select_button = TopLevelPickButton(
panel_buttons,
"Text files (txt)|*.txt|All files|*.*",
label="Show on separate window (txt)",
)
panel_buttons_sizer.Add(select_button)
panel_buttons_sizer.Add(toplevel_select_button)
panel_buttons.SetSizer(panel_buttons_sizer)
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.panel_two,1,wx.EXPAND)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel_buttons)
sizer.Add(canvas_sizer)
self.SetSizerAndFit(sizer)
self.Show()
class PickButton(wx.Button):
""" Bouton permettant de choisir un fichier """
def __init__(self, parent, wildcard, func, **kwargs):
# func est la méthode à laquelle devra être foruni le fichier sélectionné
super().__init__(parent, **kwargs)
self.wildcard = wildcard
self.func = func
self.Bind(wx.EVT_BUTTON, self.pick_file)
def pick_file(self, evt):
style = style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE
with wx.FileDialog(
self, "Pick files", wildcard=self.wildcard, style=style
) as fileDialog:
if fileDialog.ShowModal() != wx.ID_CANCEL:
chosen_file = fileDialog.GetPath()
self.func(chosen_file)
class TopLevelPickButton(PickButton):
""" Permet de choisir un fichier et d'ouvrir une toplevel """
def __init__(self, parent, wildcard, **kwargs):
super().__init__(parent, wildcard, self.create_toplevel, **kwargs)
def create_toplevel(self, file_name):
""" Ouvre une toplevel et affiche le graphique """
self.win = TopLevelCanvas(self.Parent)
self.win.canvas_panel.load_from_file(file_name)
self.win.Show()
class CanvasPanel(wx.Panel):
""" Panel du graphique matplotlib """
def __init__(self, parent , size=(200,250)):
super().__init__(parent)
self.figure = Figure(figsize =(5,3))
self.canvas = FigureCanvas(self, -1, self.figure)
self.Size = self.canvas.Size
self.parent = parent
def load_from_file(self, file_name):
"""
Méthode effectuant l'intermédiaire pour charger le fichier selon
son type
"""
self.axes = self.figure.add_subplot(111)
if file_name.endswith(".nc"):
self._load_nc(file_name)
else:
self._load_txt(file_name)
self.canvas.draw()
def _load_txt(self, file_name):
self._load_nc(file_name)
def _load_nc(self, file_name):
""" Simule le chargement et affichage à partir d'un fichier nc """
fic='air.departure.sig995.2012.nc'
#path='/home/data/'
path=''
nc = netCDF4.Dataset(path+fic,'r')
self.lons = nc.variables['lon'][:]
self.lats = nc.variables['lat'][:]
air_dep = nc.variables['air_dep'][:,:,:]
self.air_dep = air_dep[0,:,:]
self.axes.imshow(self.air_dep)
self.canvas.mpl_connect('button_press_event', self.on_press)
x = y = 1
self.rect = patches.Rectangle((x, y), 5,5,edgecolor='r', alpha=1, fill=None, label='Label')
self.axes.add_patch(self.rect)
self.axes.plot()
def float_to_dms(self,f):
# convert to seconds
f = f * 3600
if f < 0:
div1 = -3600
div2 = -1
else:
div1 = 3600
div2 = 1
#Degrees,minutes,seconds
d,m = divmod(f,div1)
m,s = divmod(m*60,div1)
s, x = divmod(s*60,div1)
d,x = divmod(d,div2)
m,x = divmod(m,1)
s,x = divmod(s,1)
return ("\n\t"+str(int(d))+"° "+str(int(m)).zfill(2)+"' "+str(int(s)).zfill(2)+'"\n')
def on_press(self, click):
x1, y1 = click.xdata, click.ydata
#Longititude values are 2.5 degrees apart for this data
lon = x1*2.5
#Latitude values from 90 to -90 2.5 degrees apart
#split the y value
i,f = divmod(y1,1)
lat = self.lats[int(i)]
#calculate the fraction of degrees
if lat > 0 and lat < 87.5:
lat = lat+(f*2.5)
elif lat < 0 and lat > -87.5:
lat = lat-(f*2.5)
#Convert to ° ' "
lon_dms = self.float_to_dms(lon)
lat_dms = self.float_to_dms(lat)
#air_dep seems the wrong way round but what do I know
#The way below gives values that seem correct
air = self.air_dep[int(y1),int(x1)]
self.parent.panel_two.Update(x1,y1,lon,lat,air,lon_dms,lat_dms)
zx1 = x1 - 2.5
zy1 = y1 - 2.5
zx2 = x1 + 2.5
zy2 = y1 + 2.5
self.rect.set_x(x1 - 2.5) #Move the rectangle and centre it on the X click point
self.rect.set_y(y1 - 2.5) #Move the rectangle and centre it on the Y click point
self.axes.plot()
self.canvas.draw()
class PanelTwo(wx.Panel): #here when i need to visualize pixel and coordinator cursor
def __init__(self,parent):
wx.Panel.__init__(self,parent,size=(300,250))
self.text_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2, size=(200,200))
lbl = wx.StaticText(self,label="Coordinato cursor & Pixel ")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl,0, wx.ALIGN_CENTRE,10)
sizer.Add(self.text_ctrl,0, wx.ALIGN_CENTRE,10)
self.SetSizer(sizer)
def Update(self,x1,y1,lon,lat,air,lon_dms,lat_dms):
update_str = "Mouse click at;\nX "+str(x1)+"\nLon "+str(lon)+"\n"+lon_dms+"\nY "+str(y1)+"\nLat "+str(lat)+"\n"+lat_dms+"\nAir Value "+str(air)
self.text_ctrl.SetValue(update_str)
class TopLevelCanvas(wx.Frame):
""" Fenêtre affichant uniquement un graph matplotlib """
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.canvas_panel = CanvasPanel(self)
self.zoom_panel = Zoom(parent=self)
self.Size = self.canvas_panel.Size
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.zoom_panel,1,wx.EXPAND)
self.SetSizerAndFit(canvas_sizer)
self.Show()
class App(wx.App):
def OnInit(self):
win = Window(title="A test dialog", size=(1000, 800))
win.Show()
return True
if __name__ == "__main__":
app = App()
app.MainLoop()
I have long text items in QComboBox, and I want to display complete text of items in multiple lines. What should I do. Thank you. Currently it puts ... between the start and end of text.
To get something like on the image:
I'll need QListView (with its method setWordWrap), QStringListModel (for example only, you can use any model) and QComboBox.
Example:
import sys
from PyQt5.QtWidget import QComboBox, QListView, QApplication
from PyQt5.QtCore import QStringListModel
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
combo = QComboBox()
combo.setMaximumWidth(150)
# For the popup items data we use QStringListModel
combo.setModel(QStringListModel([
'1. Lo',
'2. Lorem',
'3. Lorem ipsum dolor sit amet, consectetur',
'4. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut',
'5. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco labor'
]))
# The popup widget is QListView
listView = QListView()
# Turn On the word wrap
listView.setWordWrap(True)
# set popup view widget into the combo box
combo.setView(listView)
combo.show()
sys.exit(app.exec_())
Unfortunately, you have provided an example demonstrating the problem.
You can implement your idea using the sizeAdjustPolicy andsizePolicy properties.
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class Main(QtWidgets.QWidget):
def __init__(self):
super(Main, self).__init__()
sheets = [str(i) for i in ("item1",
"item2 item2",
"item3_item3_item3",
"The combobox will always adjust to the contents")]
self.combo = QtWidgets.QComboBox()
self.combo.addItems(sheets)
self.lineEdit = QtWidgets.QLineEdit("Here type a new value for the current setItemText")
self.combo.setSizeAdjustPolicy(self.combo.AdjustToContents)
self.combo.setSizePolicy(QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Fixed)
self.shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Return),
self.combo,
activated=self.onActivated)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(QtWidgets.QLabel("ComboBox:"))
layout.addWidget(self.combo)
layout.addWidget(self.lineEdit)
self.setLayout(layout)
def onActivated(self):
index = self.combo.currentIndex()
self.combo.setEditable(True)
self.combo.setItemText(index, self.lineEdit.text())
self.combo.setEditable(False)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.resize(600, 200)
main.show()
sys.exit(app.exec_())