How to Create/Add multiple charts in ChartSheet using openpyxl? - python-3.x

There is option in excel sheet which allows user to export charts to chartsheet. Manually one can add any number of charts but while using openpyxl module I could only add one chart, when I try to add more than one chart its not showing up in the chartsheet, only thing i can see is my old graph which I just added at the beginning.
here is the sample code I have used.
import openpyxl as op
wb = op.load_workbook('input_excel.xlsx')
ws= wb.get_sheet_by_name('RawData')
chart_sheet_1=wb.create_chartsheet('for_graphs')
chart_1 = op.chart.ScatterChart()
chart_2 = op.chart.ScatterChart()
data_set_1 = op.chart.Reference(ws,1,3,1,51)
data_set_2 = op.chart.Reference(ws,2,3,2,51)
data_set_3 = op.chart.Reference(ws,3,3,3,51)
series_graphs_1 = op.chart.Series(data_set_3,data_set_1)
series_graphs_2 = op.chart.Series(data_set_3,data_set_2)
chart_1.series.append(series_graphs_1)
chart_2.series.append(series_graphs_2)
chart_sheet_1.add_chart(chart_1)
chart_sheet_1.add_chart(chart_2)
As shown above i am adding two charts in shown fashion is that wrong or is there any other ways to add two or more charts in one chartsheet using openpyxl or any other modules??????????. Thanks in advance.

I was just looking for some openpyxl solutions, their site lacks a lot of information if you compare it against xlswriter. The problem with xlswriter is that it can't open excel files, just create new ones.
And even that this question is old, I would like to answer it in case anybody else comes looking for answers.
This works for python 3 and openpyxl 3.0.9 (latest version as today)
For openpyxl you can add multiple charts into a single sheet, it has a trick: you need to first write the first chart, and comment the rest of the charts, afterwards, you can uncomment them, and it will no longer produce errors or warnings... perhaps a bug?...
Leaving some simple code in here, remember to comment and uncomment when you have the initial file.
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, Color, colors, PatternFill
from openpyxl.chart import ScatterChart, Reference, Series
from numpy import random
import numpy as np
data0 = random.randint(100, size=(100)) # Some random vectors
data1 = random.randint(100, size=(100))
data2 = random.randint(100, size=(100))
data3 = random.randint(100, size=(100))
filename = 'your_file.xlsx'
wb = Workbook()
ws = wb.worksheets[0]
ws['A1'].font = Font(bold = True)
ws.cell(row = 1, column = 1).value = 'Title goes here'
yellowFill = PatternFill(start_color = 'FFFF00', end_color = 'FFFF00', fill_type = 'solid')
redFill = PatternFill(start_color = 'FF0000', end_color = 'FF0000', fill_type = 'solid')
ws['A4'].font = Font(bold = True)
ws['A4'].fill = yellowFill
ws.cell(row = 4, column = 1).value = 'x'
ws.cell(row = 4, column = 2).value = 'Rnd_1'
ws['B4'].font = Font(color = 'FFFFFF', bold = True)
ws['B4'].fill = redFill
ws.cell(row = 4, column = 3).value = 'Rnd_2'
ws['C4'].font = Font(color = 'FFFFFF', bold = True)
ws['C4'].fill = redFill
ws.cell(row = 4, column = 4).value = 'Rnd_3'
ws['D4'].font = Font(color = 'FFFFFF', bold = True)
ws['D4'].fill = redFill
ws.cell(row = 4, column = 5).value = 'Rnd_4'
ws['E4'].font = Font(color = 'FFFFFF', bold = True)
ws['E4'].fill = redFill
row_i = 5
for i in range(99):
ws.cell(row = row_i, column = 1).value = i
ws.cell(row = row_i, column = 2).value = data0[i]
ws.cell(row = row_i, column = 3).value = data1[i]
ws.cell(row = row_i, column = 4).value = data2[i]
ws.cell(row = row_i, column = 5).value = data3[i]
row_i += 1
xvalues = Reference(ws, min_col = 1, min_row = 5, max_row = row_i) # just using the same x axis for all the rest of the charts
chart_1 = ScatterChart()
chart_1.y_axis.title = 'y value_1'
yvalues_1 = Reference(ws, min_col = 2, min_row = 5, max_row = row_i)
series_1 = Series(yvalues_1, xvalues, title = 'Data 1')
chart_1.series.append(series_1)
ws.add_chart(chart_1, 'H1')
#comment the following, and uncomment the second time
chart_2 = ScatterChart()
chart_2.y_axis.title = 'y value_2'
yvalues_2 = Reference(ws, min_col = 3, min_row = 5, max_row = row_i)
series_2 = Series(yvalues_2, xvalues, title = 'Data 2')
chart_2.series.append(series_2)
ws.add_chart(chart_2, 'H17')
chart_3 = ScatterChart()
chart_3.y_axis.title = 'y value_3'
yvalues_3 = Reference(ws, min_col = 4, min_row = 5, max_row = row_i)
series_3 = Series(yvalues_3, xvalues, title = 'Data 3')
chart_3.series.append(series_3)
ws.add_chart(chart_3, 'R1')
chart_4 = ScatterChart()
chart_4.y_axis.title = 'y value_4'
yvalues_4 = Reference(ws, min_col = 4, min_row = 5, max_row = row_i)
series_4 = Series(yvalues_4, xvalues, title = 'Data 4')
chart_4.series.append(series_4)
ws.add_chart(chart_4, 'R17')
# comment up to here
wb.save(filename)

Related

Python3 Openpyxl patternfill not detecting multiple colors

I am trying to add 2 different colors to my cell rows using openpyxl engine for my xlsm file having macros based on the positive or negative values. But I end up having the most used color for all the cells.
Iam using openpyxl==3.0.6
Here is my code.
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.styles import Protection,PatternFill
wb = load_workbook(path,keep_vba=True)
ws = wb['Sheet1']
CheckColor = [-1,2,5,-5,5,-9,10,11,-1,-8,1,5,3]
yellow = PatternFill(patternType='solid',fgColor='FFF2CC')
pink = PatternFill(patternType='solid',fgColor='FF9999')
try:
for row in range(30,43):
for color in CheckColor:
if color > 0:
print("printing yellow color")
ws.cell(row = row, column = 2).value = 10
ws.cell(row = row, column = 2).fill = yellow
ws.cell(row = row, column = 3).value = 30
ws.cell(row = row, column = 3).fill = yellow
ws.cell(row = row, column = 4).value = 40
ws.cell(row = row, column = 4).fill = yellow
else:
print("printing pink")
ws.cell(row = row, column = 2).value = 10
ws.cell(row = row, column = 2).fill = pink
ws.cell(row = row, column = 3).value = 30
ws.cell(row = row, column = 3).fill = pink
ws.cell(row = row, column = 4).value = 40
ws.cell(row = row, column = 4).fill = pink
wb.save(path)
except Exception as e:
print(e)
As #Warcupine states, you select the first row (30) then create a new loop
for color in CheckColor:
which loops through the values in the CheckColor list for all of row 30. In other words you set all 13 colour options to row 30 ending with the last colour 3. Then move on to the next row (31) and do the same thing again. Thus all rows end up with the last colour in the list, 3 which being greater than 0 fills yellow.
You want to loop the rows and select the next value in the list for each. There is a couple of ways to do this, using enum allows you a count from 0 - 12 that can be used to set the colour index.
Code extract
...
CheckColor = [-1, 2, 5, -5, 5, -9, 10, 11, -1, -8, 1, 5, 3]
yellow = PatternFill(patternType='solid', fgColor='FFF2CC')
pink = PatternFill(patternType='solid', fgColor='FF9999')
try:
for enum, row in enumerate(range(30, 43)):
color = CheckColor[enum] # For each loop of the rows enum increments and sets color to the next CheckColor index
if color > 0:
print("printing yellow color")
ws.cell(row=row, column=2).value = 10
ws.cell(row=row, column=2).fill = yellow
ws.cell(row=row, column=3).value = 30
ws.cell(row=row, column=3).fill = yellow
...

Make combo boxes exclusive/precise in generating data

I have a userform with 7 combo boxes that are used to search data from the worksheet. I intend to present column 6(mass) and 8(index) as ranges i.e. for mass: 0.007-0.1; 0.11-2.5; 0.251-0.5 etc. The other 5 combo boxes are just absolute values (not range).I'm attempting to loop through the cells in the data sheet(shD) and whenever the row matches matches all selections made on the userform; then the entire row is copied to the results sheet(shR). The user may leave some of the combo boxes blank, but they should still be able to get results. What the code is doing now is may be say in the time combobox(cbInj) I selected say 15 seconds; the code will include 20 seconds which does not match the 15sec on the combo box. Here is my code;
'combo boxes variable definition, in order to compact and make the code easy to be understood:
Set cbPr = User_search.Cbx_Project_code
Set cbTr = User_search.Cbx_TrueNOC
Set cbDn = User_search.Cbx_DNAmass
Set cbK = User_search.Cbx_Kit
Set cbQ = User_search.Cbx_QIndex
Set cbInj = User_search.Cbx_Injection_time
Set cbInstr = User_search.Cbx_Instrument
'Check selection for mass and present it as a range
If Len(cbDn.Value) > 0 Then
arrDn = Split(cbDn.Value, "-")
mnDn = CDbl(arrDn(0))
mxDn = CDbl(arrDn(1))
End If
'checkfor Index if selected and present it as a range
If Len(cbQ.Value) > 0 Then
arrQ = Split(cbQ.Value, "-")
mnQ = CVar(arrQ(0))
mxQ = CVar(arrQ(1))
End If
'count the total rows on Data
totD = shD.Range("B" & Rows.Count).End(xlUp).Row 'last row of "Data" sheet
For i = 5 To totD
vDn = shD.Cells(i, 6).Value
vQ = shD.Cells(i, 8).Value
If (Trim(shD.Cells(i, 2)) = Trim(cbPr.Value) Or cbPr.Value = "") And _
(Trim(shD.Cells(i, 5)) = Trim(cbTr.Value) Or cbTr.Value = "") And _
vDn > mnDn And vDn <= mxDn Or cbDn.Value = "" And _
(Trim(shD.Cells(i, 7)) = Trim(cbK.Value) Or cbK.Value = "") And _
vQ > mnQ And vQ <= mxQ Or cbQ.Value = "" And _
(Trim(shD.Cells(i, 9)) = Trim(cbInj.Value) Or cbInj.Value = "") And _
(Trim(shD.Cells(i, 10)) = Trim(cbInstr.Value) Or cbInstr.Value = "") Then
totR = shR.Cells(Rows.Count, 1).End(xlUp).Row
shD.Rows(i).EntireRow.Copy Destination:=shR.Cells(totR + 1, 1)
End If
Next i
Slight logic problem in your "range" tests - eg:
vQ > mnQ And vQ <= mxQ Or cbQ.Value = "" And _
should be
((vQ > mnQ And vQ <= mxQ) Or cbQ.Value = "") And _
I would do something like this. Individual tests are faster, since there's no need to continue checking after any failed test
Sub Tester()
Dim cbPr, cbTr, cbDn, cbk, cbQ, cbInj, cbInstr 'all variants
Dim rw As Range, isMatch As Boolean, arrCrit
'get combo boxes values
cbPr = Trim(User_search.Cbx_Project_code.Value)
cbTr = Trim(User_search.Cbx_TrueNOC.Value)
cbDn = Trim(User_search.Cbx_DNAmass.Value)
If Len(cbDn) > 0 Then cbDn = Split(cbDn, "-") 'convert to array
cbk = Trim(User_search.Cbx_Kit.Value)
cbQ = Trim(User_search.Cbx_QIndex.Value)
If Len(cbQ) > 0 Then cbDn = Split(cbQ, "-") 'convert to array
cbInj = Trim(User_search.Cbx_Injection_time.Value)
cbInstr = Trim(User_search.Cbx_Instrument.Value)
arrCrit = Array(2, cbPr, 5, cbTr, 6, cbDn, 7, cbk, 8, cbQ, 9, cbInj, 10, cbInstr)
For i = 5 To shD.Range("B" & Rows.Count).End(xlUp).Row
Set rw = shD.Rows(i)
For n = LBound(arrCrit) To UBound(arrCrit) - 1 Step 2
isMatch = CellIsMatch(rw.Cells(arrCrit(n)), arrCrit(n + 1))
If Not isMatch Then Exit For
Next n
If isMatch Then rw.Copy shR.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
Next i
End Sub
'Does a cell value match the supplied criteria?
' Criteria could be a range of two numeric values
Function CellIsMatch(cell As Range, crit) As Boolean
Dim v
v = cell.Value
If Len(v) > 0 Then
'is the criteria an array (range) ?
If TypeName(crit) Like "*()" Then
'assumes v is numeric
CellIsMatch = (v > CDbl(Trim(crit(0))) And _
v < CDbl(Trim(crit(1))))
Else
CellIsMatch = (Trim(v) = crit) Or Len(crit) = 0
End If
End If
End Function

For loop without looping if statement

Here is my code:
import openpyxl
wb = openpyxl.load_workbook("Bok1.xlsx", data_only=True)
ws = wb["Blad1"]
n = 0
for row in ws['A1:A100']:
for cell in row:
if cell.value == "Konto":
for hej in range(13):
n+=1
konto = cell.offset(row=n).value
if konto == None or isinstance(konto, str) == True:
pass
else:
if konto == 306888 or konto == 306889:
#derp = input("derpderpderp?: ")
#if derp == "y":
if konto == 306888 or konto == 306889:
kst = cell.offset(row=n, column = 1).value
proj = cell.offset(row=n, column = 2).value
vht = cell.offset(row=n, column = 3).value
motp = cell.offset(row=n, column = 4).value
fin = cell.offset(row=n, column = 5).value
text = cell.offset(row=n, column = 8).value
belopp = cell.offset(row=n, column = 9).value
print(konto)
print(kst)
print(proj)
print(vht)
print(motp)
As you can see by the # in the code, I have a input that is "dependent" on a loop. How could I write this code so that it is not looping over and asking for the input for every iteration?
What I would do is to keep a counter:
n = 0
cnt=1 # counter
for row in ws['A1:A100']:
# continue with your code
Then, when you get to your input :
if cnt==1:
derp = input("derpderpderp?: ")
cnt += 1
if derp == "y":
#continue with your code
This way, you ensure that your input is taken only on the first go.

Changing barchart's bar size using Epplus in vb

I am completely new to EPPlus. I have following code that generates the barchart where bars size changes according to no of rows gets. if only one data row provided it creates one big bar. if I more numbers of data row provided the smaller bar becomes. How do I fix this as single size?
Dim barChart1 As OfficeOpenXml.Drawing.Chart.ExcelBarChart = worksheet5.Drawings.AddChart("BarChart1", OfficeOpenXml.Drawing.Chart.eChartType.ColumnStacked)
barChart1.Style = OfficeOpenXml.Drawing.Chart.eChartStyle.Style27
Dim startColConsumable As String = "D"
Dim startRangeCol As String = "B"
Dim startColIndex As Integer = 10
Dim endColIndex As Integer = totalRow - 1
Dim endRangeCol As String = "C"
Dim startColUncommitted As String = "I"
Dim consumableSeries As OfficeOpenXml.Drawing.Chart.ExcelBarChartSerie = barChart1.Series.Add(worksheet.Cells(String.Concat(startColConsumable, startColIndex.ToString(), ":", startColConsumable, endColIndex.ToString())), worksheet.Cells(String.Concat(startRangeCol, startColIndex.ToString(), ":", endRangeCol, endColIndex.ToString())))
Dim uncommittedSeries As OfficeOpenXml.Drawing.Chart.ExcelBarChartSerie = barChart1.Series.Add(worksheet.Cells(String.Concat(startColUncommitted, startColIndex.ToString(), ":", startColUncommitted, endColIndex.ToString())), worksheet.Cells(String.Concat(startRangeCol, startColIndex.ToString(), ":", endRangeCol, endColIndex.ToString())))
consumableSeries.Header = "Current FY V0 Consumable"
consumableSeries.Fill.Color = System.Drawing.Color.FromArgb(204, 51, 0)
consumableSeries.DataLabel.ShowValue = True
'consumableSeries.DataLabel.ShowLeaderLines = True
uncommittedSeries.Header = "Current FY V0 Uncommitted"
uncommittedSeries.Fill.Color = System.Drawing.Color.FromArgb(51, 153, 255)
uncommittedSeries.DataLabel.ShowValue = True
'uncommittedSeries.DataLabel.ShowLeaderLines = True
barChart1.Title.Text = "FY 2018 Consumable vs Uncommitted"
barChart1.SetPosition(4, 4, 4, 4)
barChart1.XAxis.Title.Text = "Func Area 5 and Fund Center"
barChart1.YAxis.Title.Text = "Amount"
barChart1.XAxis.Title.Font.Size = "15"
barChart1.YAxis.Title.Font.Size = "15"
barChart1.DataLabel.ShowPercent = True
barChart1.SetSize(1540, 880) // fixed chart size
barChart1.DataLabel.ShowValue = True
barChart1.DataLabel.ShowLeaderLines = True
barChart1.DataLabel.Separator = ";"
barChart1.GapWidth = 40 'space between adjacent bars
End If
Thanks In advance.
I tried changing the plot area: That helped me.
If worksheet.Cells(String.Concat(startColConsumable, startColIndex.ToString(), ":", startColConsumable, endColIndex.ToString())).Count = 1 Then
barChart1.SetSize(500, 880)
ElseIf worksheet.Cells(String.Concat(startColConsumable, startColIndex.ToString(), ":", startColConsumable, endColIndex.ToString())).Count = 2 Then
barChart1.SetSize(700, 880)
end if

Add columns to csv using python prompt for data

I found this code to split a CSV file using python.
I need to split 3,000,000 record CSV file when the column A changes.
I also need to add 2 more fields to the table
Blank (add a comma next to each line).
Add a date in the last field, but it should ask me for the date.
Would someone be able to help me add 2 thing to this code.
A prompt to add more fields
A prompt what should be in the field
I am copying the code from the link included earlier
#!/usr/bin/env python3
import binascii
import csv
import os.path
import sys
from tkinter.filedialog import askopenfilename, askdirectory
from tkinter.simpledialog import askinteger
def split_csv_file(f, dst_dir, keyfunc):
csv_reader = csv.reader(f)
csv_writers = {}
for row in csv_reader:
k = keyfunc(row)
if k not in csv_writers:
csv_writers[k] = csv.writer(open(os.path.join(dst_dir, k),
mode='w', newline=''))
csv_writers[k].writerow(row)
def get_args_from_cli():
input_filename = sys.argv[1]
column = int(sys.argv[2])
dst_dir = sys.argv[3]
return (input_filename, column, dst_dir)
def get_args_from_gui():
input_filename = askopenfilename(
filetypes=(('CSV', '.csv'),),
title='Select CSV Input File')
column = askinteger('Choose Table Column', 'Table column')
dst_dir = askdirectory(title='Select Destination Directory')
return (input_filename, column, dst_dir)
if __name__ == '__main__':
if len(sys.argv) == 1:
input_filename, column, dst_dir = get_args_from_gui()
elif len(sys.argv) == 4:
input_filename, column, dst_dir = get_args_from_cli()
else:
raise Exception("Invalid number of arguments")
with open(input_filename, mode='r', newline='') as f:
split_csv_file(f, dst_dir, lambda r: r[column-1]+'.csv')
# if the column has funky values resulting in invalid filenames
# replace the line from above with:
# split_csv_file(f, dst_dir, lambda r: binascii.b2a_hex(r[column-1].encode('utf-8')).decode('utf-8')+'.csv')
Thank you
had it written in VBS
basename = "csv_split_"
hasHeader = True 'Change to False if there is no header.
argCnt = WScript.Arguments.Count
If argCnt < 1 Then
WScript.Echo "Drag a CSV over this script to edit it."
WScript.Quit
End If
flnm = WScript.Arguments.Item(0)
set fs = WScript.CreateObject("Scripting.FileSystemObject")
set cv = fs.OpenTextFile (WScript.Arguments.Item(0))
If Not fs.FileExists(flnm) Or LCase(fs.GetExtensionName(flnm)) <> "csv" Then
WScript.Echo "This script is meant for CSV only."
WScript.Quit
End If
fol = fs.GetParentFolderName(flnm)
customValue = InputBox("What should the last column contain?", "Column Info")
Set pat = New RegExp
pat.Global = True
pat.IgnoreCase = True
pat.Pattern = "^(""[^""]*""|[^,]*)?,"
recentCol = ""
csvCount = 1
header = ""
cnt = 0
While Not cv.AtEndOfStream
cnt = cnt + 1
row = cv.ReadLine
If Right(row,1) <> "," Then: comma = ",": Else: comma = "": End If
Set col1 = pat.Execute(row)
col = col1.Item(0).Value
sameFile = true
If recentCol <> "" Then
If col <> recentCol And cnt > 2 Then
csvCount = csvCount + 1
sameFile = false
End If
Else
header = row & comma & """Off Peak"",""Effective Date"""
Set csv = fs.OpenTextFile(fol & "\" & basename & csvcount & ".csv", 8, True)
End If
recentCol = col
If Not samefile Then
csv.close
Set csv = fs.OpenTextFile(fol & "\" & basename & csvcount & ".csv", 8, True)
End If
If hasHeader And (1 = cnt or Not samefile) Then
csv.WriteLine(header)
Else
csv.WriteLine(row & comma & ",""" & customValue & """")
End if
Wend
csv.Close
Works Great!

Resources