I am creating pdf using Reportlab and python 3.when a table split automatically it overlap my page header.In first page no issue.From second page onwards header gets overlapped by data. Any solution?
There are probably a few different ways to solve this problem with ReportLab. Frames can be a used to achieve the results you are looking for. The example below draws the same header on each page and restricts the table to the frame. Additionally, it uses the repeatRows table parameter to repeat the table header on each page.
The showBoundary parameter was used to show the location and size of the frame. Delete this argument or set to 0 to remove boundary lines.
The solution was developed based on this answer about text and footers.
from reportlab.platypus import BaseDocTemplate
from reportlab.platypus import Frame, PageTemplate
from reportlab.platypus import Paragraph, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib import colors
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
def header(canvas, pdf):
# Draw heading
heading = Paragraph("Heading", styleH)
heading.wrap(pdf.width, inch * 0.5)
heading.drawOn(canvas, pdf.leftMargin, pdf.height + inch)
# Draw subheading.
subheading = Paragraph("subeading", styleN)
subheading.wrap(pdf.width, inch * 0.25)
subheading.drawOn(canvas, pdf.leftMargin, pdf.height + inch * 0.75)
pdf = BaseDocTemplate('example.pdf', pagesize=letter)
frame = Frame(
pdf.leftMargin,
pdf.bottomMargin,
pdf.width,
pdf.height - inch * 0.5,
showBoundary=1) # Delete to remove line around the frame.
template = PageTemplate(id='all_pages', frames=frame, onPage=header)
pdf.addPageTemplates([template])
# Data for table
data = [["String", "String", "Number", "Number", "Number"]]
data.extend([["one", "two", i, i, i] for i in range(90)])
# Styles for table
table_style = TableStyle([
('LINEBELOW', (0, 0), (-1, 0), 2, colors.black),
('ALIGN', (2, 0), (4, -1), 'RIGHT'),
])
# Create table and repeat row 1 at every split.
table = Table(data, repeatRows=1, style=table_style)
pdf.build([table])
Related
I have a plot made up of 3 choropleth subplots next to each other. I set the overall height and width to my desired dimensions (800 x 400 pixels). I want each subplot to go from top to bottom, but as it stands, the subplots retain the aspect ratio of 2:1, meaning I have wide margins at top and bottom. Those I want to remove.
As a minimum example, I am attaching the data and plot code:
The toy dataset:
import geopandas as gpd
from shapely.geometry.polygon import Polygon
minidf = gpd.GeoDataFrame(dict(
krs_code = ["08111", "08118"],
m_rugged = [42.795776, 37.324421],
bip = [83747, 43122],
cm3_over_1999 = [47.454688, 47.545940],
geometry = [Polygon(((9.0397, 48.6873),
(9.0397, 48.8557),
(9.3152, 48.8557),
(9.3152, 48.6873),
(9.0397, 48.6873))),
Polygon(((8.8757, 48.7536),
(8.8757, 49.0643),
(9.4167, 49.0643),
(9.4167, 48.7536),
(8.8757, 48.7536)))]
)).set_index("krs_code")
The plotting code:
import json
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(rows = 1, cols = 3,
specs = [[{"type": "choropleth"}, {"type": "choropleth"}, {"type": "choropleth"}]],
horizontal_spacing = 0.0025 )
fig.update_layout(height = 400, width = 800,
margin = dict(t=0, r=0, b=0, l=0),
coloraxis_showscale=False )
for i, column in enumerate(["m_rugged", "cm3_over_1999", "bip"]):
fig.add_trace(
go.Choropleth(
locations = minidf.index,
z = minidf[column].astype(float), # Data to be color-coded
geojson = json.loads(minidf[["geometry"]].to_json()),
showscale = False
),
col = i+1, row = 1)
fig.update_geos(fitbounds="locations", visible=True)
fig.show()
Notice the margins at top and bottom, which retain the aspect ratio of each subplot, while they are supposed to stretch from top to bottom:
I tried several parameters within go.Choropleth() and .update_layout(), but to no avail.
hand-filled character per box form
I want to automate a process in which I would get hand-filled character per box type forms in image format and I need to extract text from these forms. The boxes surrounds each letter, I have to extract all the text from the image form.
You can use selecting contours by size, find rotated rectangle and inverse transform make.
import cv2
import numpy as np
img = cv2.imread('4YAry.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# convert to binary image
thresh=cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV )[1]
contours,hierarchy = cv2.findContours(thresh, 1, 2)
for cnt in contours:
x , y , w , h = cv2 . boundingRect ( cnt )
if abs(w-345)<10: # width box is 345 px
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
srcTri=np.array( [box[1], box[0], box[2]] ).astype(np.float32)
dstTri = np.array( [[0, 0], [0, rect[1][1]], [rect[1][0],0]] ).astype(np.float32)
warp_mat = cv2.getAffineTransform(srcTri, dstTri)
warp_dst = cv2.warpAffine(img, warp_mat, (np.int0(rect[1][0]), np.int0(rect[1][1])))
N=14
s=0.99*warp_dst.shape[1]/N # tune rectangle positions
for i in range(N):
warp_dst = cv2.rectangle ( warp_dst , ( 2+int(i*s) ,2 ), ( 2+int((i+1)*s) , warp_dst.shape[0]-3 ), ( 255 , 255 , 255 ), 2 )
cv2.imwrite('chars.png', warp_dst)
Using for instance Hough, detect the top and bottom edges and the vertical separations. Validate the separations by checking that they run from top to bottom. The horizontal lines will be more reliable and accurate, you can use their direction for deskewing if necessary.
After doing that, you will have missing separations and false ones. Using some heuristics, try to find the correct pitch and detect the false positives and false negatives. Now you can extract the content of the individual boxes, or erase the edges.
This process cannot be perfect, some characters will be damaged.
I'm trying to create a 'table' where the headers and row labels remain visible. Kind of like the Frozen Column Example, but using a QGridLayout.
My approach is to put three QGridLayouts into aligned QScrollAreas, where the main table contains RxC rows and columns and is scrollable in any direction, and the column and row headers are contained in 1xC and Rx1 cells, respectively. Then I can just get the different QScrollAreas to track each other.
I've got this working (though I haven't touched the matched-scrolling bit yet), but I'm having a lot of trouble getting the three different QGridLayouts to have the same size cells (in width or height).
Part of the problem is that the table cells can be variously sized, meaning that each row and column is not the same height/width as all of the others. But I also can't figure out how to get the heights/widths of the rows of the different QGridLayouts to match.
As you can see in the image above, there are three rows and two columns. I want the Row Headers to line up with the three rows, and the Column Headers to line up with the two columns.
Here's a minimal example. There's a lot still for me to figure out and get working, but this first step is an important one.
import PyQt5.QtGui
import PyQt5.QtWidgets
import PyQt5.QtCore
class MainBuyerWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(PyQt5.QtWidgets.QMainWindow, self).__init__(*args, **kwargs)
(
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
) = self.setup_table()
self.columns_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.rows_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.table_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.columns_scroll_widget.setWidget(columns_widget)
self.rows_scroll_widget.setWidget(rows_widget)
self.table_scroll_widget.setWidget(table_widget)
central_layout = PyQt5.QtWidgets.QGridLayout()
central_layout.addWidget(self.columns_scroll_widget, 0, 1, 1, 5)
central_layout.addWidget( self.rows_scroll_widget, 1, 0, 5, 1)
central_layout.addWidget( self.table_scroll_widget, 1, 1, 5, 5)
central_widget = PyQt5.QtWidgets.QWidget()
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
self.show()
def setup_table(self):
columns_layout = PyQt5.QtWidgets.QGridLayout()
rows_layout = PyQt5.QtWidgets.QGridLayout()
table_layout = PyQt5.QtWidgets.QGridLayout()
columns_widget = PyQt5.QtWidgets.QWidget()
columns_widget.setLayout(columns_layout)
rows_widget = PyQt5.QtWidgets.QWidget()
rows_widget.setLayout(rows_layout)
table_widget = PyQt5.QtWidgets.QWidget()
table_widget.setLayout(table_layout)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell\nis a\ntall one"), 0, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell is shorter"), 1, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("This cell is of\nmedium height"), 2, 0)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("Also notice that widths of columns are not all the same"), 0, 1)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("Though that doesn't matter"), 1, 1)
table_layout.addWidget(PyQt5.QtWidgets.QLabel("(In this example anyway)"), 2, 1)
columns_layout.addWidget(PyQt5.QtWidgets.QLabel("Column Header 1"), 0, 0)
columns_layout.addWidget(PyQt5.QtWidgets.QLabel("Column Header 2"), 0, 1)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 1"), 0, 0)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 2"), 1, 0)
rows_layout.addWidget(PyQt5.QtWidgets.QLabel("Row Header 3"), 2, 0)
return (
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
)
######################################################################
if __name__ == "__main__":
app = PyQt5.QtWidgets.QApplication([])
window = MainBuyerWindow()
app.exec_()
I think my main problem is I don't understand Qt well enough, so I'm not sure what to look at. What I tried was calling the show() (or activate()) method to get the geometries all worked out, and then going through the first row and column of the table_layout to get their dimensions using cellRect, and then calling columnMinimumWidth and rowMinimumHeight on the corresponding row/column of rows_layout and columns_layout.
This is the same idea used in this (almost identical) question, but when I implement it here it also doesn't seem to work. (Essentially just adding the following lines)
table_layout.activate()
for i in range(table_layout.columnCount()):
w = table_layout.columnMinimumWidth(i)
columns_layout.setColumnMinimumWidth(i, w)
for i in range(table_layout.rowCount()):
h = table_layout.rowMinimumHeight(i)
rows_layout.setRowMinimumHeight(i, h)
Can someone help me out here? Thank you so much!
I would set a fixed height to each row header label based on the height of that row in the table (and similarly a fixed width to each column header label based on the width of that column in the table), which fixes the layout alignment but also makes it simple to match up the scrolling.
class MainBuyerWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(PyQt5.QtWidgets.QMainWindow, self).__init__(*args, **kwargs)
(
columns_widget, rows_widget, table_widget,
columns_layout, rows_layout, table_layout,
) = self.setup_table()
self.columns_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.rows_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.table_scroll_widget = PyQt5.QtWidgets.QScrollArea()
self.columns_scroll_widget.setWidget(columns_widget)
self.rows_scroll_widget.setWidget(rows_widget)
self.table_scroll_widget.setWidget(table_widget)
for i in range(rows_layout.count()):
label = rows_layout.itemAt(i).widget()
label.setFixedHeight(table_layout.cellRect(i, 0).height())
for i in range(columns_layout.count()):
label = columns_layout.itemAt(i).widget()
label.setFixedWidth(table_layout.cellRect(0, i).width())
...
Use QScrollArea.setWidgetResizable(True) so the scroll area can resize the widget as needed, and allow the header labels to take up the required amount of space to be aligned with the table.
...
self.columns_scroll_widget.setWidgetResizable(True)
self.rows_scroll_widget.setWidgetResizable(True)
self.table_scroll_widget.setWidgetResizable(True)
# Size policies to keep the row and column headers minimal/sufficient
# and allow table_scroll_widget to get as much space as possible when resized
self.columns_scroll_widget.setSizePolicy(PyQt5.QtWidgets.QSizePolicy.Preferred, PyQt5.QtWidgets.QSizePolicy.Minimum)
self.rows_scroll_widget.setSizePolicy(PyQt5.QtWidgets.QSizePolicy.Minimum, PyQt5.QtWidgets.QSizePolicy.Preferred)
...
QScrollBar inherits from QAbstractSlider so you can connect its valueChanged signal to the setValue slot of another scroll bar. These values will be consistent across the layouts now since their row heights and column widths are equal.
...
# User scrolling in table will cause headers to scroll
self.table_scroll_widget.horizontalScrollBar().valueChanged[int].connect(self.columns_scroll_widget.horizontalScrollBar().setValue)
self.table_scroll_widget.verticalScrollBar().valueChanged[int].connect(self.rows_scroll_widget.verticalScrollBar().setValue)
# User scrolling on headers will cause table to scroll
self.columns_scroll_widget.horizontalScrollBar().valueChanged[int].connect(self.table_scroll_widget.horizontalScrollBar().setValue)
self.rows_scroll_widget.verticalScrollBar().valueChanged[int].connect(self.table_scroll_widget.verticalScrollBar().setValue)
central_layout = PyQt5.QtWidgets.QGridLayout()
central_layout.addWidget(self.columns_scroll_widget, 0, 1, 1, 5)
central_layout.addWidget( self.rows_scroll_widget, 1, 0, 5, 1)
central_layout.addWidget( self.table_scroll_widget, 1, 1, 5, 5)
central_widget = PyQt5.QtWidgets.QWidget()
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
self.show()
Outcome:
Here the code I wrote.
I took the data from pandas DF (not pasted here).
The x values are from DF index columns that is a DateTime column.
The issue that I want to resolve is in line:
TOOLTIPS = [("index", "$index"),("(Time,Temperature)", "($x, $y)"),]
when I have to change the $x format to a correct format in order to see the the time format in the hover window on the bokeh plot.
see the python code
import datetime as dt
from bokeh.plotting import figure, output_file, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter
from bokeh.models import DatetimeTickFormatter
x=df_bases.index
y0=df_bases["base_1"]
y1=df_bases["base_5"]
y2=df_bases["base_12"]
# output to static HTML file
output_file("temperatures from thermocouples.html")
# add some renderers
output_file("Thermocouples temperature.html", title="Thermocouples temperature")
TOOLTIPS = [("index", "$index"),("(Time,Temperature)", "($x, $y)"),]
# create a new plot with a datetime axis type
p = figure( tooltips=TOOLTIPS , plot_width=1250, plot_height=580, x_axis_type="datetime", x_axis_label='Time',
y_axis_label='Temperature [°C]', title="Thermocouples temperature")
p.line(x, y0, legend="thermocouple 1", line_width=1 , color='navy', alpha=1)
p.line(x, y1, legend="thermocouple 5", color="green")
p.line(x, y2, legend="thermocouple 12", line_width=1 , color='orange', alpha=1)#, line_dash="4 4")
p.border_fill_color = "whitesmoke"
p.xaxis.formatter=DatetimeTickFormatter(
microseconds = ['%Y-%m-%d %H:%M:%S.%f'],
milliseconds = ['%Y-%m-%d %H:%M:%S.%3N'],
seconds = ["%Y-%m-%d %H:%M:%S"],
minsec = ["%Y-%m-%d %H:%M:%S"],
minutes = ["%Y-%m-%d %H:%M:%S"],
hourmin = ["%Y-%m-%d %H:%M:%S"],
hours=["%Y-%m-%d %H:%M:%S"],
days=["%Y-%m-%d %H:%M:%S"],
months=["%Y-%m-%d %H:%M:%S"],
years=["%Y-%m-%d %H:%M:%S"],
)
p.title.align = 'center'
# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1, y2=y2))
# create a view of the source for one plot to use
view = CDSView(source=source)
# show the results
show(p)
Currently (as of Bokeh 1.2) the hover tool does not have any "always on" mode It only hovers in response to hit-testing glyphs that are added to the plot. Additionally there is no way to apply formatting to "special vars" like $x (that will be possible starting in Bokeh 2.0). Custom formatters can only be applied to hover tooltips for data columns. Given that, my best suggestion is to switch to using #xinstead (which interrogates the "x" data column, not the x mouse position". If you do that, you can use all the techniques in the Formatting Tooltip Fields section of the docs.
Since you did not provide a complete example (no data to run), I can only provide partial untested suggestions:
# use #x{%F} to specify the %F datetime format (or choose another) for the x column
TOOLTIPS = [("index", "$index"),("(Time,Temperature)", "(#x{%F}, $y)")]
# tell bokeh to use the "datetime" formatter for the x column
p.hover.formatters = {'x': 'datetime'}
# just a suggestion, often useful for timeseries plots
p.hover.mode = 'vline'
I first render the figure in notebook, then I save it in svg form. The figure display in notebook is correct, but the saved svg is missing some markers.
#!/usr/bin/python3
import pandas as pd
import numpy as np
import math
from bokeh.plotting import figure, show, ColumnDataSource, save, output_file, reset_output
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from itertools import cycle
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
markers = ['circle', 'square', 'triangle', 'asterisk', 'circle_x', 'square_x', 'inverted_triangle', 'x', 'circle_cross', 'square_cross', 'diamond', 'cross']
pool = cycle(markers)
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
plot_row_col_glyph = p.scatter(x_data, y_data, color=(int(255*color_r),int(255*color_g),int(255*color_b)), size=10, marker=next(pool))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line, plot_row_col_glyph]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
output_notebook()
show(grid_row_col)
p.output_backend = "svg"
export_svgs(grid_row_col, filename="%s/"%'.' + "_" +"stackoverflow.svg")
Here is what I see in notebook, which is what I expected:
And here is what I see when opening '_stackoverflow.svg'
The legend color for 'female 25' and 'male 20' is partially black. (the marker part) and 'female 20' is missing both marker and its legend.
The reason you are seeing different colors in the notebook and the exported image is that they use two different backends. You set the backend to SVG just before exporting it in the last line. Therefore, though the exported image uses SVG backend, the image on the notebook uses the default backend, which is canvas.
The backend can be set with two options: one you used and one where you add output_backend="svg" argument when you call figure. That is, replacing
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
with
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension), output_backend="svg").
Now you will see the same issue you see in the exported image on you notebook image. It seems like this is a bug in the SVG backend, and most likely they are working on that. Unfortunately there is no expected date for the bug fix.