I want to have labels next to data points in an Excel chart. There is a VBA code from Microsoft for this purpose:
http://support2.microsoft.com/kb/914813/en-us
Sub AttachLabelsToPoints()
'Dimension variables.
Dim Counter As Integer, ChartName As String, xVals As String
' Disable screen updating while the subroutine is run.
Application.ScreenUpdating = False
'Store the formula for the first series in "xVals".
xVals = ActiveChart.SeriesCollection(1).Formula
'Extract the range for the data from xVals.
xVals = Mid(xVals, InStr(InStr(xVals, ","), xVals, _
Mid(Left(xVals, InStr(xVals, "!") - 1), 9)))
xVals = Left(xVals, InStr(InStr(xVals, "!"), xVals, ",") - 1)
Do While Left(xVals, 1) = ","
xVals = Mid(xVals, 2)
Loop
'Attach a label to each data point in the chart.
For Counter = 1 To Range(xVals).Cells.Count
ActiveChart.SeriesCollection(1).Points(Counter).HasDataLabel = _
True
ActiveChart.SeriesCollection(1).Points(Counter).DataLabel.Text = _
Range(xVals).Cells(Counter, 1).Offset(0, -1).Value
Next Counter
End Sub
It works so far. But only if the collection has no name:
When I name the collection then the macro returns an error:
Does anyone know how to use the code provided by Mircosoft and still be able to name the data collection?
Excel 2013 introduced the capability to label a chart series with data from cells, after many years of users begging for it. Select the series, and add data labels. Select the data labels and format them. Under Label Options in the task pane, look for Label Contains, select the Value From Cells option, and select the range containing the label text.
And even before this, you could use a free add-in called the XY Chart Labeler (which works on all charts that support data labels, not just XY charts), which you can download from Applications Professionals. It's written by Rob Bovey, a former Microsoft Excel MVP.
I had the same problem. All you need to do is replace the hardcoded '9' with 'InStr(xVals, ",")' and it will accept any length SERIES name in the field before the first comma.
There already are some good answers like ZAT's one, explaining how to add labels to a data point in native Excel with VBA language.
But if you don't know anything about VBA it might be difficult to understand. For complex charts like this one I prefer to use Javascript which I think is more "readable" than VBA. And if you want to make a dynamic and interactive chart, javascript comes with a lot of powerful libraries.
Here is a working code I have written for you, with plotly.js (the documentation is very good for js beginners) :
https://www.funfun.io/1/#/edit/5a60bbe7404f66229bda3e39
So to build this chart I put my data in the embedded spreadsheet, which I can then use in my javascript code thanks to a Json file.
I can create a scatter plot like so :
var trace1 = {
x: firstX,
y: firstY,
text: firstLabel,
mode: 'markers+text',
textposition:'top right'
};
The firstX and firstY variable are the X and Y values.
To add a label to each point I added a label to text and changed the mode to marker+textinstead of just marker.
Once you've made your chart you can load it in Excel by passing the URL in an Excel add-in called Funfun.
Here is how it looks like:
Disclosure : I’m a developer of funfun
Try this after chart generation (assuming chart in the same sheet):
(modify this according to your need)
Option Explicit
Sub RenameChartDataLabel()
Dim rngDLabel As Range
Dim iii as integer, pp as integer, dlcount as integer
Set rngDLabel = ActiveSheet.Range("A2:A6") 'change range for datalabels text
ActiveSheet.ChartObjects("Chart 2").Activate 'change chart name
dlcount = ActiveChart.SeriesCollection(1).DataLabels.Count
iii = 1
pp = 1
For iii = dlcount To 1 Step -1
ActiveChart.SeriesCollection(1).DataLabels(iii).Select
Selection.Text = rngDLabel(pp).Value
Selection.Font.Bold = True
Selection.Position = xlLabelPositionAbove
pp = pp + 1
Next
Set rngDLabel = Nothing
End Sub
Related
I often make a lot of scatter plots (column j vs column i) in a single Worksheet. I want to export them as png/jpg files. Each plot would need a sensible file name. I have thought that the file name could be something like plot_[column i]_[column j].png.
How do I get the column (like C or AE) from each plot (or ActiveChart)? Then I can create a file name string to be fed in to the Export method. I am a complete beginner for VBA macros, but understand some Visual Basic.
You can extract that information from the source data string using text functions. The source data is available using .SeriesCollection:
activesheet.chartobjects("Chart 1").chart.SeriesCollection(1).Formula
will return something like this:
"=SERIES(,Sheet1!$A$1:$A$4,Sheet1!$B$1:$B$4,1)"
That contains the two columns you need, "A" and "B". You can extract them using text functions like INSTR(), MID(), and LEFT(). Here is an example using debug.print to output the columns. I'm assuming you already know how to export them since that was not included in your question.
Sub FindSourceColumns()
Dim sourcedata, firstcolumn, secondcolumn As String, c as chartobject
for each c in activesheet.chartobjects
sourcedata = c.Chart.SeriesCollection(1).Formula
firstcolumn = Mid(sourcedata, InStr(sourcedata, "!$") + 2, 5)
firstcolumn = Left(firstcolumn, InStr(firstcolumn, "$") - 1)
Debug.Print firstcolumn
secondcolumn = Mid(sourcedata, InStr(InStr(sourcedata, "!$") + 2, sourcedata, "!$") + 2, 5)
secondcolumn = Left(secondcolumn, InStr(secondcolumn, "$") - 1)
Debug.Print secondcolumn
next c
End Sub
I have dynamically changing table in Excel and need for a bubble chart to automatically be based off the table even when data changes. The key is that each ROW in the table represents a series to be in the bubble chart.
Series A 5 10 5%
Series B 4 8 3%
.
.
I understand how to use dynamic ranges without VBA for bubble but since I need to create/update dynamic series from my research I need to use VBA. I have written the following code but am getting error message that the chart can't be found even though I have checked 50 times I have the correct name
I need the VBA code, to reference the Existing chart on Sheet11, and update it (add/edit) based on table.
My code:
Public Sub CreateMultiSeriesBubbleChart()
Dim bubbleChart As ChartObject
Set bubbleChart = Sheet11.ChartObjects("Chart 13") ''verified i have chart 13 on sheet 11
Dim r As Integer
For r = 2 To 201
With bubbleChart.Chart.SeriesCollection.NewSeries
.Name = "=" & Sheet11.Cells(r, 1).Address(External:=True)
.XValues = Sheet11.Cells(r, 2).Address(External:=True)
.Values = Sheet11.Cells(r, 3).Address(External:=True)
.BubbleSizes = Sheet11.Cells(r, 4).Address(External:=True)
End With
Next
bubbleChart.Chart.SetElement (msoElementPrimaryCategoryAxisTitleAdjacentToAxis)
bubbleChart.Chart.Axes(xlCategory, xlPrimary).AxisTitle.Text = "=" & Sheet11.Cells(1, 2).Address(External:=True)
bubbleChart.Chart.SetElement (msoElementPrimaryValueAxisTitleRotated)
bubbleChart.Chart.Axes(xlValue, xlPrimary).AxisTitle.Text = "=" & Sheet11.Cells(1, 3).Address(External:=True)
''bubbleChart.Chart.Axes(xlValue).MinimumScaleIsAuto = True
''bubbleChart.Chart.Axes(xlValue).MaximumScaleIsAuto = True
''bubbleChart.Chart.SetElement (msoElementPrimaryCategoryGridLinesMajor)
End Sub
Unfortunately, this code provides error on Set bubbleChart line saying 'Application defined or Object defined error'. I can use very similar code to create a new chart so I figure the rest works properly but I can't reference existing chart which is what I need to be able to do
You need to change the way your referencing your sheet. Use Sheets("Sheet11") instead of Sheet11
Try this:
Set bubbleChart = Sheets("Sheet11").ChartObjects("Chart 13")
Note that you can call the name or the index this way. For example:
Sheets("MySheetName").select
or
Sheets(11).select
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.
I want to add number (0) to values and find a bit of trouble with that. Excels macro record this as
Sub Makro2()
ActiveSheet.ChartObjects("Chart 1").Activate
ActiveChart.SeriesCollection.NewSeries
ActiveChart.SeriesCollection(2).XValues = _
"='Sheet'!$C$221;'Sheet'!$C$223;'Sheet'!$C$225"
ActiveChart.SeriesCollection(2).Values = _
"='Sheet'!$B$222;'Sheet'!$B$224;'Sheet'!$B$226"
End Sub
But when I try the same with my code I get error.
Dim lineSeries1 As Range
Dim lineSeries2 As Range
With ActiveChart.SeriesCollection.NewSeries
.Values = "={0;100}" 'It works
.Name = ""
.XValues = "={0;100}" 'It works
End With
With ActiveChart.SeriesCollection.NewSeries
.Values = lineSeries1 ' + {0} or & SomeCellWithZero.Address
.Name = ""
.XValues = lineSeries2 ' + {0} or & SomeCellWithZero.Address
End With
So the question is how to add zero to Values?
Personally I'd make my ranges one cell bigger and add a constant zero value. If that's not possible for some reason, what follows may help ;-)
Here's a slightly roundabout way to get there. It might be possible to do it with fewer steps but I can't see how yet.
I'm going to use a VBA function to build a new array from the original range with a zero included. I'm putting the zero at the start of the array, change the code for something different. Here's the VBA function:
Public Function PrependZero(rng As range)
Dim val As Variant, res As Variant
Dim idx As Long
val = Application.Transpose(rng.Columns(1).Value) ' get range values as a 1-dimensional array, assumes values are in a column
ReDim res(LBound(val) To UBound(val) + 1) ' array to hold the extended values
res(LBound(res)) = 0 ' add the fixed value
For idx = LBound(val) To UBound(val)
res(idx + 1) = val(idx) ' copy the other values
Next
PrependZero = res
End Function
Excel doesn't seem to like us using a VBA function in a series definition, so we need to add some indirection to fool it. Create a new named formula (Formulas...Define Name). I called mine YSeries and set the "Refers to" value to =PrependZero(Sheet1!$A$2:$A$6), using my Y Value range as the input to the function.
That named formula can be used in a chart series definition: set "Series Y Values" to [YourWorkbookNameHere]!YSeries (use whatever name you created above).
If you want to do the same to the X values, the same approach should work.
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