0
votes

I am trying to apply conditional formatting to more than one range. The formatting is always the same but the cell that contains the condition is not.

For example:

`=OR($B$11="金",$B$11="木")` //this custom formula applies to range C5:Q14  
'=OR($B$22="金",$B$22="木")  //this custom formula applies to range C16:Q25

The first formula checks the cell B11 for condition
The second formula checks the cell B22 for the condition

It works as intended. However I have to set up many such ranges and if I copy or paste, the formatting ranges just get added to the ones already in the formula and they all check the same cell for the condition. I can achieve what I need if I set the condition for every range manually but I would like to know if there is a better way via sheets formula or a script if formulas are not viable.

Please see the sheet for the example

https://docs.google.com/spreadsheets/d/1G0eUibjNKlZ1fDm9id5SPFNlF392cuEPzNDMB8E5jyU/edit?usp=sharing

2
Hi! Welcome to SO! I am glad you are using Spreadsheet formulas in interesting and productive ways. Just one thing: please try your best to abstract your problem and present it with minimal examples. Questions that do not require other contributors to perform particular tasks Mechanical Turk style but instead allow everyone else to adapt the question/answer to their own tasks do best at attracting answers, and attracting the best answers.Argyll
In my answer, I abstracted your question into "apply conditional formatting by referencing the top row in a group of rows". If you provide more suitable abstraction of your task, I am happy to update my answer. If you could kindly provide a minimal example demonstrating your abstracted question, I will be more able to tailor to your question.Argyll
Thanks. I'll be sure to read the minimal examples paper.SystemWorks

2 Answers

0
votes

Apps Script Solution

AFAIK the best way is with Apps Script

With a script like this:

function createRule() {
  // Get Ranges
  var sheet = SpreadsheetApp.getActiveSheet();
  var cellToWatch = sheet.getActiveCell()
  var rangeForRule = cellToWatch.offset(-6, 1, 10, 15)
  
  // Create absolute Cell reference
  var cellNotation = cellToWatch.getA1Notation()
  var patt = /([a-zA-Z]+)([\d]+)/
  var result = patt.exec(cellNotation)
  var absoluteRef = "$" + result[1] + "$" + result[2]

  // Create Conditional Formatting rule
  var rule = SpreadsheetApp.newConditionalFormatRule()
    .whenFormulaSatisfied('=OR('+ absoluteRef +'="金",'+ absoluteRef +'="木")')
    .setBackground("#00FF00")
    .setRanges([rangeForRule, cellToWatch])
    .build();
  var rules = sheet.getConditionalFormatRules();
  rules.push(rule);
  sheet.setConditionalFormatRules(rules);
}

This script

  • Assumes that your sheet will always have exactly the same format

  • Needs you to select the cell with the criteria, like this:

    enter image description here

  • At the moment you need to run from the script editor

  • It chooses a range to apply the formatting rule based on the position of the selected cell. var rangeForRule = cellToWatch.offset(-6, 1, 10, 15)

  • It gets the A1 notation of the selected cell and using RegEx, makes an absolute version of the A1 notation. A1 => $A$1

  • It creates a conditional formatting rule using the references it has just built.

  • You will need to modify the HEX value of the color to suit your needs "#00FF00"

  • You could make this a custom sidebar on your spreadsheet, so that creating this rule is just a couple mouse clicks.

References

1
votes

I see 3 possible abstractions of your problem.

1) Conditional formatting by groups of rows of known length. 2) How to identify the row index of the top of a section in a copy-paste friendly way if helper column is permitted. 3) Detect the top most non-blank cell in a column above a particular row.

Of course, 3) is most general. But if you only need 1) or 2), it's better to use simpler solutions. So I'll comment on each.


Conditional formatting by groups of rows of known length

Use a combination of index and match. (If the requirement grows more complicated, also consider using indirect.)

For example, if you need rows in groups of 11 to refer to the head row, you can do

=match(index($A:$A,floor(row(B1)/11)*11+1,1),{"金";"木"},0)

in B1:C11; given that you have weekday character in A:A every 11 rows.

Recall that in Conditional Formatting, we specify a (fixed) range and a formula that refers to relative ranges. Google Sheet will then iterate the cell indices over the (fixed) range -- meaning, starting with the top-left most cell, when you move down 1 row, the (relative) ranges in the formula will all have row index adds +1; when you move right 1 column, the (relative) ranges in the formula will all have column index adds +1. $ sign functions normally.

The only (relative) range in the formula that is free to iterate is B1. Thus what happens here is that: as you move along (fixed) range B1:C11, for example when you reach C10, the (relative) range in the formula becomes C10 (coincidentally) because C10 is 9 rows down and 1 column right from the top-left most cell in range B1:C11.

Test:

Identify/Designate row index of the top of a section

If you can use a helper column, there is an easy way to designate a row index as a function of the position of a section.

For example, let's say your section spans B1:D10. You want to be able to copy-paste this section to B21:D21 and you want everything else to keep.

It's simple if you can tolerate using A:A just for labeling the top of the section.

You do not need to know the number of rows per section ahead of time.

In A1, input =row(A1). In A2, input =A1. Now drag the formula across your section, ie. to A10.

Now you can put conditional formatting in C1:D10 as simply

=match(index($B:$B,A1,1),{"金";"木"},0)

and of course you can use simpler formulas for string comparison.

Detect the top most non-blank cell in a column above a particular row

If you don't have a fixed template for your section with known number of rows, and you need to keep your sheet clean of helper columns, then the only way is to detect the last non-empty cell above a given row in the column you have your weekday characters.

A number of variations are possible here. You can detect non-empty cell. Or you can detect the presence of a string from a list of string. You can retrieve the content of the cell or you can get the row index. etc. We are going to do the simplest thing here.

Suppose you have your weekday characters in A:A and you need conditional formatting in B:C. Then, in Conditional Formatting tab, put range as B:C, and formula as follows.

=match(+SORT($A$1:$A1,$A$1:$A1<>"",,ROW($A$1:$A1),),{"金";"木"},0)

What happens here is that +SORT($A$1:$A1,$A$1:$A1<>"",,ROW($A$1:$A1),) will pick out the content of the last non-empty cell in column A relative to the row in question.

Here is how SORT achieves the result for us:

The 2nd input of SORT will evaluate into a column of boolean with TRUE meaning non-empty. $A$1:$A1 relative to B1:C means to pick out cells in A1:A above and including the cell in question. Leaving the 3rd input empty means descending, which in turn means TRUE comes before FALSE. + tells Google Sheet to output the first element in an array output. Up to this point, +SORT($A$1:$A1,$A$1:$A1<>"",) will output the top-most non-empty cell. Within all the non-empty cells, you want the last one. Hence, the 4th input for row index and 5th input for descending. The non-empty cell with the highest row index in A1:A is the cell you want.


It is up to you as the Asker to identify the exact requirements for your task at hand.

I would say it is important that the Asker abstracts the requirements and state them in the question --- as opposed to seeking how to assess the task at hand in answers.

That is what often distinguishes a programming question that everyone else can search easily, thus learn and adapt from vs an outsourcing query.