i am testing a script where I want to create Mutliple charts with the same pivot data,
I am able to get the first chart using the below snippets
$pivotTableParams = #{
PivotTableName = "PWAuditData1"
Address = $excel.Sheet1.cells["F1"]
SourceWorkSheet = $excel.Sheet1
PivotRows = echo o_username
PivotData = #{'Total' = 'sum'}
PivotTableStyle = 'Light21'
PivotChartDefinition = #{
Title="Total Downloads by Users";
ChartType="ColumnClustered";
Column=5; Row=15; Width=600; Height=500;
YMajorUnit=500; YMinorUnit=100;
LegendPosition="Bottom"
}
}
and then call the above definition using
$pt = Add-PivotTable #pivotTableParams -PassThru
I want to use the same data from above Pivot Table and insert for example a doughnut chart for which i use the Add-ExcelChart
$pivotSheet=$excel.Sheet1
$pt1 = Add-ExcelChart -Worksheet $excel.Sheet1 -PivotTable $pivotSheet.pivotTables[0] -ChartType Doughnut -Row 100 -Column 20 -PassThru
when the code runs, I am able to get the first chart, but for inserting the second chart it gives error as below,
Parameter set cannot be resolved using the specified named parameters
image
Question:
What is the correct way to reference the existing Pivot object which is already? placed on the sheet ?
Thanks for looking to this,
RJ
Related
I have created an office script that does some operations on excel data and creates a table. The data of this table is the input for a chart. The chart is already existing in the Excel file.
Now I need to update the data range of the chart manually whenever new items are added to the table, but I want to do this automated ofcourse.
I was not able to find any documentation on this. In VBA I can update data ranges of a chart and I tried to port that to the office script, but with no luck.
in VBA it is like this:
ActiveChart.FullSeriesCollection(1).XValues = "='summary and graph'!$A$2:$A$42"
ActiveChart.FullSeriesCollection(1).Values = "='summary and graph'!$B$2:$B$42"
ActiveChart.FullSeriesCollection(2).Values = "='summary and graph'!$C$2:$C$42"
What would be a way to do this in excel office script? What would the syntax be?
Thanks, Joris
Here is the code you get when you do the recording to create a chart for the table
function main(workbook: ExcelScript.Workbook) {
let selectedSheet = workbook.getActiveWorksheet();
let table1 = workbook.getTable("Table1");
// Insert chart on sheet selectedSheet with source data from table1
let chart_5 = selectedSheet.addChart(ExcelScript.ChartType.columnClustered, table1.getRange());
}
You can change the chart source data using the setData method.
You can also get the chart series collection using the getSeries method, and set Values for each series using the setValues method. Example code could be -
let seriesCollection = chart_5.getSeries();
let range = workbook.getWorksheet("summary and graph").getRange("B2:B42");
seriesCollection[0].setValues(range);
range = workbook.getWorksheet("summary and graph").getRange("C2:C42");
seriesCollection[1].setValues(range);
I'm trying to write a script that finds data in a sheet dynamically (the dimensions of the table need to be flexible in both axis) and then updates the source data range for an existing chart on another sheet (so that my users don't need to set up the styling themselves).
Below is my script so far. Everything works apart from the final line where Excel Online gives me the error:
"Line 10: Chart setData: You cannot perform the requested operation"
function main(workbook: ExcelScript.Workbook)
{
let selectedSheet = workbook.getWorksheet("Enter data in this sheet");
// Add a new table at used range on selectedSheet
let range = selectedSheet.getUsedRange();
if(selectedSheet.getTables().length == 0)
{
let newTable = workbook.addTable(range, true);
}
workbook.getWorksheet("The Chart").getChart("Chart 1").setData(range);
}
The worksheet names are correct and so is the chart name as far as I can see:
screenshot of excel online showing chart and worksheet names
Answer found; the chart was on a protected sheet which did not allow editing of objects. Updating the protection to allow all users to edit objects has resolved the issue.
There is one other question from Sep 2017 that addresses this same problem but does not have an answer: create a pivotchart with python win32com
I have tried several approaches to get this working so I want to explain these and hopefully get some insight from someone on how to get this working. This does not appear to be a well worn path so I do not have high hopes.
Environment details:
Windows 10
Office 2013
Anaconda 3.6
I use
win32com.client.gencache.EnsureDispatch('Excel.Application')
but I can also use
win32com.client.DispatchEx('Excel.Application')
or
win32com.client.dynamic.Dispatch('Excel.Application')
Each one returns this CLSID win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x8
Each one also has the same error.
I also ran this command as per documentation here:
C:\Users\home_dir>python AppData\Local\Continuum\anaconda3\pkgs\pywin32-223-py36hfa6e2cd_1\Lib\site-packages\win32com\client\makepy.py -i "Microsoft Excel 15.0 Object Library"
Output generated from makepy.py:
Microsoft Excel 15.0 Object Library {00020813-0000-0000-C000-000000000046}, lcid=0, major=1, minor=8
>>> # Use these commands in Python code to auto generate .py support
>>> from win32com.client import gencache
>>> gencache.EnsureModule('{00020813-0000-0000-C000-000000000046}', 0, 1, 8)
This version of gencache did not work successfully:
Excel = win32com.client.gencache.EnsureModule('{00020813-0000-0000-C000-000000000046}', 0, 1, 8)
Source Data
There are 100 rows and 18 columns in a Pandas Dataframe that I write to Excel using ExcelWriter
ew = pd.ExcelWriter('c:\devworkspace\SQL-Pandas-Excel\SampleData.xlsx')
sample_data_df.to_excel(ew, sheet_name='Source Data')
ew.save()
ew.close()
Excel = win32com.client.gencache.EnsureDispatch('Excel.Application')
win32c = win32com.client.constants
wb = Excel.Workbooks.Open('c:\devworkspace\SQL-Pandas-Excel\SampleData.xlsx')
src_sheet = wb.Worksheets('Source Data')
rng_row = 100
rng_col = 18
rng_beg = src_sheet.Cells(1,2)
rng_end = src_sheet.Cells(rng_row,rng_col)
pvt_src_rng = src_sheet.Range(rng_beg, rng_end)
pvt_src_rng.Select()
pvt_src = "%s!R1C2:R%dC%d"%(src_sheet.Name,rng_row+1,rng_col+1) #add 1 for header and df index
Pivot Cache
I use PivotCaches().Create() as opposed to .Add() so I can specify Version=win32c.xlPivotTableVersion15 which is the correct version for office 2013. Otherwise it appeared to default to version 11.
pc = wb.PivotCaches().Create(SourceType=win32c.xlDatabase, SourceData=pvt_src, Version=win32c.xlPivotTableVersion15)
This change moved the dial but did not solve my problem - I am still getting an error and the chart is not getting created:
The formatting on the pivot table is enhanced when I applied this
change.
The root error code changed from -2147024809 which is "The
parameter is incorrect" To root error code -2146827284
Which it cannot translate to human readable message:
print(win32api.FormatMessage(error.excepinfo[5]))
error: (317, 'FormatMessageW', 'The system cannot find message text for message number 0x%1 in the message file for %2.')
Searching on this error code 2146827284 the discussions appear to be related to the excel object being busy. But Excel.Visible is set to 0 - also the default - so it is running in headless mode.
Adding Sheets, Creating the Pivot Table, Adding Fields is successful
These are all wrapped in try-except in the actual code - removed for brevity.
pvt_sheet = wb.Sheets.Add(After=src_sheet)
pvt_sheet.Name = 'Pivot Sheet'
pvt_rng_beg = pvt_sheet.Cells(2,2)
pvt_rng_end = pvt_sheet.Cells(2,2)
pvt_dest_rng = pvt_sheet.Range(pvt_rng_beg, pvt_rng_end)
pt = pc.CreatePivotTable(TableDestination=pvt_dest_rng,TableName='PivotTable1')
pt.AddFields(RowFields="claimant_type_desc" , ColumnFields="claim_cause_desc" )
pt.AddDataField(Field=pt.PivotFields("total_direct_payment"), Caption="Total Incurred")
I can add a sheet or a chart as the "ChartDestination" but neither option alters the outcome. I can validate that the object is getting added successfully.
#chrt_sheet = wb.Charts.Add(After=pvt_sheet)
chrt_sheet = wb.Sheets.Add(After=pvt_sheet)
chrt_sheet.Name = 'Pivot Chart'
ChartDestination argument is the only required argument the other arguments are optional: XlChartType, Left, Top, Width, Height
Docs here:
Based on examples I pass the name of the sheet or chart object as a string 'Pivot Sheet'. That should work.
pch = pc.CreatePivotChart(ChartDestination='Pivot Chart')
Because the parameter is defined as a Variant then I explicitly assign the string to a Variant object. I tried a range of different variant types but that did not yield a different outcome.
chrt_sheet_name_variant = win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_BSTR, chrt_sheet.Name)
print(chrt_sheet_name_variant.value)
print(chrt_sheet_name_variant.varianttype)
print(chrt_sheet_name_variant.__class__)
print(type(chrt_sheet_name_variant))
pch = pc.CreatePivotChart(ChartDestination=chrt_sheet_name_variant)
#Output:
#Pivot Chart
#16392
#<class 'win32com.client.VARIANT'>
#<class 'win32com.client.VARIANT'>
This is the error that is generated:
File "C:\Users\xxxxxx\AppData\Local\Temp\gen_py\3.6\00020813-0000-0000-C000-000000000046x0x1x8\PivotCache.py", line 36, in CreatePivotChart
, XlChartType, Left, Top, Width, Height
com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2146827284), None)
Thank you Boussif for finding the way on this.
Here is my working solution:
Generate a Pivot Chart from your Pivot Table
Using the Workbook object that we created earlier we can "insert" a Chart to our workbook and assign a name to our chart - name appears in the tab.
Because the Pivot Table is an object in the Pivot Cache - which is an object in the Workbook, the Chart object is added as a Pivot Chart and uses the Pivot Table as its the data.
Read the relevant documentation here:
https://learn.microsoft.com/en-us/office/vba/api/excel.workbook.charts
https://learn.microsoft.com/en-us/office/vba/api/excel.chart(object)
chrt_sheet = wb.Charts.Add()
chrt_sheet.Name = 'Pivot Chart'
Specify the Chart Type
The chart type corresponds to the same Chart Types that are available in the Excel program.
Choose from a list of enumerated types.
For example a 3D Column Chart has a value of xl3DColumn coded as follows:
win32c.xl3DColumn
Use the win32c object ew defined earlier to reference all constant values including enumerated lists.
Documented here:
https://learn.microsoft.com/en-us/office/vba/api/excel.chart.charttype
https://learn.microsoft.com/en-us/office/vba/api/excel.xlcharttype
chrt_sheet.ChartType = win32c.xl3DColumn
Set the Chart Title
The sheet object will not have a ChartTitle property until to set HasTitle Property to True
chrt_sheet.HasTitle = True
chrt_sheet.ChartTitle.Text = "Chart Title"
Set the Color Scheme
ChartColor values 10 - 26 correspond to the Change Colors menu on the DESIGN tab of the CHART TOOLS ribbon.
chrt_sheet.ChartColor = 13
Specify a Chart Layout
Layout is optional
Layouts are the same as the list of Layouts to choose from in the Excel program.
In this case they are not provided as an enumerated list. So you have to provide a number for the layout. It will generally be from 1 to 10. Layouts are different depending on the respective Chart Type. To find out which layout to choose you will need to run the Excel program and try each one. If you hover your mouse over the Layout option it will display a "Tool Tip" with the name of the Layout. For example: 'Layout 7'. You would provide the number associated with your respective layout.
you use the ApplyLayout method to choose the Layout:
ApplyLayout(Layout, Chart Type)
Documented here:
https://learn.microsoft.com/en-us/office/vba/api/excel.chart.applylayout
chrt_sheet.ApplyLayout(7, win32c.xl3DColumn)
Specify a Chart Style
Style is optional
Documentation states that 1 to 48 are valid values. However the correct range depends on which Chart Type is chosen.
Depending on the Chart Type 201 to 352 is also valid.
To get the numbers numbers that match the style selections that are available for your Chart Type:
From the Developer Tab - Run a macro
From the Chart Design Tab - Select all the styles
From the Devloper Tab - Stop running the macro
From the Developer Tab - Edit the macro
This will reveal the correct values for your case
For the 3D Column Chart Type, the range is 286 to 297
Relevant documentation here:
https://learn.microsoft.com/en-us/office/vba/api/excel.chart.chartstyle
xlChartStyle = 294
chrt_sheet.ClearToMatchStyle
chrt_sheet.ChartStyle = xlChartStyle
Chart Style Experiment
If you are curious and want to see what each one looks like then run this code
hint: Remove the comments '#'
#import time
#from time import sleep
#for xlChartStyle in range(286, 297):
# try:
# chrt_sheet.ClearToMatchStyle
# chrt_sheet.ChartStyle = xlChartStyle
# chrt_sheet.ChartTitle.Text = "Chart Style = "+str(xlChartStyle)
# sleep(1)
# except pythoncom.com_error as error:
# print("Chart Style = %s" % str(xlChartStyle))
Format Axis Labels
There are three Axis dimensions specified by the XlAxisType Enumeration
X axis = xlCategory
Y Axis = xlValue
Z Axis = xlSeriesAxis (3D Charts only)
In this example we are also removing the Z Axis Tick Labels
chrt_sheet.Axes(win32c.xlCategory).AxisTitle.Text = "X Axis Title"
chrt_sheet.Axes(win32c.xlSeries).TickLabelPosition = win32c.xlNone
chrt_sheet.Axes(win32c.xlSeries).HasTitle = True
chrt_sheet.Axes(win32c.xlSeries).AxisTitle.Text = "Z Axis Title"
chrt_sheet.Axes(win32c.xlValue).AxisTitle.Text = "Y Axis Title"
I've written a Powershell script to grab a CSV, create an Excel sheet from it, and create a Pivottable with a few rows and summary values. I'm also able to add a filter field but what I cannot successfully do is tell the filter field what value I want it to filter on.
The PivotFilters.Add and PivotFilters.Add2 methods always throw a "Value does not fall within the expected range" error.
Here's the relevant portions of the code:
# Add PivotTable
$WS2 = $WBK1.Worksheets.Add()
$WS2.Name = "PivotTable"
$xlPivotTableVersion15 = 5 # Excel 2013
$xlPivotTableVersion12 = 3 # Excel 2007
$xlDescending = 2
$xlDatabase = 1 # this just means local sheet data
$xlHidden = 0
$xlRowField = 1
$xlColumnField = 2
$xlFilterField = 3
$xlDataField = 4
$xlSum = -4157
$xlAverage = -4106
$xlCount = -4112
$xlRight = -4152
$pivotRange = $WS1.UsedRange
$PivotTable = $WBK1.PivotCaches().Create($xlDatabase,$pivotRange,$xlPivotTableVersion15)
$PivotTable.CreatePivotTable("R1C1","Tables1") | Out-Null
[void]$WS2.Select()
$WBK1.ShowPivotTableFieldList = $true
# configure pivottable fields
$PivotFields = $WS2.PivotTables("Tables1").PivotFields("VmName")
$PivotFields.Orientation = $xlRowField
$PivotFields = $WS2.PivotTables("Tables1").PivotFields("RAM")
$PivotFields.Orientation = $xlDataField
$PivotFields.Function = $xlAverage
$PivotFields = $WS2.PivotTables("Tables1").PivotFields("vCPU")
$PivotFields.Orientation = $xlDataField
$PivotFields.Function = $xlAverage
$PivotFields = $WS2.PivotTables("Tables1").PivotFields("Dept") # I also tried commenting these lines out
$PivotFields.Orientation = $xlFilterField # I also tried commenting these lines out
$xlValueEquals = 7
$xlCaptionEquals = 15
$WS2.PivotTables("Tables1").PivotFields("Dept").PivotFilters.Add2($xlValueEquals, "Finance")
I have tried using both PivotFilters.Add and PivotFilters.Add2 either way I always get "Value does not fall within the expected range."
I've also tried commenting out the lines annotated above as I wondered if already having a filter created was confusing it.
Any advice greatly appreciated!
OK so in the end I found that basically writing a value into the cell of the filter I created using...
$PivotFields = $WS2.PivotTables("Tables1").PivotFields("Dept")
$PivotFields.Orientation = $xlFilterField
...worked and so I could do away with the PivotFilters.Add / PivotFilters.Add2 method. If anyone does know the answer to that I'm sure someone on t'interweb would be grateful for the solution being shared.
I too never got the PivotFilters.Add method to work. What did work for me, however, was creating and manipulating a slicer.
Once created and configured (e.g. a date slicer), you can query the slicer's SlicerCacheLevel.SlicerItems array for valid values and then apply filtering / slice the data by updating the array in SlicerCache.VisibleSlicerItemsList.
Confusingly, there is a SlicerCacheLevel.VisibleSlicerItemsList that errors when you try to update it. I didn't look into further as SlicerCache.VisibleSlicerItemsList worked.
I am attempting to automate the process of adding a worksheet (with data) per clientname in excel for a monthly report type workbook
I thought it should be straight forward... but the method I am using isnt working.... it doesn't even get to the sheet making mode... can you help me figure out what I did wrong?
The following is the function I made
function Excelclientstatstemplate ($clients) {
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
#### Check if Clients worksheet exists, if no then make one with client name ###
$sheetcheck = if (($clientws)) {} Else {
$WS = $WB.worksheets.add
$WS.name = "$clients"
}
$sheetcheck
$WB.Save
# Enter stat labels
$clientws.cells.item(1,1) = "CPU Count"
$clientws.cells.item(2,1) = "RAM"
$clientws.cells.item(3,1) = "Reserved CPU"
$clientws.cells.item(4,1) = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2) = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2) = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2) = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2) = [decimal]$cstats.resmemoryLimitGB
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
and then I tried to make a Foreach thing for it
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
The entire Process of the function goes
Take client name
Open a particular excel workbook
Check if there are any sheets with client name
If there are NO sheets with client name, make one with client name
Fill The first column Cells with labels
Fill the second column cells with data (data works I already write CSVs withem)
Save and exit
The Foreach variable just does the function for each of Clients names from a clientlist (nothing wrong with clientlist)
Am I messing something up?
Thanks for the help.
You are not calling the .Add() method correctly. You are missing the parenthesis at the end of it. To fix it you should be able to simply modify the line to this:
$WS = $WB.worksheets.add()
Also, the cells have properties that you should refer to, so I would also modify the part that sets your cell values to something like this:
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
I'm fairly sure that defining the type is pointless, since to Excel they're all strings until you set the cell's formatting settings to something else. I could be wrong, but that is the behavior that I have observed.
Now, for other critiques that you didn't ask for... Don't launch Excel, open the book, save the book, and close Excel for each client. Open Excel once at the beginning, open the book, make your updates for each client, and then save, and close.
Test to see if the client has a sheet, and add it if needed, then select the client's sheet afterwords. Right now there's nothing there to set $clientws if you have to add one for that client.
Adding a worksheet by default places it before the active worksheet. This was a poor choice in design in my opinion, but it is what it is. If it were me I'd add new sheets specifying the last worksheet in the workbook, which will add the new worksheet before the last one, making it the second to the last worksheet. Then I'd move the last worksheet up in front of the new one, effectively adding the new worksheet as the last one listed. Is it possible to add the new worksheet as the last one when you make it? Yes, but it's was too complicated for my taste. See here if you are interested in doing that.
When testing for an existing client worksheet to make one if it is missing, do that, don't tell it to test for something, and do nothing, and put everything you want in an Else statement. That just complicates things. All that said, here's some of those suggestions put into practice:
function Excelclientstatstemplate ($clients) {
#### Check if Clients worksheet exists, if no then make one with client name ###
if (($clients -notin $($WB.worksheets).Name)){
#Find the current last sheet
$LastSheet = $WB.Worksheets|Select -Last 1
#Make a new sheet before the current last sheet so it's near the end
$WS = $WB.worksheets.add($LastSheet)
#Name it
$WS.name = "$clients"
#Move the last sheet up one spot, making the new sheet the new effective last sheet
$LastSheet.Move($WS)
}
#Find the current client sheet regardless of if it existed before or not
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL