I'm trying to get SheetsJS to bbreadown hour by hour from a raneg of shifts in a spreadsheet - so for example 07-1900 would have 12 hours. I'm then trying to get this to display in a table, of which for all rows, the hour will be broken down and then totalled togetehr and displayed in a table (so 0700 miught have 11 people, 0800 mikght have 12 so on and so forth.
THis is my code:
const fileInput = document.getElementById("fileInput");
const table = document.getElementById("table");
fileInput.addEventListener("change", function (event) {
const reader = new FileReader();
reader.onload = function (e) {
const data = e.target.result;
const workbook = XLSX.read(data, { type: "binary" });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const hourCounts = countHours(sheet);
updateTable(hourCounts);
};
reader.readAsBinaryString(event.target.files[0]);
});
function countHours(sheet) {
const hourCounts = {};
const range = XLSX.utils.decode_range(sheet["!ref"]);
for (let row = range.s.r + 1; row <= range.e.r; row++) {
const shift = sheet['B${row}'].v;
const [start, end] = shift.split("-").map((hour) => parseInt(hour));
for (let hour = start; hour < end; hour = hour + 100) {
const key = hour.toString().padStart(4, "0").slice(0, 2);
hourCounts[key] = (hourCounts[key] || 0) + 1;
}
}
return hourCounts;
}
function updateTable(hourCounts) {
for (const [hour, count] of Object.entries(hourCounts)) {
document.getElementById(hour).textContent = count;
}
}
I've tried many options but I am getting stuck. I am getting errors in Chrome where v is saying it's undefined.
Related
I can get Excel to take formula from NodeJS. Sums are fine. If Statements are fine. Concatenate works too. Textjoin and Concat do not. Excel does not seem to recognize the coding until the text from it is copied and pasted into a new cell. I was just able to say { f: "Sum(:A1:A2") } and that'd work fine. Is there some notation I need to know about, or a packet I need to download? Concatenate is nice, but it does not cover ranges.
Shortened code:
var XLSX = require("xlsx");
var fs = require("fs"); //Helps navigate directory
function getHeaderRow(sheet) { //Copying specific data from my excel files
var headers = [];
var range = XLSX.utils.decode_range(sheet["!ref"]);
let C;
var R = range.s.r;
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) {
/* walk every column in the range */
var cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })];
/* find the cell in the first row */
let hdr = "UNKNOWN " + C; // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell);
headers.push(hdr);
}
return headers;
}
function readFileToJson(filename, sheet){
var workbook = XLSX.readFile(filename);
var worksheet = workbook.Sheets[sheet];
var range = XLSX.utils.decode_range(worksheet['!ref']);
range.s.r = 2; // <-- zero-indexed, so setting to 1 will skip row 0
worksheet['!ref'] = XLSX.utils.encode_range(range);
header = getHeaderRow(worksheet,range);
range.s.r = 3; var header = getHeaderRow(worksheet,range);
var data = XLSX.utils.sheet_to_json(worksheet);
var newData = data.map(function(record){
var ConcatColumn = '=CONCAT(AF1:AF5)';
record.Concats = { f: ConcatColumn};
}
return newData;
}
var targetDir = __dirname + "/Want_Combined";
var files = fs.readdirSync(targetDir); //Can get undwanted files in folder.
var combinedData = [];
files.forEach(function(file){ //For loop
var FilePath = __dirname + "/Want_Combined/" + file;
var data = readFileToJson(FilePath, "2"); //Calls function
combinedData = combinedData.concat(['']).concat(['']).concat(data); //Add a space between entries
});
var newWB = XLSX.utils.book_new(); //New Document
var newWS = XLSX.utils.json_to_sheet(combinedData); //New Sheet, data is on it. Pasted at A3.
XLSX.utils.book_append_sheet(newWB,newWS,"Category 2"); //Combining all
XLSX.writeFile(newWB,"Results/GData.xlsx"); //Saving file
console.log("Done!");```
I have the following code:
function test1() {
var app = SpreadsheetApp;
var activesheet = app.getActiveSpreadsheet().getActiveSheet();
for (var j = 2; j<= 3; j++) {
activesheet.getRange(j,2).setValue("C");
}
}
This code will add the character "C" to the cell B2 and B3 which is great.
I am looking to get my code to add two "C" characters to 2 random cells from the first 30 cells of the B column.
Any help is much appreciated. Thank you.
This code may help you
function test1() {
var app = SpreadsheetApp;
var activesheet = app.getActiveSpreadsheet().getActiveSheet();
let randomCellIndex;
let usedCell = [];
for (var i = 0; i < 2; i++) {
// make sure not to use same index twice
do
randomCellIndex = Math.floor(Math.random() * 30) + 1;
while (usedCell.indexOf(randomCellIndex) !== -1);
usedCell.push(randomCellIndex);
activesheet.getRange(randomCellIndex,2).setValue("CC");
}
}
I assume that you don't want duplicate values so i would grab two values from shuffled array. To avoid going into the rabbit hole with shuffling array exsample uses underscore
_.shuffle([...Array(30)].map((value, index) => index+1))
Adds character string to range n times making a unique selection each time.
function addCharsRandom(chr = "AB", a1rg = "", n = 3) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
const rg = (a1rg.length < 2) ? sh.getActiveRange() : sh.getRange(a1rg);
if (rg.getDisplayValues().flat().length >= n) {
rg.setBackground("yellow")
const w = rg.getWidth();
const h = rg.getHeight();
const r = rg.getRow();
const c = rg.getColumn();
let rA = [];
let cA = [];
[...Array.from(new Array(n).keys())].forEach(i => {
let row, col;
do {
row = r + Math.floor(Math.random() * h);
col = c + Math.floor(Math.random() * w);
} while (rA.indexOf(row)>-1 && cA.indexOf(col)>-1);
rA.push(row);
cA.push(col);
sh.getRange(row, col).setValue(sh.getRange(row, col).getDisplayValue() + chr).setBackground("white");
});
} else {
SpreadsheetApp.getUi().alert("Range is to small for number of unique selections");
}
}
if a1rg equals "" then it will select the active range.
Demo:
I used the below code to get the data from API.
I used for loop within for loop and it's taking long time and program stops as time exceeds.
function devicedetails(){
var apikey='YWQ0OWFhYjgtNTY2asiHSNSajiasn'
var todaydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24), "UTC", "yyyy-MM-dd")
var thirtydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24*30), "UTC", "yyyy-MM-dd")
var cisss= SpreadsheetApp.getActiveSpreadsheet();
var workspacesheet = cisss.getSheetByName("Device");
var lastRows = workspacesheet.getLastRow()+1;
for(var im = 2; im < lastRows; im++)
{
var workspacedata = workspacesheet.getRange('B'+im).getValue();
var encodedata = encodeURIComponent(workspacedata);
var cisurl = "https://testapi.com/v1/workspaceDurationMetrics?workspaceId="+encodedata+"&aggregation=daily&measurement=timeUsed&from="+thirtydate+"T00%3A00%3A00.00Z&to="+todaydate+"T00%3A00%3A00.00Z";
var cisss= SpreadsheetApp.getActiveSpreadsheet()
var ciswsLocation = cisss.getSheetByName("HourlyUsed")
var lastRow = ciswsLocation.getLastRow();
var headers = {
"Content-type": "application/json",
"Authorization": `Bearer ${apikey} `
};
var options = {
"method" : "get",
"headers" : headers
};
var response = UrlFetchApp.fetch(cisurl,options);
var cisjson=response.getContentText();
var cisdata=JSON.parse(cisjson);
for(var i = 0; i < cisdata['items'].length; i++)
{
ciswsLocation.getRange(lastRow+1+i,1).setValue([cisdata["workspaceId"]]);
ciswsLocation.getRange(lastRow+1+i,2).setValue(Utilities.formatDate(new Date([cisdata["items"][i]['start']]), "UTC", "yyyy-MM-dd"));
ciswsLocation.getRange(lastRow+1+i,3).setValue([cisdata["items"][i]['duration']]);
}
}
}
Please help me how to reduce time of execution?
Exactly what liqidkat said.
With that, it may look something like this:
function devicedetails() {
/** Variables **/
const apikey ='YWQ0OWFhYjgtNTY2asiHSNSajiasn'
const todaydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24), "UTC", "yyyy-MM-dd")
const thirtydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24*30), "UTC", "yyyy-MM-dd")
/** Sheet Variables **/
const cisss = SpreadsheetApp.getActiveSpreadsheet()
const workspacesheet = cisss.getSheetByName("Device")
const workspaceData = workspacesheet.getRange(2, 2, workspacesheet.getLastRow()-1).getValues().flat()
const ciswsLocation = cisss.getSheetByName("HourlyUsed")
const lastRow = ciswsLocation.getLastRow()
/** Request Handling **/
const allRequests = workspaceData.map(i => {
const encodeData = encodeURIComponent(i)
return {
"url": `https://testapi.com/v1/workspaceDurationMetrics?workspaceId=${encodeData}&aggregation=daily&measurement=timeUsed&from=${thirtydate}T00%3A00%3A00.00Z&to=${todaydate}T00%3A00%3A00.00Z`,
"method": "get",
"headers": {
"Content-type": "application/json",
"Authorization": `Bearer ${apikey}`
}
}
})
/** Response Handling **/
const allResponses = UrlFetchApp.fetchAll(allRequests)
const data = allResponses.map(response => {
const cisjson = response.getContentText()
const cisData = JSON.parse(cisjson)
return cisData[`items`].map(i => [
cisdata["workspaceId"],
Utilities.formatDate(new Date(i['start']), "UTC", "yyyy-MM-dd"),
i['duration']
])
})
/** Set data **/
ciswsLocation.getRange(lastRow+1, 3, data.length, data[0].length).setValues(data)
}
See Also:
Array.map()
UrlFetchApp.fetchAll()
Range.setValues()
I will provide my particular approach to this problem, as I think it may be of interest to the community.
Since the OP has not provided the type of response the API provides (and refers that it is for private use), I will use a public API for the example, Google Books APIs in this case. I will also consider that the calls are made to the same API, so the response is assumed to be identical.
I think the problem can be divided into 4 major steps.
Generate the URLs of the calls (depends on the API).
Get the data via UrlFetchApp.fetchAll(Object)
Normalize the data (this is the most critical step, as it depends on the API response). The main point is to obtain an array of arrays (Object[][]) as required for the next step.
Write the data to the sheet using setValues(Object[][]).
Full example here.
Generate URLs
const generateUrl = (authors) => authors.map(author => `https://books.googleapis.com/books/v1/volumes?q=${author}&country=US`)
Get The Data
const fetchAndNormalizeData = (urlList) => {
const resAll = UrlFetchApp.fetchAll(urlList).map(res => res.getContentText())
return resAll.map(normalizeResponse).flat()
}
Normalize The Data
const normalizeResponse = (res) => {
/* This depends on the RestAPI response */
const { items } = JSON.parse(res)
return items.map((book) => {
const { selfLink, volumeInfo: { title, authors, publishedDate } } = book
const parsedAuthors = authors ? authors.join('\n') : ""
return [title, parsedAuthors, selfLink, publishedDate]
})
}
Write to Sheet
const writeToSheet = (data) => {
sS
.getRange(sS.getLastRow() + 1, 1, data.length, data[0].length)
.setValues(data)
console.log("DATA SAVED")
}
Main function
const SS_ID = "<SS_ID>"
const sS = SpreadsheetApp.openById(SS_ID).getSheetByName('BookData')
const main = () => {
const urlList = generateUrl(["Twain", "Joyce", "Pasternak"])
const data = fetchAndNormalizeData(urlList)
writeToSheet(data)
}
In the case of the OP just have to modify the normalizeResponse (callback for the map function) and generateUrl to adapt it to their needs.
Documentation:
Array.prototype.map()
Array.prototype.flat()
Description
I took the liberty of editing your script to replace all getValue/setValue with getValues/setValues. And I moved all variable that only need to be set once outside the loop. First I get all workspacedata, then in side the loop, index into that array for each row. Next since your results are contiguous in rows and columns, I collect all the results and make one call to setValues to place in the sheet.
Although I am not able to test it since input data is not available I believe it will work and will run much faster.
Although Google has made improvements in it performance of getValue/setValue by caching requests I try to organize my spreadsheets so that I will always use getValues/setValues. Same for other getters and setters.
Script
function devicedetails(){
var apikey='YWQ0OWFhYjgtNTY2asiHSNSajiasn'
var todaydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24), "UTC", "yyyy-MM-dd")
var thirtydate = Utilities.formatDate(new Date(Date.now() - 1000*60*60*24*30), "UTC", "yyyy-MM-dd")
var cisss= SpreadsheetApp.getActiveSpreadsheet();
var workspacesheet = cisss.getSheetByName("Device");
var lastRows = workspacesheet.getLastRow()-1;
var workspacedata = workspacesheet.getRange(2,2,lastRows-1,1).getValues();
var ciswsLocation = cisss.getSheetByName("HourlyUsed")
for(var im = 0; im < lastRows; im++) {
var encodedata = encodeURIComponent(workspacedata[im][0]);
var cisurl = "https://testapi.com/v1/workspaceDurationMetrics?workspaceId="+encodedata+"&aggregation=daily&measurement=timeUsed&from="+thirtydate+"T00%3A00%3A00.00Z&to="+todaydate+"T00%3A00%3A00.00Z";
var lastRow = ciswsLocation.getLastRow();
var headers = {
"Content-type": "application/json",
"Authorization": `Bearer ${apikey} `
};
var options = {
"method" : "get",
"headers" : headers
};
var response = UrlFetchApp.fetch(cisurl,options);
var cisjson=response.getContentText();
var cisdata=JSON.parse(cisjson);
var results = [];
for(var i = 0; i < cisdata['items'].length; i++) {
let row = []
row[0] = cisdata["workspaceId"];
row[1] = Utilities.formatDate(new Date(cisdata["items"][i]['start']), "UTC", "yyyy-MM-dd");
row[2] = cisdata["items"][i]['duration'];
results.push(row);
}
ciswsLocation.getRange(lastRow+1,1,results.length,results[0].length).setValues(results);
}
}
I have a Google Apps Script that will replicate Excel's 'Trace Dependents' function by finding all the dependents of a cell from the entire worksheet, taking into account named ranges. The script works perfectly for small worksheets, unfortunately when working with largish worksheets the script will time out before it manages to complete. I have worksheets with around 1m+ cells and the script sometimes manages to run fully but even then it takes around 5 minutes which is quite long.
Essentially the script works by looping through every formula in the worksheet and performing regex tests on them to see if the formulas include the audited cells reference or name.
I was wondering if there are any quick wins in my script that could help speed up the performance, or if anyone has any suggestions on how to go about improving somehow?
Apologies if this isn't the right place to ask this sort of question, if there is somewhere else I should ask this please let me know.
const getNamedRange = function (actSheet, cell) {
//loop through the sheets named ranges and if the nr's range is the cell, then that name is the name of the current cell
let matchedName;
actSheet.getNamedRanges().forEach((name) => {
if (name.getRange().getA1Notation() === cell) matchedName = name.getName();
});
return matchedName;
};
const isInRange = function (ss, currentCell, stringRange, j, k) {
//extract the sheet name from the range if it has one
const sheetName = stringRange[0].toString().match(/^(.*)!/)
? stringRange[0].toString().match(/^(.*)!/)[1]
: null;
//if there is a sheet name, get the range from that sheet, otherwise just get the range from the active sheet as it will be on the same sheet as audited cell
const range = sheetName
? ss.getSheetByName(sheetName).getRange(stringRange)
: ss.getActiveSheet().getRange(stringRange);
const startRow = range.getRow();
const endRow = startRow + range.getNumRows() - 1;
const startCol = range.getColumn();
const endCol = startCol + range.getNumColumns() - 1;
const cellRow = currentCell.getRow();
const cellCol = currentCell.getColumn();
const deps = [];
if (cellRow >= startRow && cellRow <= endRow && cellCol >= startCol && endCol <= endCol)
deps.push([j, k]);
return deps
};
function traceDependents() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const currentCell = ss.getCurrentCell();
const curCellRef = currentCell.getA1Notation();
const dependentRefs = [];
const sheets = ss.getSheets();
const actSheet = ss.getActiveSheet();
const actSheetName = actSheet.getName();
const actIndex = actSheet.getIndex();
//get the name of the cell
const namedRange = getNamedRange(actSheet, curCellRef);
//get the row and column text from the current cell
const rowText = currentCell.getRow().toString();
const columnText = curCellRef.substring(0, curCellRef.length - rowText.length);
//If the sheet name has a space, then need to add the quote marks and ! as per Google Sheets standard
const formattedActSheetName = actSheetName.includes(" ")
? `'${actSheetName}'!`
: `${actSheetName}!`;
for (let i = 0; i < sheets.length; i++) {
const range = sheets[i].getDataRange();
const formulas = range.getFormulas();
const dependents = [];
//If the sheet is the current sheet, then all references will not have the sheet ref, so it should be blank
const curSheetRef = i === actIndex - 1 ? "" : formattedActSheetName;
//create the tests to see if the formulas include the current cell
const crRegex = new RegExp(
`(?<!!|:)${curSheetRef}${curCellRef}(?![0-9])|` +
`(?<!!|:)${curSheetRef}\\$${curCellRef}(?![0-9])|` +
`(?<!!|:)${curSheetRef}[^$]${columnText}\\$${rowText}(?![0-9])|` +
`(?<!!|:)${curSheetRef}\\$${columnText}\\$${rowText}(?![0-9])`
);
const nrRegex = new RegExp(`(?<!_)${namedRange}(?!_)`);
//run through all of the cells in the sheet and test their formulas with the above to see if they are dependents
for (let j = 0; j < formulas.length; j++) {
const row = formulas[j];
for (let k = 0; k < row.length; k++) {
const cellFormula = row[k];
if (crRegex.test(cellFormula) || nrRegex.test(cellFormula))
dependents.push([j, k]);
//check if the current cell formula includes a range in it, e.g. A1:A20, if it does, create a unique array with all the large ranges
const largeRegex = new RegExp(
`(?<!!|:|\\$)${curSheetRef}\\$?[A-Z]{1,3}\\$?([0-9]{1,7})?:\\$?[A-Z]{1,3}\\$?([0-9]{1,7})?`,
"g"
);
const largeRange = [...new Set(cellFormula.match(largeRegex))];
//if there are any large ranges, check if the range includes the audited cell. If it does, add the cell to the dependents
if (largeRange) {
largeRange.forEach((range) => {
range.replaceAll("$", "");
isInRange(ss, currentCell, range, j, k).forEach((dep) =>
dependents.push(dep)
);
});
}
}
}
//Format the dependent's cell references with their sheet name to allow navigation to them
for (let l = 0; l < dependents.length; l++) {
const cell = range.getCell(dependents[l][0] + 1, dependents[l][1] + 1);
dependentRefs.push(`${sheets[i].getName()}!${cell.getA1Notation()}`);
}
}
//Add the current cell as the first element of the array
dependentRefs.unshift(`${actSheetName}!${curCellRef}`);
return [...new Set(dependentRefs)];
}
I have a list of these items:
hours = ['19:30', '20:10', '20:30', '21:00', '22:00']
Assuming that now it's 20:18, how can I get the '20:10' item from the list? I want to use this to find the currently running show in a TV Guide.
First we should parse it to datetime in some way:
function parseTime(str) {
var values = str.split(':');
var hour = parseInt(values[0]);
var minutes = parseInt(values[1]);
var d = new Date();
d.setHours(hour);
d.setMinutes(minutes);
return d;
}
var now = new Date();
var bestMatch = undefined;
var bestMatchDiff = undefined;
And finally:
for(var i=0; i<hours.length; i++) {
var parsedTime = parseTime(hours[i]);
var diff = Math.abs(now - parsedTime);
if (!bestMatchDiff || bestMatchDiff>diff) {
bestMatch = parsedTime;
bestMatchDiff = diff;
}
}
bestMatch would be the closest time. This is not the currently running show now. For that, you need to ignore times that are yet to come:
for(var i=0; i<hours.length; i++) {
var parsedTime = parseTime(hours[i]);
var diff = Math.abs(now - parsedTime);
if (now<parsedTime) {
continue;
}
if (!bestMatchDiff || bestMatchDiff>diff) {
bestMatch = parsedTime;
bestMatchDiff = diff;
}
}
But keep in mind this might return undefined even if your list is not empty.
var hours = ['19:30', '20:10', '20:30', '21:00', '22:00']
var diffrenceTime
var selectedShow
var d1 = new Date()
var currentHH = 20
var currentMM = 18
d1.setHours(currentHH, currentMM, 0)
hours.forEach(v => {
var d2 = new Date()
var hh = v.split(':')[0]
var mm = v.split(':')[1]
d2.setHours(hh, mm, 0)
if (diffrenceTime == undefined) {
diffrenceTime = d2 - d1
selectedShow = v
}
if (d2 - d1 < 0 && d2 - d1 >= diffrenceTime) {
diffrenceTime = d2 - d1
selectedShow = v
}
})
console.log(selectedShow)
To find the currently running show (of course more validations need to be added):
const currentShow = hours[
hours.findIndex(
(c) => new Date(`01/01/2000 ${c}`) - new Date(`01/01/2000 20:31`) >= 0
) - 1
];
To find the next show:
const nextShow = hours.find(
(c) => new Date(`01/01/2000 ${c}`) - new Date(`01/01/2000 20:31`) >= 0
);
Well, you could do something like this
var originalArray = ['19:30', '20:10', '20:30', '21:00', '22:00'];
var newArray = originalArray.map(i=>{
return i.split(":")
})
newArray.forEach((k, idx)=>{newArray[idx] = parseInt(k[0]) + parseInt(k[1])/60})
console.log(newArray);
var time = "20:18".split(':');
var t = parseInt(time[0])+ parseInt(time[1])/60;
console.log(t);
var closestTimeIndex = 0, closestDistance = Math.abs(t-newArray[closestTimeIndex]);
for(var m=1; m<newArray.length;m++){
if(Math.abs(newArray[m]-t) < closestDistance){
closestDistance = Math.abs(newArray[m]-t);
closestTimeIndex = m;
}
}
console.log("colsest time is: " + originalArray[closestTimeIndex]);