I need help in making some kind of dynamic color scaling in excel.
I need to scale one column but based on the values from other column. Actually, I need to reset the color scaling to the second column whenever value on the first column changes.
Unless I've misunderstood, seems like you want value-specific conditional formatting.
So all rows in column A that contain value Value1 should have their own colour scale in column B.
Similarly, all rows in A that contain value Value2 should have their own colour scale in column B.
And so forth for all remaining values in column A.
One approach to do this might involve VBA and consist of the following.
You can get all rows where column A contains a certain value (e.g. Value1) with Range.AutoFilter in conjunction with Range.SpecialCells.
You can add conditional formatting with Range.FormatConditions.Add.
It makes sense to complete the above two steps only once for each unique value. Otherwise, the steps will be completed for every value in column A.
You can get code to run when a change occurs in column A using Worksheet_Change event and some conditional IF logic.
Assuming your values in column A are sorted (as they appear to be in the document you've shared), the code might look something like:
Option Explicit
Private Sub ApplyValueSpecificConditionalFormatting(ByVal columnToFormat As Variant)
Dim filterRangeIncludingHeaders As Range
Set filterRangeIncludingHeaders = Me.Range("A1", Me.Cells(Me.Rows.Count, columnToFormat).End(xlUp))
Dim filterRangeExcludingHeaders As Range
Set filterRangeExcludingHeaders = filterRangeIncludingHeaders.Offset(1).Resize(filterRangeIncludingHeaders.Rows.Count - 1)
filterRangeExcludingHeaders.Columns(columnToFormat).FormatConditions.Delete ' Prevent redundant/obsolete rules.
' In your case, values in column A appear to be sorted. So we can assume that whenever
' the current row's value (in column A) is not the same as the previous row's value (in column A),
' that we have a new, unique value -- for which we should add a new colour scale in column B.
' A better, more explicit way would be to build a unique "set" of values (possibly accomodating
' type differences e.g. "2" and 2), and loop through the set.
Dim inputArray() As Variant
inputArray = filterRangeIncludingHeaders.Value
Dim rowIndex As Long
For rowIndex = (LBound(inputArray, 1) + 1) To UBound(inputArray, 1)
If inputArray(rowIndex, 1) <> inputArray(rowIndex - 1, 1) Then
filterRangeIncludingHeaders.AutoFilter Field:=1, Criteria1:=inputArray(rowIndex, 1)
Dim cellsToFormat As Range
On Error Resume Next
Set cellsToFormat = filterRangeExcludingHeaders.Columns(columnToFormat).SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If Not (cellsToFormat Is Nothing) Then
' Probably best to put the below in its own function.
With cellsToFormat.FormatConditions.AddColorScale(colorscaleType:=2)
.SetFirstPriority
.ColorScaleCriteria(1).Type = xlConditionValueLowestValue
.ColorScaleCriteria(1).FormatColor.Color = vbWhite
.ColorScaleCriteria(2).Type = xlConditionValueHighestValue
.ColorScaleCriteria(2).FormatColor.Color = 8109667
End With
End If
Set cellsToFormat = Nothing
End If
Next rowIndex
Me.AutoFilterMode = False
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
ApplyValueSpecificConditionalFormatting columnToFormat:=2 ' or B
ApplyValueSpecificConditionalFormatting columnToFormat:="C" ' or 2
End If
End Sub
The code should be placed in the code module of the worksheet (containing values in column A and colour scales in column B).
Related
I want to insert a timestamp (E3) when the status (B3) changes. This should happen for at least 30 more such examples in the worksheet. The code currently works only for one example (Country1). Do you have an idea how this can be implemented?
I already tried different types but it just worked for example "Country 1" not for "Country 1", "Country 2", "Country 3" etc.
When I adjust the code for the range "B3:I3" then I received an adjustment in every 3rd column, example: I add a comment in D3 then a timestamp will be created in H3. That is not what I want. :(
Is there a way to adjust the code so that as soon as a change is made in the Status column (B3;F3;J3etc.), the Timestamp column (E3;I3 etc.) will reflect the time stamp?
Code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("B3:B5"))
Is Nothing Then Exit Sub
Application.EnableEvents = False
Target.Offset(0,3).Value = Now
Application.EnableEvents = True
Please, try the next adapted event. It will calculate how many groups of four columns exists and set a range of their first column intersected with rows 3 to 5. Only for this range the event will be triggered:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim lastCol As Long, rngCols As Range
lastCol = Me.cells(2, Me.Columns.count).End(xlToLeft).column 'last column on the second row
Set rngCols = Me.Range(trigData(Me.Range("B2", Me.cells(2, lastCol)))) 'create the range of the columns for what the event to be triggered
Set rngCols = Intersect(Me.rows("3:5"), rngCols) 'create the range inside which the change to trigger the event
If Not Intersect(rngCols, Target) Is Nothing Then
Application.EnableEvents = False
Target.Offset(0, 3).Value = Now
Application.EnableEvents = True
End If
End Sub
Function trigData(rngCols As Range) As String
Dim i As Long, strCols As String
For i = 1 To rngCols.Columns.count Step 4 'iterate from four to four and create the necessary columns string address
strCols = strCols & "," & rngCols.cells(i).EntireColumn.address
Next i
trigData = Mid(strCols, 2) 'Mid eliminates the first (unnecessary) comma...
End Function
The code will be confused if you place on the second row data after the necessary groups of four columns. If necessary, one or two such columns, the code can be adapted to work for a fix number extracting the divided integer (without decimals).
The code assumes that you need to be triggered for the mentioned rows (3 to 5). If you need something different in terms of rows to be affected, you should change Me.rows("3:5") according to your need.
Please, send some feedback after testing it.
Your request is a little unclear, and your table format may not have come across correctly in your post. Your code is written to add the current time to a cell 3 columns away from the target cell. It is dynamic, so if you set
If Intersect(Target, Range("B2:I3"))
You are going to get the value in cell 3 columns offset from the changed cell. If you always want it to update column E, then you can use the target.row property...
Cells(Target.Row,5).Value = Now
...to make the row dynamic, and the column static. Clarify your question if this is not what you're looking for. If country2 is in cell F2, where do you want to write the timestamp?
You can use this simple function:
Public Function TimeStamp(Status As Range) As Double
TimeStamp = Now
End Function
So, in Cell E3 will be the formula =TimeStamp(B3). (Format cell E3 appropriately as Time Format)
I have an excel sheet where I paste some data and I want to run a function automatically on pasting data at the end of each column to count the number of cells that have some text and then give that row which contains formula a specific color.
For example, I paste the below data:
And now I want to run a function at the end of each column which will display the count of cells containing 'Error'.
The function for the first column would be =countif(A2:A9, "Error"), the function for the second column would be =countif(B2:B9, "Error") and so on.
Appreciate any help in advance.
Format a blank table and create a sum row(Click in table -> Tabletools -> Sum row):
Write in the sum row your formula like: =countif([Second],"Error")
Now you can simply copy in your data and it will calculates the occurence in the last row. On pasting the table in, it will move the sum row automaticly downwards.
Expanding on Doomenik's answer
Part 1 Set your data up as a table and insert a total row. Adjust the following table name as appropriate.
Then insert total row by going into the design tab, which appears when you are inside the table range, and checking the Total Row box
A total row will appear at the bottom of the table with a dropdown icon
Starting with column A you want to select the COUNTIF function to apply to the total row which means selecting More Functions from the drop down and then typing in COUNTIF.
In the box that appears enter the following:
Notice that the entire data area of column A in the table is referenced by [ID]. This will be automatically entered when you select the data area of the table A column range when specifying the range argument to COUNTIF i.e. when selecting as below:
The criteria argument is NA() for error.
You then drag the formula from column A, in the total row, across to column C and autofill will do the rest.
Part 2: Apply conditional formatting to the total row by using
=ISFORMULA(INDIRECT("Table1[#Totals]"))
in Excel 2016 or
=LEFT(FORMULATEXT(INDIRECT("Table1[#Totals]")),8) = "=COUNTIF"
in earlier versions.
Entering the formula:
Now, specifying the range to apply to:
I messed around with specifying the last row with
=INDIRECT("Table1[#Totals]")
Turns out, Excel still converts this to the current last row range e.g.
=$A$11:$C$11
And this updates even if i add rows to the table.
Part 3: Adding new rows by pasting
Now, how to handle the adding of rows by pasting? Insert the following code by Zak into the worksheet containing the table.
Then paste the new rows into the first column of the totals row and it will update and shift the totals down.
Option Explicit
Private Const SingleRowOnly As Boolean = False
Private Const MaxRowCount As Long = 100
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ResizeRange As Range
Dim Table As ListObject
Dim TotalsShowing As Boolean
Dim ExpandTables As Boolean
Dim RowIndex As Long
Dim RowCount As Long
' Make sure sheet isn't protected
If Me.ProtectContents Then Exit Sub
' If already in a table, then exit
If Not Target.ListObject Is Nothing Then Exit Sub
' Make sure only one row is being changed
If Target.Rows.Count > 1 Then Exit Sub
' Make sure we're not in row 1
If Target.Row = 1 Then Exit Sub
' Make sure we're in the row right under the Totals row
If Target.Offset(-1, 0).ListObject Is Nothing Then Exit Sub
' Set table
Set Table = Target.Offset(-1, 0).ListObject
TotalsShowing = Table.ShowTotals
ExpandTables = Application.AutoCorrect.AutoExpandListRange
' If Totals not showing, exit
If Not TotalsShowing Then Exit Sub
' Make sure the selection is a contiguous range
If Target.Areas.Count > 1 Then Exit Sub
' Make sure Target range is within the table columns
If Target(1, 1).Column < Table.ListColumns(1).Range.Column Then Exit Sub
If Target(1, Target.Columns.Count).Column > Table.ListColumns(Table.ListColumns.Count).Range.Column Then Exit Sub
' Prepare to adjust table
Application.EnableEvents = False
Table.ShowTotals = False
Application.AutoCorrect.AutoExpandListRange = True
' Set the resize range
If WorksheetFunction.CountA(Table.Range(1, 1).Offset(Table.Range.Rows.Count + 1).Resize(1, Table.Range.Columns.Count)) > 0 Then
If Not SingleRowOnly Then
RowIndex = Target.Row
RowCount = RowIndex
Do Until WorksheetFunction.CountA(Me.Range(Me.Cells(RowCount, Table.Range(1, 1).Column), Me.Cells(RowCount, Table.Range(1, Table.ListColumns.Count).Column))) = 0 Or RowCount - RowIndex > MaxRowCount
RowCount = RowCount + 1
Loop
Set ResizeRange = Table.Range.Resize(Table.Range.Rows.Count + RowCount - RowIndex, Table.Range.Columns.Count)
Else
Set ResizeRange = Table.Range.Resize(Table.Range.Rows.Count + 1, Table.Range.Columns.Count)
End If
Else
Set ResizeRange = Table.Range.Resize(Table.Range.Rows.Count + 1, Table.Range.Columns.Count)
End If
' Make table adjustment
Table.Resize ResizeRange
' Put things back the way we found them
Application.AutoCorrect.AutoExpandListRange = ExpandTables
Table.ShowTotals = TotalsShowing
Application.EnableEvents = True
End Sub
Quoting from the link:
There are two constants declared at the top of this code.
SingleRowOnly. This specifies whether multiple rows should be included
in appending into the Table, or if only a single row should be.
MaxRowCount. As to not go crazy with appending rows to a Table
automatically, this is the maximum number of rows to include at any
one time. If SingleRowOnly is set to True, this constant is moot.
So you can adjust as appropriate.
With Autocomplete feature it should update column references automatically
EDIT
If you want to auto-do this, maybe you can try to paste the following formula at the end of your data:
=COUNTIF((INDIRECT(ADDRESS(ROW()-8;COLUMN()))):(INDIRECT(ADDRESS(ROW()-1;COLUMN()))); "Error")
Explanation
COUNTIF(range; pattern)
The range is specified with two INDIRECT functions. One pointing to the first row, and one pointing to the last one (those 8 and 1 respectively).
So the range looks like:
(INDIRECT(ADDRESS(ROW()-8;COLUMN()))) : (INDIRECT(ADDRESS(ROW()-1;COLUMN())))
NOTE that I assumed that you have 8 rows in total, but you can put any other number there
I want to do the following: I have a table = Listobject which has a column called MasterID. Some columns have the same MasterID and some have even non. I need to manually add the missing MasterIDs.
I want take a row where the MasterID is empty and then I want to click on the column MasterID and select an ID for this column. It can either be an existing ID. Which is the unique lsit of used MasterIDs in the hole listcolumn OR it can be a new MasterID. If a new ID is selected it should be the next integer from the biggest MasterID. So if the highest masterID up until now was 1000 then the new one should be 1001.
So I wanted to know if there is a way to use data validation in order to suggest me the next bigger MasterID or all existing. Since the already filled MasterIDs are randomly distributed I need to make this into a single formula.
Lets formulize this a little:
IF the cell is NOT EMPTY it can be whatever it wants to be, ELSE the cell needs to be one of the values used in the listcolumn OR the MAXIMUM of the Listcolumn +1.
If possible I would like to use a dropdown list.
I have tried this with a data validation list option but I couldn't figure out how. I know there needs to be a structur like this:
If Isempty then BeWhatever
Else Be DynamicAdjustedListofEntries OR MaximumEntry+1
I have thought of doing this with a macro but I don't want to update this everytime I change something. Can anyone help?
I don't think dynamically filling up the list type of validation is possible using pure Excel. Here's my solution using VBA. Place this macro in the appropriate worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
If Target.Column = Range("Table26[MasterIDs]").Column Then
Dynamic_Data_Validation "Table26[MasterIDs]"
End If
Application.EnableEvents = True
End Sub
Note: Change Table26[MasterIDs] according to your data.
Then paste this in a normal module. You can, of course, paste these lines directly into the Worksheet_Change procedure.
Sub Dynamic_Data_Validation(table_range As String)
Dim ids() As Variant 'Didn't declare as Long because JOIN function doesn't accept it
Dim row_count As Long
Dim src As Range, tmp_rng As Range
Dim validation_list As String
Application.ScreenUpdating = False
Set src = Range(table_range)
ids = src.Value
'Change X to some other column name if you don't prefer this
Set tmp_rng = Range("X1").Resize(UBound(ids))
tmp_rng = ids
'If sorted in descending order, it becomes difficult to add the
'(MAX + 1) ID in the beginning of the array
tmp_rng.Sort Key1:=tmp_rng, Order1:=xlAscending
tmp_rng.RemoveDuplicates Columns:=1
row_count = tmp_rng.End(xlDown).Row
'Add the (MAX + 1) ID to the end of the range and resize it
tmp_rng.Cells(row_count + 1).Value = tmp_rng.Cells(row_count).Value + 1
Set tmp_rng = tmp_rng.Resize(row_count + 1)
tmp_rng.Sort Key1:=tmp_rng, Order1:=xlDescending
ids = Application.Transpose(tmp_rng)
tmp_rng.Delete Shift:=xlToLeft
'Perhaps consider adding a code to save the workbook after this line,
'as pressing CTRL + END will move the cursor to column X or whatever you choose
validation_list = Join(ids, ",")
'The existing validation needs to be deleted, otherwise it raises error
src.Validation.Delete
src.Validation.Add Type:=xlValidateList, Formula1:=validation_list
End Sub
I've used the worksheet to place the array items temporarily for sorting and removal of duplicates, because this seemed easier. An alternative is to manipulate the array elements within the array itself and then passing it as an argument to VBA.Join.
Here's the output:
I have written VBA code that copies a filtered table from one spreadsheet to another. This is the code:
Option Explicit
Public Sub LeadingRetailers()
Dim rngRows As Range
Set rngRows = Worksheets("StoreDatabase").Range("B5:N584")
With rngRows
.SpecialCells(xlCellTypeVisible).Copy _
Destination:=Worksheets("LeadingRetailersAUX").Range("B2")
End With
Sheets("Leading Retailers").Activate
End Sub
The filter is applied before the code is ran and then the code selects the visible cells and copies them so as to get only those rows that passed the filter.
In the filtered table to be copied I have, in column L of the range, a certain set of names, some of which are repeated in several rows.
I would like to add to the code so that it only copies one row per name in column L. In other words, I would like the code to copy only the first row for each of the names that appears in Column L of the filtered table.
Pehaps something like this can help you. Code will loop through your rows (5 to 584). First it checks if row is hidden. If not, will check if the value in column "L" is already in the Dictionary. If it is not, it will do two things: copy the row to Destination Sheet, and add the value to the Dictionary.
Option Explicit
Public Sub LeadingRetailers()
Dim d As Object
Dim i As Long
Dim k As Long
Set d = CreateObject("scripting.dictionary")
i = 2 'first row of pasting (in "LeadingRetailersAUX")
For k = 5 To 584
If Not (Worksheets("StoreDatabase").Rows(k).RowHeight = 0) Then 'if not hidden
If Not d.Exists(Worksheets("Hoja1").Cells(k, 12).Value) Then 'if not in Dictionary
d.Add Worksheets("StoreDatabase").Cells(k, 12).Value, i 'Add it
Worksheets("LeadingRetailersAUX").Cells(i, 2).EntireRow.Value = Worksheets("StoreDatabase").Cells(k, 1).EntireRow.Value
i = i + 1
End If
End If
Next
End Sub
You could apply another filter to the table to only show the first occurrence of each set of names and then run your macro as usual. See this answer:
https://superuser.com/a/634284
I have a list in Excel and I need to format rows based on the value in the cell 2 of that row. This is how data looks like
No. | Name | Other data | Other data 2 | Date | Date 2 |
For example, if Name=John Tery => color row as Red, if Name=Mary Jane => color row as Pink, etc.
I tried using conditional formatting, but I did not know how to make this work. I have very little experience with such tasks in Excel.
Can anyone help?
PS. all name are two-word names
if there are only a few names to handle, each conditional-format formula would look like this
=$B2="John Tery"
you need to have selected the affected rows from the top row down (so current active cell is in the 2nd row, not in the last row)
absolute reference to column $B means that for all cells in different columns, column B will be tested
relative reference to row 2 means that for cell in different rows, its own row will be tested (e.g. for cell A42, the formula will test value of $B42)
equality operator = will return either TRUE or FALSE (or an error if any of the arguments are errors) and it has the same use as inside IF conditions...
Edit Rereading the question, I saw that the entire row is to be coloured not just the name. I also decided that if a recognised name is replaced by an unrecognised name, the colour should be removed from the row. The original code has been replaced to address these issues.
I decided I did not care about the answers to my questions because the solution below seems the easiest for any scenerio I could identify.
First you need some method of identifying that "John Tery" is to be coloured red and "Mary Jane" is to be coloured pink. I decided the easiest approach was to have a worksheet NameColour which listed the names coloured as required. So the routine knows "John Tery" is to be red because it is red in this list. I have added a few more names to your list. The routine does not care how many words are in a name.
The code below must go in ThisWorkbook. This routine is triggered whenever a cell is changed. The variables MonitorColNum and MonitorSheetName tell the routine which sheet and column to monitor. Any other cell changes are ignored. If it finds a match, it copies the standard form of the name from NameColour (delete this statement from the code if not required) and colours the cell as required. If it does not find a match, it adds the name to NameColour for later specification of its colour.
Hope this helps.
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Changed As Range)
Dim CellCrnt As Variant
Dim ColLast As Long
Dim Found As Boolean
Dim MonitorColNum As Long
Dim MonitorSheetName As String
Dim RowNCCrnt As Long
MonitorSheetName = "Sheet2"
MonitorColNum = 2
' So changes to monitored cells do not trigger this routine
Application.EnableEvents = False
If Sh.Name = MonitorSheetName Then
' Use last value in heading row to determine range to colour
ColLast = Sh.Cells(1, Columns.Count).End(xlToLeft).Column
For Each CellCrnt In Changed
If CellCrnt.Column = MonitorColNum Then
With Worksheets("NameColour")
RowNCCrnt = 1
Found = False
Do While .Cells(RowNCCrnt, 1).Value <> ""
If LCase(.Cells(RowNCCrnt, 1).Value) = LCase(CellCrnt.Value) Then
' Ensure standard case
CellCrnt.Value = .Cells(RowNCCrnt, 1).Value
' Set required colour to name
'CellCrnt.Interior.Color = .Cells(RowNCCrnt, 1).Interior.Color
' Set required colour to row
Sh.Range(Sh.Cells(CellCrnt.Row, 1), _
Sh.Cells(CellCrnt.Row, ColLast)).Interior.Color = _
.Cells(RowNCCrnt, 1).Interior.Color
Found = True
Exit Do
End If
RowNCCrnt = RowNCCrnt + 1
Loop
If Not Found Then
' Name not found. Add to list so its colour can be specified later
.Cells(RowNCCrnt, 1).Value = CellCrnt.Value
' Clear any existing colour
Sh.Range(Sh.Cells(CellCrnt.Row, 1), _
Sh.Cells(CellCrnt.Row, ColLast)).Interior.ColorIndex = xlNone
End If
End With
End If
Next
End If
Application.EnableEvents = True
End Sub