Google Sheets - If statement to populate cell - if-statement

It feels like this should be really easy, but I keep getting errors related to circular logic.
Column C "Total" will always be entered by the user first. If user enters number in Column B "Variable" then Column A "Fixed" will be populated with C - B. If user enters number in Column A "Fixed", then Column B "Variable" will be populated with C - A.
https://docs.google.com/spreadsheets/d/1xBbU6A_MDK6fyLjdFUD7X06b7BQ1VhQ-FWQBET4cLso/edit?usp=sharing

You are trying to add formulas that will always need to rely on eachother to produce an output and as result of that, it will run into a Circualr Dependency error.
Possible solution:
Try using the "Iterative Calculation" option under File –> Spreadsheet Settings –> Calculation. You can see the description for Iterative Calculation here.

Here is one way to avoid circular references: do not hand enter any formulas, but use an onEdit() script to insert formulas programmatically only when necessary.
The following script will enter a formula in column B when column A is edited, and vice versa:
function onEdit(e) {
if (!e) {
throw new Error('Please do not run the script in the script editor window. It runs automatically when you hand edit the spreadsheet.');
}
const watchSheet = /^(Sheet1|Sheet2|Sheet3|Sheet4)$/i;
const watchColumns = [
{
colNumber: 1,
formula: '=C:C - B:B',
},
{
colNumber: 2,
formula: '=C:C - A:A',
},
];
const sheet = e.range.getSheet();
if (!sheet.getName().match(watchSheet)) {
return;
}
const editedColumn = watchColumns.filter(column => column.colNumber === e.range.columnStart)[0];
if (!editedColumn) {
return;
}
const updateColumns = watchColumns.filter(column => column.colNumber !== editedColumn.colNumber);
updateColumns.forEach(column => {
sheet
.getRange(e.range.rowStart, column.colNumber)
.setFormula(column.formula);
});
}

Related

ChartJS v3.X - Limit the string size of label on canvas, without changing tooltip hover string

So, I had some old charts code using a very old Chart.js version 2.x but now due to the need of some functions only found on versions 3.x I'm updating a lot of code from the old charts on the website because a lot of the syntax changed between versions.
I just finished updating all charts and they are working nicely using plugins and etc, except for one.
On this type: 'bar' chart I have some very long strings on the 'X' axis, so I need to trim them to a max of 12 characters, but the tooltips need to show the whole string. The old code used to be this one and worked like a charm, right before rendering the chart I would set the updateScaleDefaults:
// Limit the size of label for 12 characters, tooltip on hover not changed
Chart.scaleService.updateScaleDefaults('category', {
ticks: {
callback: function(tick) {
var characterLimit = 12;
if ( tick.length >= characterLimit) {
return tick.slice(0, tick.length).substring(0, characterLimit -1).trim() + '...';;
}
return tick;
}
}
});
So right after that I would call the new Chart instance and would render it on a canvas. But now this code doesn't work anymore on v3.x, and I can't make an alternative version of this work.
As the migration guide for V3.x states on the documentation ,
Chart.scaleService was replaced with Chart.registry. Scale defaults are now in Chart.defaults.scales[type]
So I tried changing the first line of the code above to this and many other variations, trying to reuse the old code just changing the call to the object:
Chart.defaults.scales['category']
Nothing I tried worked.
I then tried creating a plugin with an beforeDraw, acessing the chart.scales.x.ticks and trying to make an arrow function on a map call, but I got a $context is a object error like this:
const pluginLimitTitle = {
beforeDraw: (chart) => {
console.log(chart.scales.x.ticks);
chart.scales.x.ticks = chart.scales.x.ticks.map(function (tick) {
var characterLimit = 12;
if (tick['label'].length >= characterLimit) {
return tick['label'].slice(0, tick['label'].length).substring(0, characterLimit - 1).trim() + '...';
;
}
return tick;
});
}
I also tried putting the ticks callback inside the options on the chart creation on options: scales: x: ticks but it did not work either way.
Can someone help me make this on v3.x?
Spent the whole day trying many things and don't look I'm getting closer to make it work.
After wasting many hours I found a "Tip" highlight on the documentation that should be in the examples, and not just badly highlighted on the "Labeling Axes" page.
When you do a callback for ticks on the chart options settings, you get 3 params to call:
function(value, index, ticks)
I tried in many ways to change the last one because it is the array of all ticks with their labels, and it is the only one where the label string appears so I was trying to modify it.
You'd think at first that the "value" parameter is the one to be changed, but it returns the exactly same integer value as the "index" parameter on each callback iteration, so I thought the only one the I could manipulate to change the string was the "ticks" array, but I was completely wrong.
You actually need to call a special function called getLabelForValue().
The working code ended up like this:
const configTotal = {
type: 'bar',
data: dataTotal,
options: {
scales: {
y: {
beginAtZero: true
},
x: {
ticks: {
callback: function(value, index, ticks_array) {
let characterLimit = 12;
let label = this.getLabelForValue(value);
if ( label.length >= characterLimit) {
return label.slice(0, label.length).substring(0, characterLimit -1).trim() + '...';
}
return label;
}
}
}
}
},
};
I hope this helps anyone having the same problem as me.
Maybe all the time I wasted is all on me for not reading every single line with more patience, but in my opinion, this documentation lacks a lot more example codes for accessing properties and callback parameters and it's object values, the way it is just showing the type of object and returns of each class method call, makes it very confusing for non-experienced on chart.js users.

Google Sheets If Function Not running when true

I wanted to add a button to a report in google sheets. The button works but you can not use it on a phone so I'm told you must use onEdit. The problem I'm having is to only make changes when the certain box is edited, but when it is the correct box it seems google freezes and doesn't run the script.
I've already tried with my if as (row = 8) , (Row == 8) and (Row == "8").
function onEdit(evt) {
var range = evt.range;
var row = range.getRow().toString();
Logger.log("line 1");
Logger.log("edited!! Row: " + row + " and column: " + range.getColumn ());
if (row == "8"){ // && range.getColumn() = 6 ){ (This bit is for after I figure the row issue out.
Logger.Log("right");
} else {// esstsgs
Logger.log("Wasnt the right cell");
}
Logger.log("Done the thing");
}
So with that code, if I edit any row but 8 my log looks like:
line 1 edited!!
Row x and Column x
done the thing
But if it is row 8 my log says:
line 1
Edited!! Row x and Column x
And nothing more, when is it freezing in the if statement?
Let me know what you would really like to do with this and I'll set it up for you. For now hopefully this will help you to figure the logic.
I don't use Logger.log() for debugging this kind of code. Instead I use the e.source.toast() function.
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Sheet189')return;//this is important so that you don't get edits from unwanted pages
//e.source.toast('Flag1');
if(e.range.rowStart==8 && e.range.columnStart==6) {
var msg=Utilities.formatString('You are editing %s.',sh.getRange(e.range.rowStart,e.range.columnStart).getA1Notation());
SpreadsheetApp.getUi().alert(msg);
}
if(e.range.rowStart!=8 || e.range.columnStart!=6) {
var msg=Utilities.formatString('You are not editing F8. Instead you are editing %s.',sh.getRange(e.range.rowStart,e.range.columnStart).getA1Notation());
SpreadsheetApp.getUi().alert(msg);
}
}
You will probably want to change the sheet number before running this. I didn't set up the Logger.log because I don't like to use it for this kind of code. I did test this and it works fine.
I'd like to point out some mistakes in your code first of all.
You don't need to make the Row variable a string. var row = range.getRow(); is just fine.
You are always Logging "Line 1", why not update it to actually say which line was updated? Logger.log("line " + row);
Your code will output "Done the thing" regardless of what it did. Maybe just say it did anything when it's the right box?
Your fixed code would look like:
function onEdit(evt) {
var range = evt.range;
var row = range.getRow();
var col = range.getColumn();
Logger.log("line " + row + "edited!! Row: " + row + " and column: " + col);
if (row == 8 && col == 6 ) {
Logger.log("Right cell. Did the thing.");
//Call here whatever you want to trigger when this cell changes.
} //Don't need a "else" if you are not going process anything
}
Hope this helps!

custom reduce functions in crossfilter on 2 fields

My data looks like this
field1,field2,value1,value2
a,b,1,1
b,a,2,2
c,a,3,5
b,c,6,7
d,a,6,7
The ultimate goal is to get value1+value2 for each distinct value of field1 and field2 : {a:15(=1+2+5+7),b:9(=1+2+6),c:10(=3+7),d:6(=6)}
I don't have a good way of rearranging that data so let's assume the data has to stay like this.
Based on this previous question (Thanks #Gordon), I mapped using :
cf.dimension(function(d) { return [d.field1,d.field2]; }, true);
But I am left a bit puzzled as to how to write the custom reduce functions for my use case. Main question is : from within the reduceAdd and reduceRemove functions, how do I know which key is currently "worked on" ? i.e in my case, how do I know whether I'm supposed to take value1 or value2 into account in my sum ?
(have tagged dc.js and reductio because it could be useful for users of those libraries)
OK so I ended up doing the following for defining the group :
reduceAdd: (p, v) => {
if (!p.hasOwnProperty(v.field1)) {
p[v.field1] = 0;
}
if (!p.hasOwnProperty(v.field2)) {
p[v.field2] = 0;
}
p[v.field1] += +v.value1;
p[v.field2] += +v.value2;
return p;
}
reduceRemove: (p, v) => {
p[v.field1] -= +v.value1;
p[v.field2] -= +v.value2;
return p;
}
reduceInitial: () => {
return {}
}
And when you use the group in a chart, you just change the valueAccessor to be (d) => d.value[d.key] instead of the usual (d) => d.value
Small inefficicency as you store more data than you need to in the value fields but if you don't have millions of distinct values it's basically negligible.
you always have a good way to re-arrange the data, after you have fetched it and before you feed it to crossfilter ;)
In fact, it's pretty much mandatory as soon as you handle non string fields (numeric or date)
You can do a reduceSum on multiple fields
dimensions.reduceSum(function(d) {return +d.value1 + +d.value2; });

If the cell contains the word "xxx" then do "xxx"

I am working on a spreadsheet and I am trying to create a script, but I don't know how to do this:
I want to write an if statement where if the last row of column B contains the word, let's say "flower", then do something.
Can you please help me?
Thank you in advance.
Here is a working example of how you can use Google Scripts to do an if statement. Paste this into the script editor, then run it. If A1 has the value 1, then it will change B2 to "Yay!", otherwise B2 will be "Really?!"
function myFunction()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A1"); //gets a cell to test the value of
if (range.getValue == 1)
{
sheet.getRange("B2").setValue("Yay!") //sets the value of B2 if true
}
else
{
sheet.getRange("B2").setValue("Really?!") //sets the value of B2 if false
}
}
You can also add in else if if you want to add additional conditions, then whatever manipulation or function you want to do needs to inside the brackets

Comparing Substrings to JList Strings

In advance, please forgive me if I do not give adequate background information for my question. Long time reader, first time asker.
I am making a program where one has a database of cars accessed through a tab delimited .txt file (we did something like this recently in my programming class, so I wanted to expand upon it).
Instead of using the terminal window, my format is displaying the Car objects (containing make, model, year, price, etc.) in ArrayList. I'm using JFrame, a JList, and a ListModel since I'm using an array of Car objects.
In my program, I wanted to create a delete method where the user could delete items from the database. Initially they would select the item from the JList and then would click on the delete button. This invokes the delete() method, which is the tab shown below...
void delete()
{
int i = list.getSelectedIndex();
String string = (String)listModel.getElementAt(i);
for(Car c : cars)
{
String year = String.valueOf(c.getYear());
String conditionReport = String.valueOf(c.getConditionReport());
String price = String.valueOf(c.getPrice());
if(c.getMake().indexOf(string) != -1 && c.getModel().indexOf(string) != -1 && year.indexOf(string) != -1 && conditionReport.indexOf(string) != -1 && price.indexOf(string) != -1 && c.getWarranty().indexOf(string) != -1 && c.getComments().indexOf(string) != -1)
{
int choice = JOptionPane.showConfirmDialog(null, "Are you sure you would like to remove the " + cars.get(i).getYear() + " " + cars.get(i).getMake() + " " + cars.get(i).getModel() + "?", "Choose One", JOptionPane.YES_NO_OPTION);
if(choice == JOptionPane.NO_OPTION || choice == JOptionPane.CLOSED_OPTION)
{
return;
} else
{
cars.remove(c);
listModel.removeElementAt(i);
}
}
}
writeFile();
}
I have pinpointed my issue to be inside the if statement. (I printed things before and after to try to find where the program is lying. 'list' is my JList and 'listmodel' is my default list model. Car is an object I created that contains the elements (as seen by the get methods). The elements shown in the listModel are merely Strings that show getMake(), getModel(), and so forth... (Each 'get' item is separated by about 10 spaces.)
What am I doing wrong in the if statement? I figured that the getMake() and getModel() (and so forth) would be substrings of the index selected.
Thank you so much for your assistance! Any input regarding ways I could make further questions more specific and clear would be greatly appreciated!
It seems like you are doing this to find the selected Car in some kind of data structure. You would be better off doing something like programming a custom list model that had access to cars itself. Then you could retrieve the selection more immediately. If cars is an ArrayList that list merely parallels I also don't see why you can't do something to the effect of cars.remove(list.getSelectedIndex());. Or since JList can display any object, override Car's toString to display what the list currently displays and have the list display Cars. Then you can cars.remove((Car)list.getSelectedValue());.
But aside from that, based on your description it sounds like you mean to do the evaluation the other way. It's the list item that should contain all of the Car attributes, rather than all of the Car attributes containing the list item. So something like
if( string.contains(c.getMake()) && string.contains(year) // and so on
(Or with indexOf but since contains merely returns indexOf > -1, using contains makes your code somewhat shorter.)