PHPExcel Chart deleted by Microsoft Excel - excel

I'm trying to make a chart on Excel file via PHPExcel..
I can make the table for the datasource for the chart.. Look like this
and I've write code for the chart.. Look like this..
$values = new PHPExcel_Chart_DataSeriesValues('Number', 'Worksheet!$B$1:$B$11', NULL, 11);
$categories = new PHPExcel_Chart_DataSeriesValues('String', 'Worksheet!$A$1:$A$11', NULL, 11);
$series = new PHPExcel_Chart_DataSeries(
PHPExcel_Chart_DataSeries::TYPE_LINECHART, // plotType
PHPExcel_Chart_DataSeries::GROUPING_STANDARD, // plotGrouping
range(0, count($values)-1), // plotOrder
array(), // plotLabel
array($categories), // plotCategory
array($values) // plotValues
);
$series->setPlotDirection(PHPExcel_Chart_DataSeries::DIRECTION_COL);
$plotarea = new PHPExcel_Chart_PlotArea(NULL, array($series));
$legend = new PHPExcel_Chart_Legend(PHPExcel_Chart_Legend::POSITION_RIGHT, NULL,false);
$title = new PHPExcel_Chart_Title('Test Column Chart');
$yAxisLabel = new PHPExcel_Chart_Title('Value ($k)');
$chart = new PHPExcel_Chart(
'chart1', // name
$title, // title
$legend, // legend
$plotarea, // plotArea
true, // plotVisibleOnly
0, // displayBlanksAs
NULL, // xAxisLabel
$yAxisLabel // yAxisLabel
);
$objPHPExcel->getActiveSheet()->addChart($chart);
$objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel);
$objWriter->setIncludeCharts(TRUE);
header('Content-type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="aOSalesPerformance.xlsx"');
$objWriter->save('php://output');
And this is the error that excel give me when I'm trying to open the document that generated by that code
Why this error came ? Why Excel say Drawing Shape has been Removed ?
Any help appriciated

The primary reason you received that error is because you over-selected the plot values and included the header. For example with these two lines the range should start at A2 and B2, since A1 and B1 contain the header and NOT values you want plotted.
So these two lines:
$values = new PHPExcel_Chart_DataSeriesValues('Number', 'Worksheet!$B$1:$B$11', NULL, 11);
$categories = new PHPExcel_Chart_DataSeriesValues('String', 'Worksheet!$A$1:$A$11', NULL, 11);
Should be:
$values = new PHPExcel_Chart_DataSeriesValues('Number', 'Worksheet!$B$2:$B$11', NULL, 11);
$categories = new PHPExcel_Chart_DataSeriesValues('String', 'Worksheet!$A$2:$A$11', NULL, 11);
That will take care of the error, but your chart will still not be displayed since you also need to specify a location for said chart to be displayed. So these two extra lines will also need to be added:
$chart->setTopLeftPosition('A20');
$chart->setBottomRightPosition('L50');
That will take care of the error you are receiving and the lack of displayed graph. However I would recommend creating the array for $values and $categories outside of creating the $series object as that would allow for plotting more than just one column of data. This website has a good tutorial on creating charts using PHPExcel
Corrected code below:
$values = array();
$values[] = new PHPExcel_Chart_DataSeriesValues('Number', 'Worksheet!$B$2:$B$11', NULL, 11);
$categories = array();
$categories[] = new PHPExcel_Chart_DataSeriesValues('String', 'Worksheet!$A$2:$A$11', NULL, 11);
$series = new PHPExcel_Chart_DataSeries(
PHPExcel_Chart_DataSeries::TYPE_LINECHART, // plotType
PHPExcel_Chart_DataSeries::GROUPING_STANDARD, // plotGrouping
range(0, count($values)-1), // plotOrder
null, // plotLabel
$categories, // plotCategory
$values // plotValues
);
$series->setPlotDirection(PHPExcel_Chart_DataSeries::DIRECTION_COL);
$plotarea = new PHPExcel_Chart_PlotArea(NULL, array($series));
$legend = new PHPExcel_Chart_Legend(PHPExcel_Chart_Legend::POSITION_RIGHT, NULL,false);
$title = new PHPExcel_Chart_Title('Test Column Chart');
$k="y axis";
$yAxisLabel = new PHPExcel_Chart_Title('Value ($k)');
$chart = new PHPExcel_Chart(
'chart1', // name
$title, // title
$legend, // legend
$plotarea, // plotArea
true, // plotVisibleOnly
0, // displayBlanksAs
NULL, // xAxisLabel
$yAxisLabel // yAxisLabel
);
//Location where chart will be displayed
$chart->setTopLeftPosition('A20');
$chart->setBottomRightPosition('L50');
$objPHPExcel->getActiveSheet()->addChart($chart);
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
$objPHPExcel->setActiveSheetIndex(0);
// Redirect output to a client’s web browser (Excel2007)
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="aOSalesPerformance.xlsx"');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->setIncludeCharts(TRUE);
$objWriter->save('php://output');

Related

PDFKit split text into two equal columns while using for loop

Im trying to use PDFKit to generate a simple pdf, for the most part the pdf works but albeit in a very non useful way, what i have is a deck building API that takes in a number of cards, each of these objects i want to export to a pdf, its as simple as displaying their name, but as it is, the pdf only renders one card at a time, and only on one line, what id like to happen is to get it to split the text into columns so itd look similar to this.
column 1 | column 2
c1 c8
c2 c9
c3 c10
c4 c(n)
here is my code so far,
module.exports = asyncHandler(async (req, res, next) => {
try {
// find the deck
const deck = await Deck.findById(req.params.deckId);
// need to sort cards by name
await deck.cards.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
// Create a new PDF document
const doc = new PDFDocument();
// Pipe its output somewhere, like to a file or HTTP response
doc.pipe(
fs.createWriteStream(
`${__dirname}/../../public/pdf/${deck.deck_name}.pdf`
)
);
// Embed a font, set the font size, and render some text
doc.fontSize(25).text(`${deck.deck_name} Deck List`, {
align: "center",
underline: true,
underlineColor: "#000000",
underlineThickness: 2,
});
// We need to create two columns for the cards
// The first column will be the card name
// The second column will continue the cards listed
const section = doc.struct("P");
doc.addStructure(section);
for (const card of deck.cards) {
doc.text(`${card.name}`, {
color: "#000000",
fontSize: 10,
columns: 2,
columnGap: 10,
continued: true,
});
}
section.end();
// finalize the PDF and end the response
doc.end();
res.status(200).json({ message: "PDF generated successfully" });
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: `Server Error - ${error.message}`,
});
}
});
At Present this does generate a column order like i want, however theres and extreme caveat to this solution and that is, if the card text isnt very long, the next card will start on that same line, it'd be useful if i could find a way to make the text take up the full width of that row, but i havent seen anything to do that with.
I think the problem is that you're relying on PDFKit's text "flow" API/logic, and you're having problems when two cards are not big enough to flow across your columns and you get two cards in one column.
I'd say that what you really want is to create a table—based on your initial text sample.
PDFKit doesn't have a table API (yet), so you'll have to make one up for yourself.
Here's an approach where you figure out the dimensions of things:
the page size
the size of your cells of text (either manually choose for yourself, or use PDFKit to tell you how big some piece of text is)
margins
Then you use those sizes to calculate how many rows and columns of your text can fit on your page.
Finally you iterate of over columns then rows for each page, writing text into those column-by-row "coordinates" (which I track through "offsets" and use to calculate the final "position").
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Create mock-up Cards for OP
const cards = [];
for (let i = 0; i < 100; i++) {
cards.push(`Card ${i + 1}`);
}
// Set a sensible starting point for each page
const originX = 50;
const originY = 50;
const doc = new PDFDocument({ size: 'LETTER' });
// Define row height and column widths, based on font size; either manually,
// or use commented-out heightOf and widthOf methods to dynamically pick sizes
doc.fontSize(24);
const rowH = 50; // doc.heightOfString(cards[cards.length - 1]);
const colW = 150; // doc.widthOfString(cards[cards.length - 1]); // because the last card is the "longest" piece of text
// Margins aren't really discussed in the documentation; I can ignore the top and left margin by
// placing the text at (0,0), but I cannot write below the bottom margin
const pageH = doc.page.height;
const rowsPerPage = parseInt((pageH - originY - doc.page.margins.bottom) / rowH);
const colsPerPage = 2;
var cardIdx = 0;
while (cardIdx < cards.length) {
var colOffset = 0;
while (colOffset < colsPerPage) {
const posX = originX + (colOffset * colW);
var rowOffset = 0;
while (rowOffset < rowsPerPage) {
const posY = originY + (rowOffset * rowH);
doc.text(cards[cardIdx], posX, posY);
cardIdx += 1;
rowOffset += 1;
}
colOffset += 1;
}
// This is hacky, but PDFKit adds a page by default so the loop doesn't 100% control when a page is added;
// this prevents an empty trailing page from being added
if (cardIdx < cards.length) {
doc.addPage();
}
}
// Finalize PDF file
doc.pipe(fs.createWriteStream('output.pdf'));
doc.end();
When I run that I get a PDF with 4 pages that looks like this:
Changing colW = 250 and colsPerPage = 3:

Using sheetjs to style form headers - where do I add cssStyles to the tabulator xlsx download function?

I am trying to make the headers of the exported xlsx tabulator table bold using sheetjs.
They provide documentation to style these fields and say to use cssStyles: true, but where do I add this in the download for function using tabulator?
There's not much documentation on how to get these to work with eachother even though it is recommended!
Any help would be amazing!!
table.download("xlsx", "data.xlsx",{
documentProcessing:function(workbook){
//workbook - sheetJS workbook object
cssStyles: true, <--- Would I add it here??
let ws = workbook.Sheets[workbook.SheetNames[0]]
// Reset range to be correct!
function update_sheet_range(ws) {
let range = {s:{r:Infinity, c:Infinity},e:{r:0,c:0}};
Object.keys(ws).filter(function(x) { return x.charAt(0) !== '!'; }).map(XLSX.utils.decode_cell).forEach(function(x) {
range.s.c = Math.min(range.s.c, x.c); range.s.r = Math.min(range.s.r, x.r)
range.e.c = Math.max(range.e.c, x.c); range.e.r = Math.max(range.e.r, x.r)
});
ws['!ref'] = XLSX.utils.encode_range(range)
}
update_sheet_range(ws)
// Now set range to be correct range!
let range = XLSX.utils.decode_range(ws['!ref'])
// Freeze top row
ws['!freeze'] = 'A2'
// Add filters to headers
ws['!autofilter'] = { ref: range }
// Make all columns auto width
if (!ws['!cols']) ws['!cols'] = []
for (let C = 0; C <= 16383; ++C) { // 0 = "A", 16383 = "XFD"
if(!ws['!cols'][C]) ws['!cols'][C] = { auto: 1 } // default width
}
// Bold the headers - THIS ISN'T WORKING :(
range.s.r = 0; range.e.r = 0; // restrict to the first row
XLSX.utils.sheet_set_range_style(ws, "A1:B1", {
bold: true
cellStyles: true, <--- Or do I add it here?
})
console.log(ws)
return workbook
}
});

Create Recuring appointment by Lotus Notes

Can somebody tell me what is wrong here. I am trying to create a recurring appointment; however, the code creates something different (type is not Appointment) and, if I select one created element, it throws an error:
Falscher Datentyp für Operator oder #Funktion: Text erwartet
void Main()
{
var session = new NotesSessionClass();
session.Initialize("");
var mailFile = session.GetEnvironmentString("MailFile", true);
var server = session.GetEnvironmentString("MailServer", true);
NotesDatabase database = session.GetDatabase("", mailFile, false);
if(!database.IsOpen)
database.Open();
//Normal Meeting
NotesDocument document = database.CreateDocument();
UpdateAppointment(document, session);
//Repeating Meeting
DateTime appointmentStart = new DateTime(2020, 5, 13, 15, 0, 0);
DateTime appointmentEnd = new DateTime(2020, 5, 13, 16, 0, 0);
List<DateTime> repeatStart = new List<DateTime>(){ appointmentStart, appointmentStart.AddDays(1), appointmentStart.AddDays(2), appointmentStart.AddDays(3) };
List<DateTime> repeatEnd = new List<DateTime>(){ appointmentEnd, appointmentEnd.AddDays(1), appointmentEnd.AddDays(2), appointmentEnd.AddDays(3) };
document.ReplaceItemValue("Repeats", 1);
document.ReplaceItemValue("OrgRepeat", 1);
document.ReplaceItemValue("$CSFlags", "i");
NotesDocument repeatingMaster = database.CreateDocument();
UpdateAppointment(repeatingMaster, session);
repeatingMaster.ReplaceItemValue("Repeats", 1);
repeatingMaster.ReplaceItemValue("OrgRepeat", 1);
repeatingMaster.ReplaceItemValue("$CSFlags", "c");
repeatingMaster.ReplaceItemValue("RepeatStartDate", appointmentStart);
repeatingMaster.ReplaceItemValue("RepeatHow", "F");
repeatingMaster.ReplaceItemValue("RepeatFor", 4);
repeatingMaster.ReplaceItemValue("RepeatForUnit", "D");
repeatingMaster.ReplaceItemValue("RepeatUnit", "D");
repeatingMaster.ReplaceItemValue("RepeatInterval", 1);
repeatingMaster.ReplaceItemValue("RepeatDates", repeatStart.ToArray());
repeatingMaster.ReplaceItemValue("RepeatInstanceDates", repeatStart.ToArray());
repeatingMaster.ReplaceItemValue("RepeatEndDates", repeatEnd.ToArray());
repeatingMaster.ReplaceItemValue("RepeatUntil", repeatEnd.Last());
repeatingMaster.ReplaceItemValue("StartDateTime", repeatStart.First());
repeatingMaster.ReplaceItemValue("EndDateTime", repeatEnd.First());
repeatingMaster.ReplaceItemValue("StartTimeZone", "Z=-1$DO=1$DL=3 -1 1 10 -1 1$ZX=90$ZN=Romance");
repeatingMaster.ReplaceItemValue("EndTimeZone", "Z=-1$DO=1$DL=3 -1 1 10 -1 1$ZX=90$ZN=Romance");
repeatingMaster.ReplaceItemValue("ApptUNID", repeatingMaster.UniversalID);
repeatingMaster.ComputeWithForm(false, false);
repeatingMaster.Save(true, false);
document.ReplaceItemValue("CalendarDateTime", repeatStart.ToArray());
document.ReplaceItemValue("StartDateTime", repeatStart.ToArray());
document.ReplaceItemValue("EndDateTime", repeatEnd.ToArray());
document.ReplaceItemValue("RepeatInstanceDates", repeatStart.ToArray());
document.ReplaceItemValue("StartTimeZone", "Z=-1$DO=1$DL=3 -1 1 10 -1 1$ZX=90$ZN=Romance");
document.ReplaceItemValue("EndTimeZone", "Z=-1$DO=1$DL=3 -1 1 10 -1 1$ZX=90$ZN=Romance");
document.ReplaceItemValue("$Ref", repeatingMaster.UniversalID);
document.ReplaceItemValue("$RefOptions", 1);
document.ReplaceItemValue("ApptUNID", repeatingMaster.UniversalID);
document.ComputeWithForm(false, false);
document.Save(true, false);
}
Method to update appointment document:
void UpdateAppointment(NotesDocument document, NotesSession session)
{
document.ReplaceItemValue("Form", "Appointment");
document.ReplaceItemValue("$CSVersion", 2);
document.ReplaceItemValue("Subject", "Subject");
document.ReplaceItemValue("Body", "Body");
document.ReplaceItemValue("AppointmentType", 3);
document.ReplaceItemValue("Chair", session.UserName);
document.ReplaceItemValue("Principal", session.UserName);
document.ReplaceItemValue("From", session.UserName);
document.ReplaceItemValue("SequenceNum", 1);
document.ReplaceItemValue("RequiredAttendees", "test#required.attendee");
document.ReplaceItemValue("Location", "Location");
document.ReplaceItemValue("$Alarm", 1);
document.ReplaceItemValue("Alarms", 1);
document.ReplaceItemValue("$AlarmOffset", -15);
document.ReplaceItemValue("$BusyName", session.UserName);
document.ReplaceItemValue("$BusyPriority", 1);
document.ReplaceItemValue("$PublicAccess", 1);
document.ReplaceItemValue("Importance", 1);
document.ComputeWithForm(false, false);
document.Save(true, false);
}
I tried already all ways to find the error but I am a newbie in Lotus Notes and there is not enough documentation (or I can not find it).
Creating appointments in LotusScript is already not easy. But creating repeating appointments is very advanced stuff, as they are NOT single documents to create but always combinations of ONE main document and at least ONE response document.
To get this right you need to create a bunch of fields with the right data type and content.
In your example at least the Fields StartDate, StartTime, EndDate, EndTime and CalendarDate... are missing. your „Computewithform“ tries to calculate all missing fields from the used forms. But they need some fields and they need to be of the right type, otherwise you get #Formula- errors as the one you have (in your case it might be because you set Repeats as Number, but it should be a Text, but his was just the first wrong one that I saw in your code, there might and will be others)
There is a PDF document called Calendaring & Scheduling Schema that is quite old (2006) but still holds true: it describes how to programmatically create calendar entries and which fields you need and what they mean... read it, understand it, use it.
It’s the only way to get valid calendar entries without guessing or copying an existing document...

Google Apps Script - Exporting events from Google Sheets to Google Calendar - how can I stop it from removing my spreadsheet formulas?

I've been trying to create a code that takes info from a Google Spreadsheet, and creates Google Calendar events. I'm new to this, so bear with my lack of in-depth coding knowledge!
I initially used this post to create a code:
Create Google Calendar Events from Spreadsheet but prevent duplicates
I then worked out that it was timing out due to the number of rows on the spreadsheet, and wasn't creating eventIDs to avoid the duplicates. I got an answer here to work that out!
Google Script that creates Google Calendar events from a Google Spreadsheet - "Exceeded maximum execution time"
And now I've realised that it's over-writing the formulas, I have in the spreadsheet, auto-completing into each row, as follows:
Row 12 - =if(E4="","",E4+1) // Row 13 - =if(C4="","",C4+1) // Row 18 - =if(B4="","","WHC - "&B4) // Row 19 - =if(B4="","","Docs - "&B4)
Does anyone have any idea how I can stop it doing this?
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the exportEvents() function.
* 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 : "Export WHCs",
functionName : "exportWHCs"
},
{
name : "Export Docs",
functionName : "exportDocs"
}];
sheet.addMenu("Calendar Actions", entries);
};
/**
* Export events from spreadsheet to calendar
*/
function exportWHCs() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index and a total counter for processed items
// else continue the task
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':0});
ScriptApp.newTrigger('exportWHCs').timeBased().everyMinutes(5).create();
}
// initialize all variables when we start a new task, "notFinished" is the main loop condition
var itemsProcessed = Number(PropertiesService.getScriptProperties().getProperty('itemsprocessed'));
var startTime = new Date().getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 4; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "flightcentre.com.au_pma5g2rd5cft4lird345j7pke8#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[12]); // First column
var title = row[18]; // Second column
var tstart = new Date(row[15]);
tstart.setDate(date.getDate());
tstart.setMonth(date.getMonth());
tstart.setYear(date.getYear());
var tstop = new Date(row[16]);
tstop.setDate(date.getDate());
tstop.setMonth(date.getMonth());
tstop.setYear(date.getYear());
var id = row[17]; // Sixth column == eventId
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
if (!event) {
//cal.createEvent(title, new Date("March 3, 2010 08:00:00"), new Date("March 3, 2010 09:00:00"));
var newEvent = cal.createEvent(title, tstart, tstop).addEmailReminder(5).getId();
row[17] = newEvent; // Update the data array with event ID
}
else {
event.setTitle(title);
}
if(new Date().getTime()-startTime > 240000){ // if > 4 minutes
var processed = i+1;// save usefull variable
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':processed});
range.setValues(data);
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'progress sheet to cal','item processed : '+processed);
return;
}
debugger;
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
/**
* Export events from spreadsheet to calendar
*/
function exportDocs() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index and a total counter for processed items
// else continue the task
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':0});
ScriptApp.newTrigger('exportDocs').timeBased().everyMinutes(5).create();
}
// initialize all variables when we start a new task, "notFinished" is the main loop condition
var itemsProcessed = Number(PropertiesService.getScriptProperties().getProperty('itemsprocessed'));
var startTime = new Date().getTime();
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 4; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "flightcentre.com.au_pma5g2rd5cft4lird345j7pke8#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[13]); // First column
var title = row[19]; // Second column
var tstart = new Date(row[15]);
tstart.setDate(date.getDate());
tstart.setMonth(date.getMonth());
tstart.setYear(date.getYear());
var tstop = new Date(row[16]);
tstop.setDate(date.getDate());
tstop.setMonth(date.getMonth());
tstop.setYear(date.getYear());
var id = row[20]; // Sixth column == eventId
// Check if event already exists, update it if it does
try {
var event = cal.getEventSeriesById(id);
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
if (!event) {
//cal.createEvent(title, new Date("March 3, 2010 08:00:00"), new Date("March 3, 2010 09:00:00"));
var newEvent = cal.createEvent(title, tstart, tstop).addEmailReminder(5).getId();
row[20] = newEvent; // Update the data array with event ID
}
else {
event.setTitle(title);
}
if(new Date().getTime()-startTime > 240000){ // if > 4 minutes
var processed = i+1;// save usefull variable
PropertiesService.getScriptProperties().setProperties({'itemsprocessed':processed});
range.setValues(data);
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'progress sheet to cal','item processed : '+processed);
return;
}
debugger;
}
// Record all event IDs to spreadsheet
range.setValues(data);
}
You have to ways to solve that problem.
First possibility : update your sheet with array data only on columns that have no formulas, proceeding as in this other post but in your case (with multiple columns to skip) it will rapidly become tricky
Second possibility : (the one I would personally choose because I 'm not a "formula fan") is to do what your formulas do in the script itself, ie translate the formulas into array level operations.
following your example =if(E4="","",E4+1) would become something like data[n][4]=data[n][4]==''?'':data[n+1][4]; if I understood the logic (but I'm not so sure...).
EDIT
There is actually a third solution that is even simpler (go figure why I didn't think about it in the first place...) You could save the ranges that have formulas, for example if col M has formulas you want to keep use :
var formulM = sheet.getRange('G1:G').getFormulas();
and then, at the end of the function (after the global setValues()) rewrite the formulas using :
sheet.getRange('G1:G').setFormulas(formulM);
to restore all the previous formulas... as simple as that, repeat for every column where you need to keep the formulas.

How to use D3 force layout with existing SVG elements as nodes

I have a javascript array of objects where each object has .ui attribute that contains an existing SVG shape/element of various kinds ( a self contained "g" element with others inside ).
I want to arrange these objects as a force graph layout in D3. So, instead of having d3 create circles (or whatever) I want to assign one of my ui objects to the graph.
I have created a simplified fiddle << HERE >> for that. The objects that are pre-existing and should be part of the graph are the 3 colored rectangles. In this example I failed to achieve that. The d3 append command can be used just to construct shapes/elements, but not to append an already constructed one. I was trying to do this, and it cannot work in D3:
node.append( function(d) { d.ui.node(); }) ...
... as mentioned, this wont work as d3 does not append elements.
var svgContainer = d3.select("#svgContainer");
// =============================================================
// creates the SVG elements to be used ( 3 colored rectangles )
// =============================================================
var element0a = svgContainer.append("g").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");
var element1a = svgContainer.append("g").attr("transform","translate(100,200)");
var element1b = element1a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","green");
var element2a = svgContainer.append("g").attr("transform","translate(100,300)");
var element2b = element2a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","blue");
// =============================================================
// fills up an object array that contains details plus the UI attribute
// =============================================================
var nodeArray = new Array();
nodeArray[0] = { id : "000", label : "label 000", ui : element0a };
nodeArray[1] = { id : "001", label : "label 001", ui : element1a };
nodeArray[2] = { id : "002", label : "label 002", ui : element2a };
// not interested in dealing with the edge/link right now, just empty
var linkArray = new Array();
// =============================================================
// D3 force layout stuff
// =============================================================
var force = self.force = d3.layout.force()
.nodes(nodeArray)
.links(linkArray)
.gravity(.05)
.distance(80)
.charge(-100)
.size([600, 600])
.start();
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element.
node.append("circle") // <---
.attr("class", "node")
.attr("r",10)
.attr("fill","orange")
.call(force.drag);
force.on("tick", function() {
node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
});
Based on advice from Lars Kotfoff :
Working fiddle here
If the elements to be graph-ed already exist, simply skip the enter() D3 phase and use select() or selectAll() based on a common characteristic of your elements ( a class name for instance ).
This is how the element got created ( added a specific class for allowing an isolated selection ):
var element0a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");
This is thene part of the code replacing the enter() phase
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.call(force.drag);
This is what was removed
var node = svgContainer.selectAll("g.node")
.data(nodeArray)
.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element.
node.append("circle") // <---
.attr("class", "node")
.attr("r",10)
.attr("fill","orange")
.call(force.drag);
The problem is that you append the elements to svgContainer to create them, thus they are already attached to something.
What I would suggest as a workaround is to store the element properties in a json file and then read the configuration from the json.
There might be something more elegant.

Resources