Excel - Fill a cell with different colors - vba

I need to fill a cell with different colors as in this picture (3 rows are merged vertically and colors are drawn manually in this picture using 3 rectangular shapes):
The only way I could find to fill part of a cell is using conditional formatting (by setting style as data bar and fill as solid) but it support only one color.
Is this possible with or without VBA?

It is possible.
I have found two ways to do that.
1- Using a black square shaped character (character code 2588 – vba: ActiveSheet.Cells(1, 1) = ChrW(&H2588)) and color them according to percentage. This character fills the cell height and also there is no spacing between them which allows filling a cell completely (Sure you should consider left indent in a cell). Only issue here that you cannot use a lot of characters in one cell; I use 30 of them and scale the number of characters according to 30 (ie. 50% red means 15 red character-2588).
2- It is same as what #Doktor Oswaldo has suggested: Inserting a plot in a cell using cell's position and size in pixels. This method has one big advantage: you can show the ratios exactly. In addition, you can fill a data series with a pattern as well. However if you have a lot of plots, you will sacrifice from Excel performance. For plot settings, I use following VBA code:
'Define var's
Dim src As Range, targetCell As Range
Dim chacha As ChartObject
'Set var's
Set src = Worksheets("Sheet1").Range("B1:B3")
Set targetCell = Worksheets("Sheet1").Range("C2")
'Create plot at the target cell
Set chacha = Sheets("Sheet1").ChartObjects.Add(targetCell.Left, targetCell.Top, targetCell.Width, targetCell.Height)
'Change plot settings to fill the cell
With chacha.Chart
.ChartType = xlBarStacked
.SetSourceData Source:=src, PlotBy:=xlRows
.Axes(xlValue).MinimumScale = 0
.Axes(xlValue).MaximumScale = 100
.Axes(xlCategory).Delete
.Axes(xlValue).Delete
.Legend.Delete
.PlotArea.Top = -50
.PlotArea.Left = -50
.PlotArea.Width = targetCell.Width
.PlotArea.Height = targetCell.Height
.ChartGroups(1).GapWidth = 0
End With
chacha.Chart.SeriesCollection(1).Format.Fill.ForeColor.RGB = RGB(255, 0, 0)
chacha.Chart.SeriesCollection(2).Format.Fill.ForeColor.RGB = RGB(0, 0, 255)
chacha.Chart.SeriesCollection(3).Format.Fill.ForeColor.RGB = RGB(255, 255, 0)
In the code I modified the series colors manually which can also be automatized. Following is the screenshot of both methods. The Cell "C1" is filled with block characters and "C2" is a chart.
Note: You might get an error at the line ".PlotArea.Top". To solve this issue, please check: Error setting PlotArea.Width in Excel, VBA (Excel 2010)

Related

Color Chart Columns by Cell Color Error

I am trying create a chart using cell colors from the active cells on a specific worksheet. When using the macro provided below I find that only some of the assigned cell RGB color codes match to the chart. I am not sure why some colors would match and some would not. The correct colors display in the chart when I manually enter the color codes. Is there something I am leaving out of this macro or an extra step I need to take?
I am using Excel 2016 for this project.
Sub ColorChartColumnsbyCellColor()
With Sheets("Sheet1").ChartObjects(1).Chart.SeriesCollection(1)
Set vAddress = ActiveSheet.Range(Split(Split(.Formula, ",")(1), "!")(1))
For i = 1 To vAddress.Cells.Count
.Points(i).Format.Fill.ForeColor.RGB =
ThisWorkbook.Colors(vAddress.Cells(i).Interior.ColorIndex)
Next i
End With
End Sub
You're assigning a color index to the RGB property. Color indexes have nothing to do with Red Green Blue. Besides, #Tim William's has a point: conditional formatting may play a role in what you're doing.
Try this code, which assigns the Color property to the RGB property:
Sub ColorChartColumnsbyCellColor()
With Sheets("Sheet1").ChartObjects(1).Chart.SeriesCollection(1)
Set vAddress = ActiveSheet.Range(Split(Split(.Formula, ",")(1), "!")(1))
For i = 1 To vAddress.Cells.Count
'Comment the line below and uncomment the next one to take conditional formatting into account.
.Points(i).Format.Fill.ForeColor.RGB = vAddress.Cells(i).Interior.Color
'.Points(i).Format.Fill.ForeColor.RGB = vAddress.Cells(i).DisplayFormat.Interior.Color
Next i
End With
End Sub

Select a single check box

I have a piece of code that only works when there are many check boxes. However when I only have one checkbox the code bugs. Basically what I want the macro to do is to select the ckeckbox as an object and align it to a cell it should also work if more than one checkbox. Could you please help?
Many thanks in advance. Please see code attached
Worksheets("Analysis Line Cupboards by Pick").CheckBoxes.Select
Selection.ShapeRange.Align msoAlignCenters, msoFalse
Selection.ShapeRange.IncrementLeft 45
Range("A10000").Select
You can align a checkbox (or any shape) on another shape, or on a cell with the left property.
Example, for an ActiveX checkbox in a Sheet to be aligned on cell B5:
Sheets("Sheet1").Shapes("Checkbox_1").Left = Sheets("Sheet1").Range("B5").Left
In your case (adapt it):
Sheets("Analysis Line Cupboards by Pick").Shapes("Checkbox name").Left = cell.left
Or, if you have multiple checkboxes, give them appropriate names and loop through them. For example, their names could be chbx_A, chbx_Hello, chbx_10 or something.
Sub AlignCHBX()
dim shp as shape
for each shp in Sheets("Analysis Line Cupboards by Pick").Shapes
if shp.name like "chbx*" then shp.left = Sheets("Analysis Line Cupboards by Pick").Range("B1").Left
next
End sub
This verifies that the shape's name starts with chbx to avoid moving other shapes. You can use this to differentiate certain groups of checkboxes, as well!
For a NON-ActiveX checkbox, use the following syntax to refer to it's left, for example:
Sheets("Sheet1").OLEObjects("chbx_A").Left
For it's value:
Sheets("Sheet1").OLEObjects("chbx_A").Object.Value
For .Top it works the same way. The .Left and .Top properties are numeric values measured in pixels. If you want to go to an absolute position, you can write Sheets("Sheet1").OLEObjects("chbx_A").Left = 150 for example. When you are making the shape's Left = to the cell's left, the code actually goes to see what Absolute value the left position of the cell is, and gives it to that shape. If I can elaborate, you could write:
dim nbPosition as Double
nbPosition = ActiveSheet.Range("B5").Left
debug.print nbPosition 'It could say 40, for example, depending on column width
ActiveSheets.Shapes("Shape1").Left = nbPosition + 10 'Will send it 10 pixels further than nbPosition

Chart Series Line: Bring to Front, Send to Back

If I use VBA in Excel to make a line chart with multiple series, and two of the series' data are very similar so that their chart series lines partly overlap, the last one written is in front of earlier ones written.
In the Worksheet_Change event, I want to be able to go back and forth between which chart series line is in front, based on user actions that change the data. Can I do that without deleting and recreating the chart?
Here's how I'm identifiying the series line, for example here's series 2:
Sheet1.ChartObjects("MyChart").Chart.SeriesCollection(2)
Getting TypeName on that returns Series. I see Series in help, but with no information on its properties and methods. I don't see Series in the Object Browser (I'm on Excel 2007). I was able to get a list of properties and methods in the context help dropdown, but I didn't see anything promising in the dropdown.
So, can I bring a chart series to the front/send it to the back, without deleting and recreating the chart?
Here is a simple non-VBA way to highlight one series in a chart.
Below I show X data in column B, Y data for series alpha, beta, and gamma in columns C:E, and extra data in column F.
I have a chart, and below the chart a combo box inserted using Developer tab > Insert > Form Controls > Combo Box. I formatted the control (right click) and set the Input Range to K16:18, and the Cell Link to J16.
I selected F2:F16, with F2 being the active cell. I entered =INDEX(C2:E2,$J$16) in the formula bar, then held Ctrl while I pressed the Enter key. This filled the formula into the entire range. Essentially the formula takes the value from the three cells to the left, based on the Cell Link of the combo box. Since I've selected "beta" in the combo, J16 contains the value 2, and column F shows the values of the second data column.
The chart was made using the entire data range. The first three series were formatted with light gray lines, and the fourth (duplicate) series was formatted with a color that stands out in comparison. All data is visible at once, but only one is highlighted.
What you can do is change all the series line colors to a light color except for one. That one you color dark or bright, ie Red. Then you can repeat the process (assign the macro to a button on/near the chart) to highlight each series in turn.
Sub CycleSeriesColors()
Dim seriesCount As Integer, i As Integer, smod
Static s '"Remember" what series we are on between calls.
ActiveSheet.ChartObjects("Chart 1").Activate
' ActiveChart.PlotArea.Select
seriesCount = ActiveChart.SeriesCollection.Count
s = s + 1
smod = s Mod (seriesCount + 1)
' ActiveChart.ChartArea.Select
If Not smod = 0 Then
With ActiveChart.SeriesCollection(smod).Format.Line
.Visible = msoTrue
.Visible = msoTrue
.ForeColor.RGB = RGB(192, 0, 0)
.Transparency = 0
End With
For i = 1 To seriesCount
If Not i = smod Then 'the series is to backgrounded
With ActiveChart.SeriesCollection(i).Format.Line
.Visible = msoTrue
.Visible = msoTrue
.ForeColor.RGB = RGB(240, 240, 240)
.Transparency = 0.85
End With
End If
Next i
Else
Randomize
For i = 1 To seriesCount
With ActiveChart.SeriesCollection(i).Format.Line
.Visible = msoTrue
.Visible = msoTrue
.ForeColor.RGB = RGB(((i * i) ^ 0.6 * 3), ((i * i) ^ 0.6 * 8), ((i * i) ^ 0.6 * 12))
.Transparency = 0
End With
Next i
End If
End Sub

VBA: Extracting the RGB value of lines in a chart with default colors

Problem
I would like to know how to read the current RGB value of an automatically assigned color in a chart, even if this entails freezing the colors to their current values (rather than updating them as the theme is changed, series are reordered, etc.)
Usecase
My actual usecase is that I would like to make the datalabels match the color of the lines/markers in a line chart. This is easy if I have explicitly set the colors of the series via a scheme or explicit RGB values, e.g.
' assuming ColorFormat.Type = msoColorTypeRGB
s.DataLabels.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB= _
s.Format.Line.ForeColor.RGB
However, doing this when the series color is assigned automatically results in white labels. More specifically, both of the following equalities hold
s.Format.Line.ForeColor.Type = msoColorTypeRGB
s.Format.Line.ForeColor.RGB = RGB(255,255,255) ' White
And yet the line of course isn't white, but is an automatically assigned color from the theme. This shows that the color is automatically assigned
s.Border.ColorIndex = xlColorIndexAutomatic
I suppose it makes sense that the color isn't stored with the series in question. Even storing the index into the color scheme wouldn't generally work as Excel needs to change the color if another data series is added or someone reorders the data. Still, I would love it if there were some way to identify the current RGB value automatically.
An Ugly Workaround
For charts with 6 or fewer entries, a simple workaround is to exploit the fact that theme colors are assigned sequentially, so I can do (e.g.)
chrt.SeriesCollection(1).DataLabels.Format.TextFrame2.TextRange.Font.Fill.ForeColor.ObjectThemeColor _
= msoThemeColorAccent1
Presumably this could be extended to account for the TintAndShade used to differentiate entries once the theme has been exhausted, but this is such an ugly hack.
Research
Someone asked essentially the same question (how to extract theme colors) here, but it was never answered. There are several sources suggesting ways to convert a known theme color into RGB values (e.g. here and here) but that just begs the question; I don't know the color a priori, other than "whatever color this line currently is."
So this is interesting. I create a line chart using all defaults, and then I run this procedure:
Sub getLineCOlors()
Dim cht As Chart
Dim srs As Series
Dim colors As String
Dim pt As Point
Set cht = ActiveSheet.ChartObjects(1).Chart
For Each srs In cht.SeriesCollection
With srs.Format.Line
colors = colors & vbCrLf & srs.Name & " : " & _
.ForeColor.RGB
End With
Next
Debug.Print "Line Colors", colors
End Sub
The Immediate window then displays:
Line Colors
Series1 : 16777215
Series2 : 16777215
Series3 : 16777215
But this is clearly not the case. It is obvious that they all are different colors. If, instead of .RGB I do .ObjectThemeColor, then I get all 0, which is equally and demonstrably false by observing the chart!
Line Colors
Series1 : 0
Series2 : 0
Series3 : 0
Now here is where it gets interesting:
If, after having created the chart I change the series colors (or even leave them unchanged, by assigning to the same ThemeColors), then the function shows valid RGBs:
Line Colors
Series1 : 5066944
Series2 : 12419407
Series3 : 5880731
It is as if Excel (and PowerPoint/etc.) are completely unable to recognize the automatically assigned colors, on Line Charts. Once you assign a color, then it may be able to read the color.
NOTE: Line charts are picky, because you don't have a .Fill, but rather a .Format.Line.ForeColor (and .BackColor) and IIRC there are some other quirks, too, like you can select an individual point and change it's fill color, and then that affects the visual appearance of the preceding line segment, etc...
Is this limited to line charts? Perhaps. My past experience says "probably", although I am not in a position to say that this is a bug, it certainly seems to be a bug.
If I run a similar procedure on a Column Chart -- again using only the default colors that are automatically assigned,
Sub getCOlumnColors()
Dim cht As Chart
Dim srs As Series
Dim colors As String
Dim pt As Point
Set cht = ActiveSheet.ChartObjects(2).Chart
For Each srs In cht.SeriesCollection
With srs.Format.Fill
colors = colors & vbCrLf & srs.Name & " : " & _
.ForeColor.RGB
End With
Next
Debug.Print "Column Colors", colors
End Sub
Then I get what appear to be valid RGB values:
Column Colors
Series1 : 12419407
Series2 : 5066944
Series3 : 5880731
HOWEVER: It still doesn't recognize a valid ObjectThemeColor. If I change .RGB then this outputs:
Column Colors
Series1 : 0
Series2 : 0
Series3 : 0
So based on these observations, there is certainly some inability to access the ObjectThemeColor and/or .RGB property of automatically-assigned color formats.
As Tim Williams confirms, this was a bug as far back as 2005 at least as it pertains to the RGB, and probably that bug carried over in to Excel 2007+ with the ObjectThemeColor, etc... It is not likely to be resolved any time soon then, so we need a hack solution :)
UPDATED SOLUTION
Combine the two methods above! Convert each series from line to xlColumnClustered, then query the color property from the .Fill, and then change the series chart type back to its original state. This may be more reliable than trying to exploit the sequential indexing (which will not be reliable at all if the users have re-ordered the series, e.g., such that "Series1" is at index 3, etc.)
Sub getLineColors()
Dim cht As Chart
Dim chtType As Long
Dim srs As Series
Dim colors As String
Set cht = ActiveSheet.ChartObjects(1).Chart
For Each srs In cht.SeriesCollection
chtType = srs.ChartType
'Temporarily turn this in to a column chart:
srs.ChartType = 51
colors = colors & vbCrLf & srs.Name & " : " & _
srs.Format.Fill.ForeColor.RGB
'reset the chart type to its original state:
srs.ChartType = chtType
Next
Debug.Print "Line Colors", colors
End Sub
Here is the code I used in the end.
Sub ShowSeries()
Dim mySrs As Series
Dim myPts As Points
Dim chtType As Long
Dim colors As String
With ActiveSheet
For Each mySrs In ActiveChart.SeriesCollection
'Add label
Set myPts = mySrs.Points
myPts(myPts.Count).ApplyDataLabels ShowSeriesName:=True, ShowValue:=False
'Color text label same as line color
'if line has default color
If mySrs.Border.ColorIndex = -4105 Then
chtType = mySrs.ChartType
'Temporarily turn this in to a column chart:
mySrs.ChartType = 51
mySrs.DataLabels.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = _
mySrs.Format.Fill.ForeColor.RGB
'reset the chart type to its original state:
mySrs.ChartType = chtType
'if line has a color manually changed by user
Else
mySrs.DataLabels.Font.ColorIndex = mySrs.Border.ColorIndex
End If
Next
End With
End Sub
After half a day I managed to solve this issue:
Sub ......()
Dim k as Integer
Dim colorOfLine as Long
...............
.................
'Loop through each series
For k = 1 To ActiveChart.SeriesCollection.Count
With ActiveChart.FullSeriesCollection(k)
.HasDataLabels = True
'Put a fill on datalabels
.DataLabels.Format.Fill.Solid
'Get color of line of series
colorOfLine = .Format.Line.ForeColor.RGB
'Assign same color on Fill of datalabels of series
.DataLabels.Format.Fill.ForeColor.RGB = colorOfLine
'white fonts in datalabels
.DataLabels.Format.TextFrame2.TextRange.Font.Fill.ForeColor.RGB = RGB(255, 255, 255)
End With
Next k
..........
End Sub

Getting Theme Color info from Chart Series

I have a chart series using one color (it can be an msoThemeColorAccent<#> or any other) for the marker line, another color for the marker fill (it can be an msoThemeColorAccent<#>, lighter x%, or any other), and no lines. I want to determine if the chart series is using an msoThemeColorAccent<#> for the marker line, an msoThemeColorAccent<#>, lighter x%, for the marker fill, and if so which specific accent color in each case.
I tried returning some properties of a series, with no luck. At a breakpoint during execution of a Sub that has a reference sr to a chart series (which was, for testing purposes, set to have lines, with its color being the same as marker lines and marker fill, set at Accent1), and inside a With sr construction, I tested in the immediate window:
? .Format.Line.BackColor.ObjectThemeColor, .Format.Line.ForeColor.ObjectThemeColor
0 0
? .Format.Fill.BackColor.ObjectThemeColor, .Format.Fill.ForeColor.ObjectThemeColor
0 -2
I expect to obtain in some way a value of msoThemeColorAccent1 (=5), which is used in my series. Is there any way to do this?
All the examples that I found used a msoThemeColorIndex for setting a color, none as a value that was read.
PS: I did not succeed in this first part, with a series with uniform color. I foresee that if I overcome this hurdle, I may still have problems in detecting the correct TintAndShade, or even the accent color in the target case of no line, different marker fill vs. marker line.
So this is interesting. I create a line chart using all defaults, and then I run this procedure:
Sub getLineCOlors()
Dim cht As Chart
Dim srs As Series
Dim colors As String
Dim pt As Point
Set cht = ActiveSheet.ChartObjects(1).Chart
For Each srs In cht.SeriesCollection
With srs.Format.Line
colors = colors & vbCrLf & srs.Name & " : " & _
.ForeColor.RGB
End With
Next
Debug.Print "Line Colors", colors
End Sub
The Immediate window then displays:
Line Colors
Series1 : 16777215
Series2 : 16777215
Series3 : 16777215
But this is clearly not the case. It is obvious that they all are different colors. If, instead of .RGB I do .ObjectThemeColor, then I get all 0, which is equally and demonstrably false by observing the chart!
Line Colors
Series1 : 0
Series2 : 0
Series3 : 0
Now here is where it gets interesting:
If, after having created the chart I change the series colors (or even leave them unchanged, by assigning to the same ThemeColors), then the function shows valid RGBs:
Line Colors
Series1 : 5066944
Series2 : 12419407
Series3 : 5880731
It is as if Excel (and PowerPoint/etc.) are completely unable to recognize the automatically assigned colors, on Line Charts. Once you assign a color, then it may be able to read the color.
NOTE: Line charts are picky, because you don't have a .Fill, but rather a .Format.Line.ForeColor (and .BackColor) and IIRC there are some other quirks, too, like you can select an individual point and change it's fill color, and then that affects the visual appearance of the preceding line segment, etc...
Is this limited to line charts? Perhaps. My past experience says "probably", although I am not in a position to say that this is a bug, it certainly seems to be a bug.
If I run a similar procedure on a Column Chart -- again using only the default colors that are automatically assigned,
Sub getCOlumnColors()
Dim cht As Chart
Dim srs As Series
Dim colors As String
Dim pt As Point
Set cht = ActiveSheet.ChartObjects(2).Chart
For Each srs In cht.SeriesCollection
With srs.Format.Fill
colors = colors & vbCrLf & srs.Name & " : " & _
.ForeColor.RGB
End With
Next
Debug.Print "Column Colors", colors
End Sub
Then I get what appear to be valid RGB values:
Column Colors
Series1 : 12419407
Series2 : 5066944
Series3 : 5880731
HOWEVER: It still doesn't recognize a valid ObjectThemeColor. If I change .RGB then this outputs:
Column Colors
Series1 : 0
Series2 : 0
Series3 : 0
So based on these observations, there is certainly some inability to access the ObjectThemeColor and/or .RGB property of automatically-assigned color formats.
As Tim Williams confirms, this was a bug as far back as 2005 at least as it pertains to the RGB, and probably that bug carried over in to Excel 2007+ with the ObjectThemeColor, etc... It is not likely to be resolved any time soon then, so we need a hack solution :)
UPDATED SOLUTION
Combine the two methods above! Convert each series from line to xlColumnClustered, then query the color property from the .Fill, and then change the series chart type back to its original state. This may be more reliable than trying to exploit the sequential indexing (which will not be reliable at all if the users have re-ordered the series, e.g., such that "Series1" is at index 3, etc.)
Sub getLineColors()
Dim cht As Chart
Dim chtType As Long
Dim srs As Series
Dim colors As String
Set cht = ActiveSheet.ChartObjects(1).Chart
For Each srs In cht.SeriesCollection
chtType = srs.ChartType
'Temporarily turn this in to a column chart:
srs.ChartType = 51
colors = colors & vbCrLf & srs.Name & " : " & _
srs.Format.Fill.ForeColor.RGB
'reset the chart type to its original state:
srs.ChartType = chtType
Next
Debug.Print "Line Colors", colors
End Sub
credit: David Zemens
It is one of solutions DeerSpotter.
But after you change your chart type to some temp ChartType and then changing it back to original one. You are losing some format. For an example, you get second axis after it, what is not always need.
So, when your chart line color set automatically, you can not simply get color index of it. You would always receive 0 or 16777215 - white color.
The best solution is to just before you try to change or get your chart line color, to do this little trick with Line.Visible tur off and then trun on. After it you can simply read and chnage your line color.
ppSeries.Format.Line.Visible = msoFalse
ppSeries.Format.Line.Visible = msoTrue