I've made a slicer connected to PivotTable with the option to choose type of change that will be displayed (pct. or abs.). I added to code in PowerPivot some SWITCH functions that would dynamically change formatting:
[...]
VAR pctg_chng =
IF(
ISBLANK(vol_current) && ISBLANK(vol_last);
BLANK();
DIVIDE(
vol_current;
vol_last
) - 1
)
VAR abs_chng =
IF(
ISBLANK(vol_current) && ISBLANK(vol_last);
BLANK();
vol_current - vol_last
)
VAR chng =
SWITCH(
TRUE();
IF(HASONEVALUE('Type'[Type]); VALUES('Type'[Type])) = "% chng."; pctg_chng;
IF(HASONEVALUE('Type'[Type]); VALUES('Type'[Type])) = "abs. chng."; abs_chng;
pctg_chng
)
VAR format_chng =
FORMAT(
chng;
SWITCH(
TRUE();
IF(HASONEVALUE('Type'[Type]);VALUES('Type'[Type])) = "% chng."; "# ##0.0%";
IF(HASONEVALUE('Type'[Type]);VALUES('Type'[Type])) = "abs. chng."; "# ##0.00";
"# ##0.0%"
)
)
RETURN
format_chng
It works fine, but in PivotTable there are blank values for some rows which should be automatically removed. In addition conditional formatting applied to table isn't displayed on it.
Is it possible to make anything out of it?
Thanks!
Related
I am new to using EPplus. I actually wanted to populate a dropdown list in Sheet1 from a list of values in Sheet2. This can be achieved by Cell Range Validation in Excel.But I am not sure whether EPPlus support this programmatically. It will be really helpful if anyone could help me out.
Populate a dropdown in this Designation Column from Designation Column in Sheet 2.
Sheet 2
Here's how to do it:
var departmentSheet = excel.Workbook.Worksheets.Add("department");
departmentSheet.Cells["A1"].Value = "Management";
departmentSheet.Cells["A2"].Value = "Administrator";
departmentSheet.Cells["A3"].Value = "Quality Engineering";
departmentSheet.Cells["A4"].Value = "Enablers";
var designationSheet = excel.Workbook.Worksheets.Add("designations");
designationSheet.Cells["A1"].Value = "Developer 1";
designationSheet.Cells["A2"].Value = "Developer 2";
designationSheet.Cells["A3"].Value = "Developer 3";
designationSheet.Cells["A4"].Value = "Developer 4";
// add a validation and set values
// the range of cells that will contain the validation
var validation = departmentSheet.DataValidations.AddListValidation("B1:B4");
// set validation rules as required
validation.ShowErrorMessage = true;
validation.ErrorStyle = ExcelDataValidationWarningStyle.warning;
validation.ErrorTitle = "An invalid value was entered";
validation.Error = "Select a value from the list";
// set the range that contains the validation list
validation.Formula.ExcelFormula = $"={ designationSheet.Name }!$A$1:$A$4";
I'm running a SQL query to grab some data. Then using CF's spreadsheet functions to export it as an Excel file. The problem is that cells in the query that are null are getting a non-null character in them. They LOOK blank when you open the spreadsheet, but they are not. And it specifically interferes with the use of Excel's ctrl-arrow functionality to find the next non-blank cell.
Field looks null but actually isn't
In the database, the color column is null if there is no value. In Excel, ctrl-downarrow should take you to cell D9 or "blue", but it doesn't. It takes you all the way to the bottom of the column.
If, in Excel, I go to each "blank" cell, and press the delete key, then the functionality returns. So clearly, Coldfusion is not handling it properly.
I narrowed it down to Coldfusion because if I run the same query in SSMS, and cut and paste the data into Excel from there, it preserves the null, and ctrl-downarrow works properly.
<cfquery datasource="test" name="qdata">
select ID,Name,Email,Color from TestTable
</cfquery>
<cfscript>
columns = qdata.getMetaData().getColumnLabels();
sheet=spreadsheetNew("Sheet1",true);
spreadsheetAddrows(sheet,qdata,1,1,true,[""],true);
sheetAsBinary = SpreadSheetReadBinary( sheet );
</cfscript>
<cfset filename = "TestFile.xlsx">
<cfheader name="Content-Disposition" value="attachment; filename=#filename#">
<cfcontent type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" variable="#sheetAsBinary#" reset="true">
The query used is actually irrelevant, as I can reproduce the issue with any query that returns fields with null in some of them.
I ended up using a combination of things to get this to work. Looping over the query and using SpeadSheetSetCellValue for each column, with an if statement checking if the colum was null. If it was null, I just didn't populate that column at all. Now, this works.
Thanks to all for your comments, and to #Ageax for steering me towards my ultimate solution.
This was the final code (excluding the query, which doesn't matter), that I adapted from this post:
<cfsilent>
<cfscript>
variables.cont = false;
/*variables.qdata is the name of my query object*/
switch(IsQuery(variables.qdata)){
case true:
variables.cont = true;
variables.rqCols = ArrayToList(variables.qdata.getColumnNames(),',');
variables.rqLen = ListLen(variables.rqCols,',');
variables.thisFileName = "JSM2020ProgramExport-" & DateTimeFormat(now(),'yyyymmdd_HHnnss') & ".xlsx";
variables.ssObj = SpreadsheetNew(left(trim(variables.thisFileName),30),'true');/* Setting last argument to 'true' makes this an xlsx, not xls. */
variables.format = StructNew();
variables.format.font = "Arial";
variables.format.textwrap = "true";
variables.format.verticalalignment = "VERTICAL_TOP";
variables.format.dataformat = "text";
SpreadsheetFormatColumns(variables.ssObj,variables.format,"1-#val(variables.rqLen)#");
SpreadsheetFormatRows(variables.ssObj,variables.format,"1,2");
SpreadsheetSetCellValue(variables.ssObj,variables.thisFileName, 1, 1); /* This is the name of the report, top row */
SpreadsheetAddFreezePane(variables.ssObj,0,2); /* Freeze top two rows */
for(x = 1; x lte val(variables.rqLen); x++){ /* This inserts the column names as row headers */
variables.colName = ListGetAt(variables.rqCols,x);
SpreadsheetSetCellValue(variables.ssObj,variables.colName,2,x);
}
for(y = 1; y lte val(variables.qdata.recordCount); y++){ /* This loops the query records */
for(x = 1; x lte val(variables.rqLen); x++){ /* This loops each column per recordset */
variables.colName = ListGetAt(variables.rqCols,x);
variables.thisValue = REreplaceNoCase(variables.qdata[variables.colName][y],"&##59;",";","all"); /* These make sure that no HTML entities are in the data */
variables.thisValue = REreplaceNoCase(variables.thisValue,"&apos(&##59)?;","'","all");
variables.thisValue = REreplaceNoCase(variables.thisValue,""(&##59)?;",'"',"all");
variables.thisValue = REreplaceNoCase(variables.thisValue,"<(&##59)?;",'<',"all");
variables.thisValue = REreplaceNoCase(variables.thisValue,">(&##59)?;",'>',"all");
variables.thisValue = REreplaceNoCase(variables.thisValue,"&##40(&##59|;)","(","all");
variables.thisValue = REreplaceNoCase(variables.thisValue,"&##41(&##59|;)",")","all");
if (variables.thisValue is not 'NULL'){SpreadsheetSetCellValue(variables.ssObj,variables.thisValue,val(y + 2),x);}
}
}
SpreadsheetFormatColumns(variables.ssObj,variables.format,"1-#val(variables.rqLen)#");
SpreadsheetFormatRows(variables.ssObj,variables.format,"1,2");
break;
default: /* Do nothing if the query object doesn't exist */
break;
}
</cfscript>
</cfsilent>
I'd like to be able to delete an entire row in a Google Spreadsheets if the value entered for say column "C" in that row is 0 or blank. Is there a simple script I could write to accomplish this?
Thanks!
I can suggest a simple solution without using a script !!
Lets say you want to delete rows with empty text in column C.
Sort the data (Data Menu -> Sort sheet by column C, A->Z) in the sheet w.r.t column C, so all your empty text rows will be available together.
Just select those rows all together and right-click -> delete rows.
Then you can re-sort your data according to the column you need.
Done.
function onEdit(e) {
//Logger.log(JSON.stringify(e));
//{"source":{},"range":{"rowStart":1,"rowEnd":1,"columnEnd":1,"columnStart":1},"value":"1","user":{"email":"","nickname":""},"authMode":{}}
try {
var ss = e.source; // Just pull the spreadsheet object from the one already being passed to onEdit
var s = ss.getActiveSheet();
// Conditions are by sheet and a single cell in a certain column
if (s.getName() == 'Sheet1' && // change to your own
e.range.columnStart == 3 && e.range.columnEnd == 3 && // only look at edits happening in col C which is 3
e.range.rowStart == e.range.rowEnd ) { // only look at single row edits which will equal a single cell
checkCellValue(e);
}
} catch (error) { Logger.log(error); }
};
function checkCellValue(e) {
if ( !e.value || e.value == 0) { // Delete if value is zero or empty
e.source.getActiveSheet().deleteRow(e.range.rowStart);
}
}
This only looks at the value from a single cell edit now and not the values in the whole sheet.
I wrote this script to do the same thing for one of my Google spreadsheets. I wanted to be able to run the script after all the data was in the spreadsheet so I have the script adding a menu option to run the script.
/**
* Deletes rows in the active spreadsheet that contain 0 or
* a blank valuein column "C".
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[2] == 0 || row[2] == '') {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Remove rows where column C is 0 or blank",
functionName : "readRows"
}];
sheet.addMenu("Script Center Menu", entries);
};
Test spreadsheet before:
Running script from menu:
After running script:
I was having a few problems with scripts so my workaround was to use the "Filter" tool.
Select all spreadsheet data
Click filter tool icon (looks like wine glass)
Click the newly available filter icon in the first cell of the column you wish to search.
Select "Filter By Condition" > Set the conditions (I was using "Text Contains" > "word")
This will leave the rows that contain the word your searching for and they can be deleted by bulk selecting them while holding the shift key > right click > delete rows.
This is what I managed to make work. You can see that I looped backwards through the sheet so that as a row was deleted the next row wouldn't be skipped. I hope this helps somebody.
function UpdateLog() {
var returnSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('RetLog');
var rowCount = returnSheet.getLastRow();
for (i = rowCount; i > 0; i--) {
var rrCell = 'G' + i;
var cell = returnSheet.getRange(rrCell).getValue();
if (cell > 0 ){
logSheet.
returnSheet.deleteRow(i);
}
}
}
quite simple request. Try this :
function try_It(){
deleteRow(2); //// choose col = 2 for column C
}
function deleteRow(col){ // col is the index of the column to check for 0 or empty
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n][col]!='' && data[n][col]!=0){ targetData.push(data[n])};
}
Logger.log(targetData);
sh.getDataRange().clear();
sh.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);
}
EDIT : re-reading the question I'm not sure if the question is asking for a 'live' on Edit function or a function (like this above) to apply after data has been entered... It's not very clear to me... so feel free to be more accurate if necessary ;)
There is a simpler way:
Use filtering to only show the rows which you want to delete. For example, my column based on which I want to delete rows had categories on them, A, B, C. Through the filtering interface I selected only A and B, which I wanted to delete.
Select all rows and delete them. Doing this, in my example, effectively selected all A and B rows and deleted them; now my spreadsheet does not show any rows.
Turn off the filter. This unhides my C rows. Done!
There is a short way to solve that instead of a script.
Select entire data > Go to menu > click Data tab > select create filter > click on filter next to column header > pop-up will appear then check values you want to delete > click okay and copy the filtered data to a different sheet > FINISH
reading your question carefully, I came up with this solution:
function onOpen() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// create menu
var menu = [{name: "Evaluate Column C", functionName: "deleteRow"}];
// add to menu
ss.addMenu("Check", menu);
}
function deleteRow() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get active/selected row
var activeRow = ss.getActiveRange().getRowIndex();
// get content column C
var columnC = ss.getRange("C"+activeRow).getValue();
// evaluate whether content is blank or 0 (null)
if (columnC == '' || columnC == 0) {
ss.deleteRow(parseInt(activeRow));
}
}
This script will create a menu upon file load and will enable you to delete a row, based on those criteria set in column C, or not.
This simple code did the job for me!
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet(); // get active spreadsheet
var activeRow = ss.getActiveRange().getRowIndex(); // get active/selected row
var start=1;
var end=650;
var match='';
var match2=0; //Edit this according to your choice.
for (var i = start; i <= end; i++) {
var columnC = ss.getRange("C"+i).getValue();
if (columnC ==match || columnC ==match2){ ss.deleteRow(i); }
}
}
The below code was able to delete rows containing a date more than 50 days before today in a particular column G , move these row values to back up sheet and delete the rows from source sheet.
The code is better as it deletes the rows at one go rather than deleting one by one. Runs much faster.
It does not copy back values like some solutions suggested (by pushing into an array and copying back to sheet). If I follow that logic, I am losing formulas contained in these cells.
I run the function everyday in the night (scheduled) when no one is using the sheet.
function delete_old(){
//delete > 50 day old records and copy to backup
//run daily from owner login
var ss = SpreadsheetApp.getActiveSpreadsheet();
var bill = ss.getSheetByName("Allotted");
var backss = SpreadsheetApp.openById("..."); //backup spreadsheet
var bill2 = backss.getSheetByName("Allotted");
var today=new Date();
//process allotted sheet (bills)
bill.getRange(1, 1, bill.getMaxRows(), bill.getMaxColumns()).activate();
ss.getActiveRange().offset(1, 0, ss.getActiveRange().getNumRows() - 1).sort({column: 7, ascending: true});
var data = bill.getDataRange().getValues();
var delData = new Array();
for(n=data.length-1; n>1; n--){
if(data[n][6] !=="" && data[n][6] < today.getTime()-(50*24*3600*1000) ){ //change the condition as per your situation
delData.push(data[n]);
}//if
}//for
//get first and last row no to be deleted
for(n=1;n<data.length; n++){
if(data[n][6] !=="" && data[n][6] < today.getTime()-(50*24*3600*1000) ){
var strow=n+1 ; //first row
break
}//if
}//for
for(n=data.length-1; n>1; n--){
if(data[n][6] !=="" && data[n][6] < today.getTime()-(50*24*3600*1000) ){
var ltrow=n+1 ; //last row
break
}//if
}//for
var bill2lr=bill2.getLastRow();
bill2.getRange((bill2lr+1),1,delData.length,delData[0].length).setValues(delData);
bill.deleteRows(strow, 1+ltrow-strow);
bill.getRange(1, 1, bill.getMaxRows(), bill.getMaxColumns()).activate();
ss.getActiveRange().offset(1, 0, ss.getActiveRange().getNumRows() - 1).sort({column: 6, ascending: true}); //get back ordinal sorting order as per column F
}//function
I generate an Excel sheet which contains data formatted like so:
IOW, the "Total Packages", "Total Purchases", "Average Price", and "% of Total" values are located in a column of their own (Data) for each overarching (or sidearching) description.
When I PivotTablize this data, it places these values beneath each description:
This makes sense, but those accustomed to the previous appearance want it to be replicated in the PivotTable. How can I shift the Description "subitems" in the PivotTable to their own column?
This is the code I use to generate the PivotTable:
private void PopulatePivotTableSheet()
{
string NORTHWEST_CORNER_OF_PIVOT_TABLE = "A6";
AddPrePivotTableDataToPivotTableSheet();
var dataRange = rawDataWorksheet.Cells[rawDataWorksheet.Dimension.Address];
dataRange.AutoFitColumns();
var pivotTable = pivotTableWorksheet.PivotTables.Add(
pivotTableWorksheet.Cells[NORTHWEST_CORNER_OF_PIVOT_TABLE],
dataRange,
"PivotTable");
pivotTable.MultipleFieldFilters = true;
pivotTable.GridDropZones = false;
pivotTable.Outline = false;
pivotTable.OutlineData = false;
pivotTable.ShowError = true;
pivotTable.ErrorCaption = "[error]";
pivotTable.ShowHeaders = true;
pivotTable.UseAutoFormatting = true;
pivotTable.ApplyWidthHeightFormats = true;
pivotTable.ShowDrill = true;
// Row field[s]
var descRowField = pivotTable.Fields["Description"];
pivotTable.RowFields.Add(descRowField);
// Column field[s]
var monthYrColField = pivotTable.Fields["MonthYr"];
pivotTable.ColumnFields.Add(monthYrColField);
// Data field[s]
var totQtyField = pivotTable.Fields["TotalQty"];
pivotTable.DataFields.Add(totQtyField);
var totPriceField = pivotTable.Fields["TotalPrice"];
pivotTable.DataFields.Add(totPriceField);
// Don't know how to calc these vals here, so have to grab them from the source data sheet
var avgPriceField = pivotTable.Fields["AvgPrice"];
pivotTable.DataFields.Add(avgPriceField);
var prcntgOfTotalField = pivotTable.Fields["PrcntgOfTotal"];
pivotTable.DataFields.Add(prcntgOfTotalField);
}
So there is one RowField ("MonthYr") with values such as "201509" and "201510", one ColumnField ("Description") and four DataFields, which align themseles under the Description column field. I want to shift those four fields to the right, to their own column, and the Description label to be vertically centered between those four values to their left. [How] is this possible?
Try changing the layout of your table with
pivotTable.RowAxisLayout xlTabularRow
pivotTable.MergeLabels = True
this is the result:
A little script in C# with Interop.Excel. Included the using ;)
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
var excelApp = new Excel.Application();
Excel.Workbook wb = excelApp.Workbooks.Open(#"e:\42\TestSO.xlsx");
Worksheet ws = wb.Worksheets["SheetName"];
PivotTable pt = ws.PivotTables("DynamicTableName");
pt.RowAxisLayout(XlLayoutRowType.xlTabularRow);
pt.MergeLabels = true;
wb.Save();
wb.Close();
Marshal.ReleaseComObject(ws);
It's all about PivotTable layout / design... here's the manual way - Salvador has the VBA way :)...
To create a PivotTable, source data needs to be provided from which to generate the PivotTable.
I have generated a "dummy" sheet for this purpose which contains raw data such as:
I need to generate a Pivot Table from that data so that it is generated like so:
IOW, it needs to have a filter on the "Description" column that allows the rows to be filtered (only show "Peppers" or whatever) AND a filter on which month columns to display (the sheet can have up to 13 month columns (Sep 15, Oct 15, etc.)) so that the user can choose 1..13 of those "month year" columns to display (technically, they could choose 0, but what would be the point)?
How can this be done? I've tried the following:
private void AddPivotTable()
{
var dataRange
rawDataWorksheet.Cells[rawDataWorksheet.Dimension.Address];
dataRange.AutoFitColumns();
var pivotTable
usagePivotWorksheet.PivotTables.Add(usagePivotWorksheet.Cells["A6"]
dataRange, "ProdUsagePivot");
pivotTable.MultipleFieldFilters = true;
pivotTable.GridDropZones = false;
pivotTable.Outline = false;
pivotTable.OutlineData = false;
pivotTable.ShowError = true;
pivotTable.ErrorCaption = "[error]";
pivotTable.ShowHeaders = true;
pivotTable.UseAutoFormatting = true;
pivotTable.ApplyWidthHeightFormats = true;
pivotTable.ShowDrill = true;
var descPageField = pivotTable.Fields["Description"];
pivotTable.PageFields.Add(descPageField);
descPageField.Sort
OfficeOpenXml.Table.PivotTable.eSortType.Ascending;
var descRowField = pivotTable.Fields["Description"];
pivotTable.RowFields.Add(descRowField);
. . . add others later
}
...but only get the filter for "Description" with no data beneath it:
What do I need to do yet to get the desired appearance/functionality?
Try this:
Create the entire PivotTable as usual and after adding all the required fields and calculations, move the DataPivotFields as RowFields.
In VBA will be something like this:
With PivotTable
.DataPivotField.Orientation = xlRowField
.Position = 2
End With
Also need to apply this: PivotTable.MergeLabels = True to have the Description cell merged for all the corresponding totals.
PD. Replace PivotTable with the PivotTable object in your code