I use SXSSFWorkbook() to create my workbook. I have a requirement to merge a few cells across rows/columns and also give it a border.
My problem is: merging of cells with out applying a border works perfectly fine, However, the moment I try to merge the cells and apply border application runs into an Exception.
ERROR view - java.lang.IllegalArgumentException: Attempting to write a row[2] in the range [0,204] that is already written to disk.
java.lang.IllegalArgumentException: Attempting to write a row[2] in the range [0,204] that is already written to disk.
at org.apache.poi.xssf.streaming.SXSSFSheet.createRow(SXSSFSheet.java:113)
at org.apache.poi.ss.util.CellUtil.getRow(CellUtil.java:90)
at org.apache.poi.ss.util.RegionUtil.setRightBorderColor(RegionUtil.java:139)
My Code:
CellRangeAddress rangeAdd = new CellRangeAddress(2, 41, 0, 0);
sheet.addMergedRegion(rangeAdd);
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), rangeAdd, sheet, mWb);
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), rangeAdd, sheet, mWb);
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), rangeAdd, sheet, mWb);
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), rangeAdd, sheet, mWb);
RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, rangeAdd, sheet, mWb);
RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, rangeAdd, sheet, mWb);
RegionUtil.setBorderRight(CellStyle.BORDER_THIN, rangeAdd, sheet, mWb);
RegionUtil.setBorderTop(CellStyle.BORDER_THIN, rangeAdd, sheet, mWb);
Any help/pointers around this would be great.
Related
I have a column whose content is conditioned upon another column:
=IF(B8<>"";B8/($J$2 * $J$2);"")
This column is the line data for a chart. It works fine when the IF condition is met. However, any ELSE "" value is considered as 0 in the chart and the line crashes down to the bottom.
The alternative is to use
=IF(B8<>"";B8/($J$2 * $J$2);NA())
But then it looks ugly in the spreadsheet with loads of #N/A in the column.
Any solution aside from putting a conditional formatting on top which makes the font color white when the value is #N/A?
You can plot with a Named Range which will evaluate 0 to #NA
If the data you're plotting is in C3:C8:
Create a Name for that Range which evaluates 0 as #NA (I've called the Range "PLOT", but you can give it any name you want): =IF(Sheet1!$C$3:$C$8=0,NA(),Sheet1!$C$3:$C$8)
Use the Named range in the Chart: =SERIES(,,Book1!PLOT,1)
This will allow you to keep the ELSE-cells as "" in your sheet, whilst still showing up as #NA to the Chart and hence not collapsing the line to zero
NB: See here for notes on how to use Named Ranges in charts, particularly:
If your references are Names (Named Ranges), you need to qualify the Name with the scope of the Name, that is, either its parent worksheet or the parent workbook.
=SERIES(Sheet1!TheSeriesName,Sheet1!TheXValues,Sheet1!TheYValues,1)
You can enter the Name qualified by the worksheet, and if the Name is scoped to the workbook, Excel will fix it for you.
I am having an issue with openpxyl deleting a row and not moving the merged cells up. For example, in the first picture, I have two merged cells with values Alex & Bob. When I delete row 2, Alex gets moved up to a single cell, Bob gets deleted, and the position of the merged cells stay in the same spot while the remainder of the data points get moved up. When normally working with excel outside of Python, the merged cells would simply move up with the rest of the data. It appears that openpxyl wants to move up the data values but keep the position of the merged cells the same. What is the work around for this? Thank you in advance!!
Before deleting row 2:
ws.delete_rows(2)
When I delete row 2 the following happens:
How it should look like if you were deleting row two manually in excel:
I was facing a similar issue when removing columns, merged cells that intersect such a column ended up being unchanged.
This works in a more satisfactory manner.
The code provides the delete_row() function, which can be used to imitate Excel behavior, and move merged cells accordingly:
import openpyxl
from openpyxl.utils import range_boundaries
from openpyxl.utils.cell import _get_column_letter
from openpyxl.worksheet.cell_range import CellRange
def delete_row(target_row):
# Assuming that the workbook from the example is the first worksheet in a file called "in.xlsx"
workbook = openpyxl.load_workbook('in.xlsx')
ws = workbook.worksheets[0]
affected_cells = [] # array for storing merged cells that need to be moved
row = target_row # specify the row that we want to delete
sheet_boundary = [4,6] # specify how far to search for merged cells in the sheet in the format of [ max_col, max_row ]
## Define a range of cells that are below the deleted row
# top left corner of the range; will be A2
tl_corner = "A"+str(row)
# bottom right corner of the row; will be D6
br_corner = _get_column_letter(sheet_boundary[0]) + str(sheet_boundary[1])
target_row_range_string = tl_corner+":"+br_corner
# express all cells in the row that is to be deleted as object CellRange from openpyxl
target_range = CellRange(range_string=target_row_range_string)
# loop over all merged cells in the sheet
for merged_cell_range in ws.merged_cells.ranges:
# if merged_cell is within target_range add current merged cell to 'affected_cells'
if merged_cell_range.issubset(target_range):
print("Cell '"+str(merged_cell_range)+"' is within range of '"+str(target_range)+"'")
affected_cells.append(merged_cell_range)
# unmerge all affected cells
for cell in affected_cells:
# get a tuple of coordinates, instead of Xlsx notation
cell_range = range_boundaries(cell.coord)
print("'"+str(cell)+"' ---> '"+str(cell_range)+"'")
# unmerge the affected cell
ws.unmerge_cells(start_column = cell_range[0], start_row = cell_range[1],
end_column = cell_range[2], end_row = cell_range[3])
# perform row deletion as usual
ws.delete_rows(row)
# merged all affected cells
for cell in affected_cells:
# get a tuple of coordinates, instead of Xlsx notation
cell_range = range_boundaries(cell.coord)
# merge back the affected cell, while lifting it up by one row
ws.merge_cells(start_column = cell_range[0], start_row = cell_range[1]-1,
end_column = cell_range[2], end_row = cell_range[3]-1)
# save the edited workbook
workbook.save('out.xlsx')
# call our custom function, specifying the row you want to delete
delete_row(2)
This finds all the merged cells that intersect the target_range, a range that starts with the deleted row and ends with the range defined by sheet_boundary and first unmerges them.
Only merged cells that are fully within target_range are changed.
If a part of the merged cell is within the target_range, then no operations are performed on that cell.
Then it deletes the desired row, and merges back all the affected cells, while taking into account that they have been moved up one row.
I found a temporary but unsatisfactory fix.
If I use the following code:
for merged_cell in ws.merged_cell.ranges:
merged_cell.shift(0,-1)
ws.delete_rows(2)
This will fix my problem and move the merged cells up. However, the one issue I have with this code is that it moves up ALL merged cells in the file. If I want to only moved the merged cells up in column A, I am not sure how to reduce the list of ranges to only include those in a.
For example, the following code doesn't work but highlights what I am trying to accomplish with specificity:
ws['A'].merged_cells.ranges
I am working on excel data, with in a specific range(B16:K400) there are few cells that have blue color back ground. I need to take out those value and save them to a variable.
I have tried and succeeded getting values from individual cells.
excel = actxserver('Excel.Application');
excel.Visible = true;
workbook = excel.Workbooks.Open(filename);
rgb = workbook.Worksheets.Item(sheetname).Range(cell).Interior.Color;
but, I can work one cell at a time. by doing for-loop I can do for a given range of cell.
I am interested to collect the cell Names having same color background(let it be red colour) in one step.
It would be great if some one can suggest me, Thank you in advance
This is a question for Excel. I have to create similar tables in several worksheets. But the tables are not matched up in terms of the row numbers. For example, table in worksheet one starts on row 48; table in worksheet two starts on row 35, etc. Each table refers to a cell that is 3 rows above the table starts and needs to be fixed for the length of the table. So, in table in worksheet one, the formula needs to refer to cell M45 (three rows above 48); in table in worksheet two, the formula needs to refer to cell M32 (three rows above 35); and so on. If it were just one worksheet, I know that I could fix 32 and refer to M$45. But I do not know how to automate it for the problem described above. The formula I need to automate is:
IF(SUM($L49:$L$61)>=M$45,0,MIN($L48,M$45)).
M$45 needs to be M$32 in worksheet two when I copy the formula over from worksheet one to worksheet two. The other references will also need to be modified accordingly. But once I know how to do one, I can take care of the rest.
Would appreciate any help you can provide.
If you use the following inside the table,
=OFFSET([#Headers], -3, 0, 1, 1)
The formula will adjust to show the table name as in,
=OFFSET(Table1[#Headers], -3, 0, 1, 1)
Copying the table will adjust the internal formula to the new table's name. e.g.
=OFFSET(Table2[#Headers], -3, 0, 1, 1)
I have a client that has a simple yet complicated request for an excel sheet setup, and I can't for the world thing of where to start. I'm drawing a blank.
We have a data range. Example:
Quarter Data
2010Q1 1
2010Q2 3
2010Q3 4
2010Q4 1
I have a chart built on top of that. Change data, chart changes, protect worksheet to keep other idi... er... users from changing old data. Simple.
What I want to have happen: When I add the next Q1 below Q4, the chart "automagically" selects the most recent 4Q. So when I update the data to:
Quarter Data
2010Q1 1
2010Q2 3
2010Q3 4
2010Q4 1
2011Q1 7
The chart will show data for the last 4 quarters (2010Q2 thru 2011Q1). The goal being: keep "old" data on the same sheet, but have the charts update to most recent quarters.
I'm thinking: "fixed" data locations, reverse the data (new data at top), and just insert row each new quarter:
Quarter Data
2011Q2 9
2011Q1 7
2010Q4 1
2010Q3 4
2010Q2 3
2010Q1 1
But this will involve a lot of changes to the already existing excel sheets and I was hoping that there may be an easier/better "fix".
EDIT:
#Lance Roberts ~ Running with your suggestion:
- Little more detail... The data is setup such that the column information is in A, but data for multiple tables are in B+. Table 1 is B/C. Table2 is D/E. Etc.
- Data is also on a different sheet than the tables.
Going by: This Offset Description, what I've tried doing is adjusting similar to such:
NAME FORMULA OFFSET(range, rows, columns, height, width )
DATA0 =OFFSET('DATASHEET'!$A$2, COUNTA('DATASHEET'!$A:$A - 8, 0, 8, 1)
DATA1 =OFFSET('DATASHEET'!$A$2, COUNTA('DATASHEET'!$A:$A - 8, 1, 8, 1)
DATA2 =OFFSET('DATASHEET'!$A$2, COUNTA('DATASHEET'!$A:$A - 8, 2, 8, 1)
Goal being to tie the length/location for B/C/etc data to A. So if I add a column on A, stuff tied to Data1/2 adjust accordingly (or 3/4/5/etc, which are different charts on different sheets
)
I want data cells to be picked by the first row, and then an offset number to get data x columns over. Variations on the formula don't seem to be working.
1 issue I haven't solved yet: the data is not aligning properly:
"Data" is always, last column under 2nd to last Quarter. Last quarter is always empty. Data is shifting to the right (in this example, under 3Q10 - NOT under the correct column. 11 should be under 4Q10. 9.5 should be under 2Q10).
I know I'm getting something simple wrong...
Seems to be working. First thing I had to change was CountA - 9 (not CountA - 8). Next was the "column offset" (0, 1, 2, 3,...). Also split some stuff up to make it more compartmentalized (I do have to train someone else how to do this for her reporting needs).
Thanks Lance :)
If the chart is on the same sheet as the data:
Name the first cell of the data (A2) as a named range, say TESTRANGE.
Created a named range MYDATA as the following formula:
=OFFSET(TESTRANGE, COUNTA($A:$A) - 5, 0, 4, 2)
Now, go to the SERIES tab of the chart SOURCE DATA dialog, and change your VALUES statement to:
=Sheet1!MYDATA
Now everytime you add a new row, it'll change the chart.
I know this is an old question, but I wanted to share an alternative that may be easier.
Change your Quarter-Data range to an Excel Table. Select the range, and press Ctrl+T. In the Insert Table, make sure the correct data range is selected, and that My Table Has Headers is checked, and press OK. This converts the simple range into a special data structure with magical properties.
Then make a new range which links to the last four rows of this table, and create a chart based on this new range. This is illustrated below. The table is the specially formatted range in A1:B9 (you can choose a less in-your-face style), and the plotting range is D1:E5.
The formulas in cells D2 through D5 are below. Copy D2:D5 and paste into E2:E5 to complete the formulas in our plotting range.
D2: =INDEX(Table1[Quarter],ROWS(Table1[Quarter])-3)
D3: =INDEX(Table1[Quarter],ROWS(Table1[Quarter])-2)
D4: =INDEX(Table1[Quarter],ROWS(Table1[Quarter])-1)
D5: =INDEX(Table1[Quarter],ROWS(Table1[Quarter]))
Table1 is the name assigned to the Table, and Quarter is the name of the first column of the Table (and also the column header). You don't need to type all this, just select the column in the Table. As the Table expands or contracts, Table1[Quarter] keeps track of the changes.
Now add a new data point. The Table expands, and our little staging area in D1:E5 links to the new last four rows of the table.
And as we add years worth of data, the formulas and the chart keep up.