Checking values on a pie chart, deleting if 0% - excel

I have to print out a few thousand pie charts. I don't want to show 0% and was trying to make a macro that would check for that value and then delete if it was 0%. I used Record Macro to get the following:
ActiveChart.FullSeriesCollection(1).DataLabels.Select
ActiveChart.FullSeriesCollection(1).Points(1).DataLabel.Select
Selection.Delete
So I tried to add an if statement like this:
ActiveChart.FullSeriesCollection(1).DataLabels.Select
ActiveChart.FullSeriesCollection(1).Points(1).DataLabel.Select
If Selection.Value = 0 Then
Selection.Delete
End If
But it doesn't work. Any suggestions on how to check the value?

May this can help you:
Set cht = ActiveSheet.ChartObjects(1).Chart
Set a = cht.SeriesCollection(1)
Valueschart = a.Values
For x = LBound(Valueschart) To UBound(Valueschart)
If Valueschart(x) = 0 Then
With a.Points(x)
ActiveSheet.ChartObjects(1).Delete
End With
End If
Next x

Your code fails because a DataLabel has no Value property. It has a Caption-property, so you could check if the caption is 0%, but that's rather dirty and will fail if the data label is formatted differently (eg showing absolute values).
You can check the value itself by reading the Values-property of the data series that is displayed, that's much cleaner.
Furthermore, you should avoid using Select. The macro recorder needs to work on Selection, but as a programmer, you can use the matching objects directly.
I would suggest that you don't delete the labels. Problem with deleting is that once the data is changed and the value is no longer 0, it will be tricky to get it back. The following code is not deleting the labels but just makes them invisible by setting the height to 0. When the values changes and you run the code again, it will get it's original height and is visible again.
The following routine sets the labels for one chart:
Sub HideZeroValueLabels(ch As Chart)
' Get the Values of the data series
Dim vals
vals = ch.FullSeriesCollection(1).Values
' First loop: Remember height of labels (for restore)
Dim i As Long, p As Point, maxPointLableHeight As Double
For i = 1 To ch.FullSeriesCollection(1).Points.Count
Set p = ch.FullSeriesCollection(1).Points(i)
On Error Resume Next
If p.DataLabel.Height > maxPointLableHeight Then maxPointLableHeight = p.DataLabel.Height
On Error GoTo 0
Next
' Second loop: Set height to 0 if value is 0, this makes it invisible
For i = 1 To ch.FullSeriesCollection(1).Points.Count
Set p = ch.FullSeriesCollection(1).Points(i)
On Error Resume Next
If vals(i) = 0 Then
p.DataLabel.Height = 0
Else
p.DataLabel.Height = maxPointLableHeight
End If
On Error GoTo 0
Next
End Sub
You can call the routine for every Pie chart of a sheet:
Dim co As ChartObject, ch As Chart
For Each co In activeSheet.ChartObjects
Set ch = co.Chart
If ch.ChartType = xl3DPie Or ch.ChartType = xl3DPieExploded _
Or ch.ChartType = xlPie Or ch.ChartType = xlPieExploded Then
HideZeroValueLabels ch
End If
Next

Related

Restore chart default colours after deleting series

Hi I can't believe I can't find an answer to this but I have looked.
In Excel I want to restore the default colour palette order to a chart that I have deleted some series from.
Ie if my colour palette goes: red, green, blue, yellow, orange, grey
and I originally have a chart with 6 lines they are in this order.
If I then delete the green, blue and yellow lines:
I am hoping there is a way to update the chart so instead of the remaining red, orange, grey I can return it to red, green, blue - Ie what I would get if I recreated the final chart from scratch:
Obviously I could recreate from scratch or manually change colours but I do this fairly frequently and its often a lot easy to select all columns and delete the ones I don't want than to go through and make a chart line by line.
I'm using corporate excel with minimal permissions so no addins please. Just want to know if this is possible in standard excel environment.
I did some testing, most macros that just adjust the colors seem to create a mess, especially if you want to adjust the chart by using the "chart design->change colours" option. So I went for "remove all series and add them again" approach. This works fine on my PC (Office 365). Select the chart and run this macro (you can assign a shortcut key to make this faster: https://www.excelcampus.com/vba/keyboard-shortcut-run-macro/ ):
Sub ChartRemoveReAddData()
Dim DataArr() As String
Dim n As Long
Set AChart = Application.ActiveChart
If Not AChart Is Nothing Then
'First add series to DataArr
i = 0
For Each Ser1 In AChart.SeriesCollection
sFmla = Ser1.Formula
ReDim Preserve DataArr(i)
DataArr(i) = sFmla
i = i + 1
Next Ser1
'Again, but now reverse to remove them
For n = AChart.SeriesCollection.Count To 1 Step -1
AChart.SeriesCollection(n).Delete
Next n
AChart.ClearToMatchStyle
AChart.ClearToMatchColorStyle
For i = 0 To UBound(DataArr)
Set S = AChart.SeriesCollection.NewSeries
S.Formula = DataArr(i)
Next i
End If
End Sub
And a bit more detail to the colouring: Excel has 10 theme colours (plus 2 for hyperlinks) that you can find if you click e.g. the font colour or background colour button. The last 6 of that overview are the relevant ones for charts. The are named Accent 1 to Accent 6 (https://learn.microsoft.com/en-us/office/vba/api/Office.MsoThemeColorIndex). And as you can see those colours are showing up when you select your chart and select "chart design - change colours". So e.g. ChartColor = 11 (in VBA) means that your chart shows the colours Accent 1 (first series), Accent 3 (second series), Accent 5 (3rd series).
A manual procedure is to save the graph model and reload it once the series have been deleted.
A VBA solution might be similar to this one:
Sub SubParallelMeridianFormat()
'Declarations.
Dim IntCounter01 As Integer
Dim Ser01 As Series
Dim Char01 As Object
Dim LngColourPalette(1 To 6) As Long
'Setting LngColourPalette.
LngColourPalette(1) = vbRed
LngColourPalette(2) = vbGreen
LngColourPalette(3) = vbBlue
LngColourPalette(4) = vbYellow
LngColourPalette(5) = RGB(255, 165, 0)
LngColourPalette(6) = 16711680
'Setting Char01.
Set Char01 = ActiveSheet.Shapes("Graph 1")
'If there are not enouth colour, the macro is terminated.
If Char01.Chart.SeriesCollection.Count > UBound(LngColourPalette) Then
MsgBox "The graph contains " & Char01.Chart.SeriesCollection.Count & " series while only " & UBound(LngColourPalette) & " colours have been specified. No changes will be applied. Add more colours to the code and try again.", vbCritical + vbOKOnly, "Not enouth colour"
Exit Sub
End If
'Changing colours.
For Each Ser01 In Char01.Chart.SeriesCollection
IntCounter01 = IntCounter01 + 1
Ser01.Format.Line.ForeColor.RGB = LngColourPalette(IntCounter01)
Next
End Sub
You might need to edit the settings of LngColourPalette and Char01.
In order to obtain a list of colour from a pre-existing graph, you might use this code:
Sub SubColourList()
'Declarations.
Dim Ser01 As Series
Dim Char01 As Object
Dim IntCounter01 As Integer
'Setting Char01.
Set Char01 = ActiveSheet.Shapes("Graph 1")
'Reporting colours.
For Each Ser01 In Char01.Chart.SeriesCollection
IntCounter01 = IntCounter01 + 1
Debug.Print "LngColourPalette(" & IntCounter01 & ") = "; Ser01.Format.Line.ForeColor.RGB
Next
End Sub
Here you have a list of colour constants for VBA and a list of colour codes in multiple coding style.
This approach will go through the chart, capture the formulas of the existing series, delete the existing series, and add them back from the stored formulas. In so doing, Excel will reapply the default colors.
I think it's simpler than the other proposed answers, even the one marked "Answer", and it will actually do what is requested.
Sub RebuildChartWithDefaultSeriesColors()
With ActiveChart.SeriesCollection
Dim nSrs As Long
nSrs = .Count
Dim vSrsFmla As Variant
ReDim vSrsFmla(1 To nSrs)
Dim iSrs As Long
For iSrs = nSrs To 1 Step -1
vSrsFmla(iSrs) = .Item(iSrs).Formula
.Item(iSrs).Delete
Next
For iSrs = 1 To nSrs
With .NewSeries
.Formula = vSrsFmla(iSrs)
End With
Next
End With
End Sub

VBA - Change every points color on a chart based on their values [duplicate]

I am trying to have the points in a chart change color if they are within certain value paramaters (i.e., >1 is green, <1 is red, anything else is blue). I cannot determine how to get VBA to give me the value of any given point.
In this thread, previously answered, the answer (very helpful in other ways) indicates that points(num).value will return the value at the point. However, I am getting an error message doing this, and nowhere else online or in the VBA help can I find a method that corresponds to this. Has anyone else had any success with this?
Here's the snippet of code giving me trouble:
For Count = 1 To 7
If Worksheets("Sheet1").ChartObjects("ChartName").Chart.SeriesCollection(1).Points(Count).Value > 1 Then
'... do stuff
Because of the way the data are stored in the dataset, it would definitely be better to get the value from the chart directly. I could figure out a workaround using the dataset itself, but I would rather avoid that.
Sub Tester()
Dim cht As Chart, s As Series, p As Point
Dim vals, x As Integer
Set cht = ActiveSheet.ChartObjects(1).Chart
Set s = cht.SeriesCollection(1)
vals = s.Values
For x = LBound(vals) To UBound(vals)
If vals(x) > 10 Then
With s.Points(x)
.MarkerBackgroundColor = RGB(255, 0, 0)
.MarkerForegroundColor = RGB(255, 0, 0)
End With
End If
Next x
End Sub

Line chart marker value label switching up and down

Is there a clever way to place label indicating value above marker, for next point below marker and so on like:
(62.2% above, 71.6% below, 77.3% above, 84.9% below...)
I know that I can manually place each label but I search for automatic way to achieve it.
Maybe some kind of settings/formula/VBA macro?
Run this macro:
Public Sub alternateLabels()
Dim ch As Chart
Dim lab As DataLabel
Dim s As Series
Dim count As Integer
' use the appropriate names for the objects and worksheets here
Set ch = ThisWorkbook.Worksheets("Sheet1").ChartObjects("Chart 1").Chart
' this should be the "cumulative" series, check with msgbox, and remove msgbox line if it's ok
Set s = ch.SeriesCollection(2)
MsgBox s.Name 'remove this line as needed
For Each lab In s.DataLabels
If count Mod 2 = 0 Then
lab.Position = xlLabelPositionAbove
Else
lab.Position = xlLabelPositionBelow
End If
count = count + 1
Next lab
End Sub

Graphing blank cells in form of gaps in Excel

I'm graphing a set of data that has blanks in some cells. In the blank cells I have formulas and I have to keep the formulas. When I graph the data, the blank cells are graphed as zeros. I'd like to put gaps instead of zeros in the graph.
I tried right click on the graph > Select Data > Hidden and Empty Cells Settings > Show empty cells as Gaps. But this did not help!
Instead of putting zeros or empty strings try to put #N/A.
You can do it with a formula like =IF([test],[value],NA()).
This will allow the graph not to show the missing values as zeros, but if I understand your question, it is still not what you want, because you want the missing values to be represented as gaps, not as missing values.
The only way that I know of to see the gaps is to use a scattered graph.
As far as I know, all the graphs that make a line to join two points, do join two points, and don't have the concept of missing point. They just join the two closest points.
A solution could be to make a VBA macro that goes inside the graph and changes the color of each graph line when the data is missing.
A solution could be to make a VBA macro that goes inside the graph and changes the color of each graph line when the data is missing.
I have code, that modifies charts.
It works for cells with #N/A, also na() function. Like old excel did.
First, you need a module with public sub:
Public Sub FormatNA()
Dim myChart As ChartObject
Dim series_i As Integer, series_count As Integer
Dim values_i As Integer, values_count As Integer
Dim rows As Integer, r As Integer
Dim mySeries As Object
Dim myValues As Variant
Dim myPoint As Object
Application.ScreenUpdating = False
If ActiveSheet.ChartObjects.Count = 0 Then Exit Sub
' for each chart in active sheet
For Each myChart In ActiveSheet.ChartObjects
' Determine Chart Type
Select Case myChart.Chart.ChartType
Case xlLine, _
xlLineMarkers, _
xlLineMarkersStacked, _
xlLineMarkersStacked100, _
xlLineStacked, _
xlLineStacked100, _
xlXYScatter, _
xlXYScatterLines, _
xlXYScatterLinesNoMarkers, _
xlXYScatterSmooth, _
xlXYScatterSmoothNoMarkers
' for each series
series_count = myChart.Chart.SeriesCollection.Count
For series_i = 1 To series_count
' for each data
Set mySeries = myChart.Chart.SeriesCollection(series_i)
Set myPoint = mySeries.Points(1)
myValues = mySeries.Values
values_count = UBound(myValues)
' global formatting:
Select Case mySeries.ChartType
' MARKERS:
Case xlLineMarkers, _
xlLineMarkersStacked, _
xlLineMarkersStacked100, _
xlXYScatter, _
xlXYScatterLines, _
xlXYScatterSmooth
With mySeries
.MarkerForegroundColorIndex = myPoint.MarkerForegroundColorIndex
.MarkerForegroundColor = myPoint.MarkerForegroundColor
.MarkerBackgroundColorIndex = myPoint.MarkerBackgroundColorIndex
.MarkerBackgroundColor = myPoint.MarkerBackgroundColor
.MarkerForegroundColor = myPoint.MarkerForegroundColor
.MarkerSize = myPoint.MarkerSize
.MarkerStyle = myPoint.MarkerStyle
End With
' NO MARKERS, JUST LINE:
Case Else
End Select
With mySeries
.Border.Color = myPoint.Border.Color
.Border.Weight = myPoint.Border.Weight
With .Format.Line
.ForeColor.RGB = myPoint.Format.Line.ForeColor.RGB
.BackColor.RGB = myPoint.Format.Line.BackColor.RGB
.Weight = myPoint.Format.Line.Weight
.Visible = msoTrue
End With
End With
For values_i = 2 To values_count
' set line invisible if #NA
If IsEmpty(myValues(values_i - 1)) And Not IsEmpty(myValues(values_i)) Then
mySeries.Points(values_i).Format.Line.Visible = msoFalse
'mySeries.Points(values_i).Border.Color = RGB(255, 255, 255) ' for debugging
'mySeries.Points(values_i).Border.Weight = 1
End If
Next values_i
Next series_i
Case Else
' different chart type
End Select
Next
Application.ScreenUpdating = True
End Sub
Then, you'll have to trigger this sub everytime you calculate worksheet:
In ThisWorkbook define sub:
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Static Calculated As Boolean
If Not Calculated Then
Call FormatNA
Calculated = True
Else
Calculated = False
End If
End Sub
Maybe it's not perfect, but it works for me. Sample of manipulated chart
May be it might be Usefull any one how has this problem,
Step1: First get Chartpage access and use Display blank as
Excel.Chart chartPage = myChart.Chart;
chartPage.DisplayBlanksAs = Excel.XlDisplayBlanksAs.xlInterpolated;
Happy Coding.
As stenci said, it's difficult to create a gap without VBA due to the presence of formulas in the cells. A time consuming solution is to delete the formulas, which provided blank cells, one by one so that they will then graph as gaps.
For a large dataset that might be too time consuming.
There's a workaround if you're willing to open and close the file:
Set the blank cell to appear empty. For example: =IF(COUNT(A1)>0,A1,"");
Save a copy of your workbook in your preferred format because the next step will eliminate the formulas;
Save the workbook as a .CSV file with a different file name;
Close the file. Then reopen the file;
Now a line graph will provide gaps for the empty cells.
Note that both sides of the gap need to have a line segment, i.e. at least two data cells on both sides of the gap. Specifically, this will graph a gap:
A1=1, A2=2, A3=(blank), A4=4, A5=5.
And this will not graph a gap:
A1=1, A2=(blank), A3=3, A4=4.

Excel macro to fix overlapping data labels in line chart

I am searching/trying to make a macro to fix the position of data labels in a line chart with one or multiple series collections so that they will not overlap each other.
I was thinking of some ways for my macro but when I try to make it I understand that this is way too hard for me and I get headache.
Is there anything that I missed? Do you know about such a macro?
Here's an example chart with overlapped data labels:
Here's an example chart where I manually fixed the data labels:
This task basically breaks down to two steps: access the Chart object to get the Labels, and manipulate the label positions to avoid overlap.
For the sample given all series are plotted on a common X-axis and the X values are sufficiently spread that labels don't overlap in this dimension. Therefore the solution offered only deals with groups of labels for each X point in turn.
Accessing the Labels
This Sub parses the chart and creates an array of Labels for each X point in turn
Sub MoveLabels()
Dim sh As Worksheet
Dim ch As Chart
Dim sers As SeriesCollection
Dim ser As Series
Dim i As Long, pt As Long
Dim dLabels() As DataLabel
Set sh = ActiveSheet
Set ch = sh.ChartObjects("Chart 1").Chart
Set sers = ch.SeriesCollection
ReDim dLabels(1 To sers.Count)
For pt = 1 To sers(1).Points.Count
For i = 1 To sers.Count
Set dLabels(i) = sers(i).Points(pt).DataLabel
Next
AdjustLabels dLabels ' This Sub is to deal with the overlaps
Next
End Sub
Detect Overlaps
This calls AdjustLables with an array of Labels. These labels need to be checked for overlap
Sub AdjustLabels(ByRef v() As DataLabel)
Dim i As Long, j As Long
For i = LBound(v) To UBound(v) - 1
For j = LBound(v) + 1 To UBound(v)
If v(i).Left <= v(j).Left Then
If v(i).Top <= v(j).Top Then
If (v(j).Top - v(i).Top) < v(i).Height _
And (v(j).Left - v(i).Left) < v(i).Width Then
' Overlap!
End If
Else
If (v(i).Top - v(j).Top) < v(j).Height _
And (v(j).Left - v(i).Left) < v(i).Width Then
' Overlap!
End If
End If
Else
If v(i).Top <= v(j).Top Then
If (v(j).Top - v(i).Top) < v(i).Height _
And (v(i).Left - v(j).Left) < v(j).Width Then
' Overlap!
End If
Else
If (v(i).Top - v(j).Top) < v(j).Height _
And (v(i).Left - v(j).Left) < v(j).Width Then
' Overlap!
End If
End If
End If
Next j, i
End Sub
Moving Labels
When an overlap is detected you need a strategy that move one or both labels without creating another overlap.
There are many possibilities here, you havn'e given sufficient details to judge your requirements.
Note about Excel
For this approach to work you need a version of Excel that has DataLabel.Width and DataLabel.Height properties. Version 2003 SP2 (and, presumably, earlier) does not.
This macro will prevent overlapping labels on 2 line charts when data source is listed in two adjacent columns.
Attribute VB_Name = "DataLabel_Location"
Option Explicit
Sub DataLabel_Location()
'
'
' *******move data label above or below line graph depending or other line graphs in same chart***********
Dim Start As Integer, ColStart As String, ColStart1 As String
Dim RowStart As Integer, Num As Integer, x As Integer, Cell As Integer, RowEnd As Integer
Dim Chart As String, Value1 As Single, String1 As String
Dim Mycolumn As Integer
Dim Ans As String
Dim ChartNum As Integer
Ans = MsgBox("Was first data point selected?", vbYesNo)
Select Case Ans
Case vbNo
MsgBox "Select first data pt then restart macro."
Exit Sub
End Select
On Error Resume Next
ChartNum = InputBox("Please enter Chart #")
Chart = "Chart " & ChartNum
ActiveSheet.Select
ActiveCell.Select
RowStart = Selection.row
ColStart = Selection.Column
ColStart1 = ColStart + 1
ColStart = ColNumToLet(Selection.Column)
RowEnd = ActiveCell.End(xlDown).row
ColStart1 = ColNumToLet(ActiveCell.Offset(0, 1).Column)
Num = RowEnd - RowStart + 1
With ThisWorkbook.ActiveSheet.Select
ActiveSheet.ChartObjects(Chart).Activate
ActiveChart.SeriesCollection(1).ApplyDataLabels
ActiveChart.SeriesCollection(2).ApplyDataLabels
End With
For x = 1 To Num
Value1 = Range(ColStart & RowStart).Value
String1 = Range(ColStart1 & RowStart).Value
If Value1 = 0 Then
ActiveSheet.ChartObjects(Chart).Activate
ActiveChart.SeriesCollection(1).DataLabels(x).Select
Selection.Delete
End If
If String1 = 0 Then
ActiveSheet.ChartObjects(Chart).Activate
ActiveChart.SeriesCollection(2).DataLabels(x).Select
Selection.Delete
End If
If Value1 <= String1 Then
ActiveSheet.ChartObjects("Chart").Activate
ActiveChart.SeriesCollection(1).DataLabels(x).Select
Selection.Position = xlLabelPositionBelow
ActiveChart.SeriesCollection(2).DataLabels(x).Select
Selection.Position = xlLabelPositionAbove
Else
ActiveSheet.ChartObjects("Chart").Activate
ActiveChart.SeriesCollection(1).DataLabels(x).Select
Selection.Position = xlLabelPositionAbove
ActiveChart.SeriesCollection(2).DataLabels(x).Select
Selection.Position = xlLabelPositionBelow
End If
RowStart = RowStart + 1
Next x
End Sub
'
' convert column # to column letters
'
Function ColNumToLet(Mycolumn As Integer) As String
If Mycolumn > 26 Then
ColNumToLet = Chr(Int((Mycolumn - 1) / 26) + 64) & Chr(((Mycolumn - 1) Mod 26) + 65)
Else
ColNumToLet = Chr(Mycolumn + 64)
End If
End Function
Allthough I agree that regular Excel formulas can't fix everything, I dislike VBA. There are several reasons for this, but the most important one is that chances are it will stop working with the next upgrade. I'm not saying you shouldn't use VBA at all, but only use it when necessary.
Your question is a good example of a need where VBA isn't necessary.. "OK" you say, "but then how do I fix this problem?" Feel lucky and click this link to my answer to a related question here.
What you'll find out in the link is, how you can measure your charts' exact grid. When your x-axis crosses at 0, you'll only need the maximum Y-axis label for that. You're only half way there now, cause your specific problem isn't solved yet. Here's how I would proceed:
First measure how high your labels are compared to the height of your chart. This will need some trial and error, but shouldnt be very difficult. If your chart can stack 20 labels without overlapping, this number would be 0.05 for example.
Next determine if and where any of the labels would overlap. This is quite easy, cause all you need to do is find out where numbers are too close to each other (within the 0.05 range in my example).
Use some boolean tests or for all I care IF formulas to find out. The result you're after is a table with the answers for each of the series (except the first one). Don't be afraid to duplicate that table again for the next step: creating the new chart input.
There are several ways to create the new chart, but here's the one I'd choose. For each of the series create three lines. One is the actual line, the other two are the invisible lines with just the data labels. For each of the lines there is one invisible line with just the regular labels. Those all use the same alignment. Each extra invisible line has a different allignment for the labels. You won't need one for your first series, but for the second one the label would be to the right, the third one beneath and the fourth one to the left (for example).
When none of the data labels overlap only the first invisible lines (with regular alignment) need to show the values. When labels do overlap, the corresponding extra invisible line should take over on that point and show its label. Of course the first invisible line should not show one there.
When all four labels overlap at the same x-axis value, you should see the first basic invisible line's label and the three extra invisible lines' labels. This should work for your example chart, cause there is enough room to move to labels to the left and right. Personally I'd stick with just the minimum and the maximum label at an overlapping point, cause the fact it overlaps shows the values are pretty close to each other in the first place..
I hope this helped you,
Greetings,
Patrick
#chris neilsen
Could you test your solution on Excel 2007?
When I cast the objects to DataLabel class, it looks like the .Width property has been removed from the class.
(Sorry, I was not permitted to comment on your reply)
Maybe one thing to add from below forum is to temporary adjust position of label:
http://www.ozgrid.com/forum/showthread.php?t=90439
"you get close width or height value of the data label by forcing the label off of the chart and comparing the reported left/top value to that of the chartarea inside width/height."
Based on this, please move v(i).Width & v(j).Width to a variables sng_vi_Width & sng_vj_Width and add these lines
With v(i)
sngOriginalLeft = .Left
.Left = .Parent.Parent.Parent.Parent.ChartArea.Width
sng_vi_Width = .Parent.Parent.Parent.Parent.ChartArea.Width - .Left
.Left = sngOriginalLeft
End With
With v(j)
sngOriginalLeft = .Left
.Left = .Parent.Parent.Parent.Parent.ChartArea.Width
sng_vj_Width = .Parent.Parent.Parent.Parent.ChartArea.Width - .Left
.Left = sngOriginalLeft
End With

Resources