Add zero to charts data source - excel

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.

Related

Assign multiple named ranges to multiple range arrays

I'm kinda new here, but here is what I'm trying to do.
I have a book lets pretend its a warehouse book for inventory, and we have different divisions in our enterprise, I have master sheet with all the goods and some sheets covering those divisions for distribution of goods between them.
My idea is to have a drop down list for each item type in book for separate divisions so i need macro to assign/reassign named range for each item.
I have 2 columns first with stock number and second with serial number , i need to put all the same serial number in the named range of one of stock numbers. if i have two or more serial numbers i need to put an array of serial numbers in named range of one stock number.
Stock numbers are in C column and serial numbers are in D column
I've tried this code
Private Sub CommandButton2_Click()
Dim LastRow As Long
Dim r As Range
LastRow = Cells(Rows.Count, "C").End(xlUp).Row
For Each r In Range("C2:C" & LastRow)
Range(r.Offset(0, 1), r.Offset(0, 1)).Name = r.Value
Next r
End Sub
But thats not realy working, and assigns only one serial number per one named range of stock numbers
================================================================
So i ran this code you proposed in your updated version and struck some problems
Private Sub CommandButton2_Click()
Dim this As Worksheet: Set this = Sheets("ALFA")'renamed this for my book'
Dim that As Worksheet: Set that = Sheets("STORAGE")'renamed that for my book'
serialNumbers = that.Range(that.Columns(3), that.Columns(4))'Could not find method Unique(and there is no mentions about'
'it in MS documentation) for Application object so i changed it to just Range'
For r = 2 To this.UsedRange.Rows.Count
buffer = ""
comma = ""
stockNumber = this.Cells(r, 3)
For x = 2 To UBound(serialNumbers)
If serialNumbers(x, 1) = stockNumber Then
buffer = buffer & comma & serialNumbers(x, 2)
comma = ","
End If
Next
this.Cells(r, 4).Validation.Delete
this.Cells(r, 4).Validation.Add _'After doing everything it strucks with Run time error 1004
Type:=xlValidateList, _ '/Application-defined or object-defined error in this
AlertStyle:=xlValidAlertStop, _'hole'
Formula1:=buffer 'block'
Next
End Sub
And sometimes code just hangs my excel application for atleast 3 mins, i think it's because there is no limit for cells to look up to and eventualy it tries to give all the cells in D:D a validation check
So if you want to set the validation, it is possible to set dynamic ranges BUT the validation won't accept a text list, for instance "one, two, three". The validation is looking for an array of values, and unfortunately it is tricky to pass a dynamic array using formulas only. You can set it up to do a dynamic range, but that would have to point to a range of cells that contain the needed values one per cell.
However, before you set all that up it's probably just easier to set the validation entirely in code. See this google sheet, which just contains the layout for reference. I have the complete list of items in Column 1 & 2 of the sheet (Item, Stock Number) and the complete list of serial numbers in columns 5 & 6 (Stock Number, Serial Number).
Then I run this code:
Sub setValidation()
Dim this As Worksheet: Set this = Sheets("demo")
Dim that As Worksheet: Set that = Sheets("lookups")
serialNumbers = Application.Unique(that.Range(that.Columns(5), that.Columns(6)))
For r = 2 To this.UsedRange.Rows.Count
buffer = ""
comma = ""
stockNumber = this.Cells(r, 3)
For x = 2 To UBound(serialNumbers)
If serialNumbers(x, 1) = stockNumber Then
buffer = buffer & comma & serialNumbers(x, 2)
comma = ","
End If
Next
this.Cells(r, 4).Validation.Delete
this.Cells(r, 4).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Formula1:=buffer
Next
End Sub
We assign some worksheet variables to make it easier to reference them, and then put the stock number/serial number combos into an array (with UNIQUE so I don't have to check for duplicates).
Then I run through the range that needs the validations (demo column 4), getting the stock number from column 3 and then using that stock number to find all serial numbers that match, concatenating them into a string and then using that string to set the validation.
Use Validation.Delete before setting the validation to avoid stacking rules.
Assuming that your version of Excel doesn't have UNIQUE, you can use INTERSECT to control the size of the serialNumbers array, like this:
Sub setValidation()
Dim this As Worksheet: Set this = Sheets("demo")
Dim that As Worksheet: Set that = Sheets("lookups")
serialNumbers = Intersect( _
that.Range(that.Columns(5), that.Columns(6)), _
that.UsedRange _
)
For r = 2 To this.UsedRange.Rows.Count
buffer = ""
comma = ""
stockNumber = this.Cells(r, 3)
For x = 2 To UBound(serialNumbers)
If serialNumbers(x, 1) = stockNumber Then
buffer = buffer & comma & serialNumbers(x, 2)
comma = ","
End If
Next
this.Cells(r, 4).Validation.Delete
this.Cells(r, 4).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Formula1:=buffer
Next
End Sub
Assuming you do have UNIQUE and FILTER in your Excel version, there is another way to do it, using the EVALUATE function to access the Excel function engine. In this case we will just write out a formula just like we would in a cell, and then evaluate it from VBA. Unless specified, evaluate occurs in the context of the active sheet, so that's what I use that.evaluate in this code:
Sub setValidation()
Dim expr As String
Dim this As Worksheet: Set this = Sheets("demo")
Dim that As Worksheet: Set that = Sheets("lookups")
For r = 2 To this.UsedRange.Rows.Count
stockNumber = this.Cells(r, 3)
expr = "Textjoin("","", true, Unique(Filter(F:F, E:E=""" & stockNumber & """)))"
serialNumbers = that.Evaluate(expr)
this.Cells(r, 4).Validation.Delete
this.Cells(r, 4).Validation.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Formula1:=serialNumbers
Next
End Sub
In this case, we use FILTER to return ONLY the serial numbers that match a stock number, UNIQUE to make sure there are no duplicates, and then TEXTJOIN to create a list from that, and then we can just pass that result straight to the validation.
===================================================
Original answer, shows how to get a list of all serial numbers for a specific stock number using only excel formulas, but it became clear that this wouldn't be sufficient, since the lists were going to be used to set validation. Left here for completeness.
So you have two columns, C and D, and you need to get a list of all values in D that match the entries in C. This is actually simple enough to not need code, but you may have more requirements. I'm going to start an answer with just a very basic set of formulas. See this google sheet.
To get a unique list of the stock numbers, I have used UNIQUE(C:C) in G1. This will produce the list in column G.
Then in column H, I have used TEXTJOIN+UNIQUE+FILTER to produce a comma separated list of serial numbers. FILTER takes one input array (in this case Col D) and filters it with another array (Col C) and a condition (the serial number) to return a list of matches, and wrapping that in UNIQUE makes sure that the result array contains only unique values. Wrapping that in TEXTJOIN converts the result array into text.
What I'm not entirely clear on is your need for a named range, or what you will do with the multiple rows in a sheet. For instance, STORAGE rows 35 & 36 are both for DDG_33:
DDG_33 0BV1111
DDG_33 0AV0951
and if you ran some code to produce a list of values and put it in D35 you would have:
DDG_33 0BV1111, 0AV0951
DDG_33 0AV0951
but you would still have two entries for DDG_33. If you ran the code again, you would have
DDG_33 0BV1111, 0AV0951, 0AV0951
DDG_33 0AV0951
and so forth in an infinite loop. It seems like you would need to take the list of unique stock numbers and put them on a different sheet, along with the list of matching serial numbers, but just clarify what you want to do and I can update my answer.

VBA function runs as a macro but gives error when called with function

I have an excel table called AnimeList, where I have listed all the anime I have finished watching along with their info. The table has the following headers:
Name, Main Genre, Genre2, Genre3, Rating, Seasons, Episodes, Mins/Episode, Status.
I have written some VBA code that can count the distinct genres from the 3 columns as well as the number of them present.
Function CountAndSortGenre()
Dim size As Integer: size = Range("AnimeList[Main Genre]").Rows.Count
ReDim genreExtract((size * 3) - 1) As String
Dim i As Integer: i = 0
Dim cell As Range
For Each cell In Range("AnimeList[Main Genre]")
genreExtract(i) = cell.Value
i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 2]")
genreExtract(i) = cell.Value
i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 3]")
genreExtract(i) = cell.Value
i = i + 1
Next
Dim distinctGenres As New Dictionary
Dim genre As Variant
For Each genre In genreExtract
If distinctGenres.exists(genre) Then
distinctGenres(genre) = distinctGenres(genre) + 1
Else
distinctGenres.Add genre, 1
End If
Next
size = distinctGenres.Count
Erase genreExtract
ReDim sortedGenres(size - 1, 1) As Variant
For i = 0 To distinctGenres.Count - 1
sortedGenres(i, 0) = distinctGenres.Keys(i)
sortedGenres(i, 1) = distinctGenres.Items(i)
Next i
distinctGenres.RemoveAll
QuickSort sortedGenres, 0, size - 1 'This is done in a separate function
End Function
At the end I have what I need, i.e. the sorted genre counts in my sortedGenre array.
But I need to output it to the excel sheet now which is proving to be rather difficult task.
I tried calling the function after adding return type "As Variant" in the declaration and adding the statement CountAndSortGenre = sortedGenres at the end like so:
=CountAndSortGenre()
but the array which is returned is not spilled across multiple cells. Instead only the first element of the array is displayed on the cell where I input the formula.
I tried using Ctrl+Shift+Enter which changed the formula to:
{=CountAndSortGenre()}
but that did not change the output. It was still the first element of the array
I tried putting it in the index formula like so:
INDEX(CountAndSortGenre(), 1, 2)
trying to at least get something other than the first value of the array but that still kept returning the first value only.
Afterwards I tried using a manual approach to push the values into the cells by removing the As Variant return type and the return value in the end and adding the following code:
For i = 0 To size - 1
Application.ActiveCell.Offset(i + 1, 1) = sortedGenres(i, 0)
Application.ActiveCell.Offset(i + 1, 2) = sortedGenres(i, 1)
Next i
This approach worked when I ran the code but when I tried using the function like:
= CountAndSortGenre()
Excel gave me circular reference warning and thus it did not work.
The reason I dont want to use the macro and want to use it as a function is that I want these values to get updated as I update my source table. I am not sure that using a function will be dynamic, but it is the best bet. But right now I just want this function to start working.
I used an Array List because I'm too lazy to go look for my QuickSort routine; and I only created a single dimension output for horizontal output.
I used the range as an argument for the function so it would update dynamically when a cell in the called range is changed.
If your range may change in size, I'd suggest using either a dynamic named range, or using a Table with structured references, either of which can auto adjust the size.
If you require a vertical output, you can either Transpose before setting the output of the function; or loop into a 2D array.
Option Explicit
Option Compare Text
Function CountAndSortGenre(rg As Range) As Variant()
Dim v As Variant, w As Variant
Dim distinctGenres As Object
v = rg
Set distinctGenres = CreateObject("System.Collections.ArrayList")
With distinctGenres
For Each w In v
If w <> "" Then
If Not .contains(w) Then .Add w
End If
Next w
.Sort
CountAndSortGenre = .toarray
End With
End Function

Print Up to 300 Strings of Arrays to PDF Based on a Calculated Value

I need to print a string of arrays dependent on a difference of two values on my input page to separate sheets within the same PDF but I have been running into a few issues.
Based on the difference of two cells, the function will determine which arrays to print.
There are two possible solutions I have thought of but have been unsuccessful attempting both.
Indirectly reference a string of arrays in a cell to print such as "abc,bcd,cde,def,efg..."
(As Shown Below) Use conditional if-then functions to invoke the array based on the difference in these two cells
Primary Goals
Print into a single PDF
Determine specific arrays to print depending on the difference in two values contained in a cell on my input page
Allow for PageSetup values (have this figured out)
I am using MSFT 365. I tried initially using an indirect array reference to a cell with a variable value string including the arrays to be included without success.
Next, I tried to hardcode for all 100 possible values for this difference but in that case, I am running into line limits and errors associated with using _ to continue the array function on another line.
If the difference value equals 3, it is shown as below. If the difference value equals 4, you would add another array line including "schedule05","report05","p&l05"
Option Explicit
Sub PrintTest()
'if a certain difference value, use
If (Worksheets("Inputs").Range("D7") - Worksheets("Inputs").Range("D6")) = "3" Then
Dim pageArray As Variant
'set array for given difference
pageArray = Array("schedule01", "report01", "p&l01", _
"schedule02", "report02", "p&l02", _
"schedule03", "report03", "p&l03", _
"schedule04", "report04", "p&l04")
Worksheets("data").Activate
Worksheets("data").PageSetup.CenterHorizontally = True
'page setup values
With ActiveSheet.PageSetup
.FitToPagesWide = 1
.FitToPagesTall = 1
.Orientation = xlLandscape
End With
'call array for print
Worksheets("data").Range("pageArray").PrintOut
Elseif
'Here is where I could put another similar function for a difference of 4
'......
Else
'Here is where I could put another similar function for a difference of x
End If
End Sub
I expected this would get me a PDF where each of these arrays is printed on a separate sheet and will print a selection of arrays based on the difference value.
To expand on my comment, it would look like this:
Dim lDiff As Long
Dim pageArray As Variant
Dim sFormat As String
Dim i As Long, j As Long
'if a certain difference value, use
lDiff = Worksheets("Inputs").Range("D7").Value - Worksheets("Inputs").Range("D6").Value
ReDim pageArray(1 To (lDiff + 1) * 3)
For i = 1 To UBound(pageArray, 1) Step 3
j = j + 1
If j < 100 Then sFormat = "00" Else sFormat = "000"
pageArray(i) = "schedule" & Format(j, sFormat)
pageArray(i + 1) = "report" & Format(j, sFormat)
pageArray(i + 2) = "p&l" & Format(j, sFormat)
MsgBox pageArray(i)
Next i

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.

Add Variable Series to Chart w/ VBA

I have a spreadsheet that looks like this:
A 1/1/2013 100
A 2/1/2013 200
A 3/1/2013 300
B 1/1/2013 150
B 2/1/2013 175
B 3/1/2013 200
The three columns are fixed, but the number of entries for each series (first column) varies.
Using VBA, I'd like to automatically add each unique value in the first column as a series on a scatterplot, using the second column for the X values and third column for the Y values. For example, for Series A above, I would need to dynamically ascertain ranges B1:B3 and C1:C3.
I generated the code for adding the series to a chart using recorded macros, so my real obstacle is finding the ranges for each series. I need to retain the name of the series for association (first column), and would like to avoid using filters.
I eventually thought of a somewhat roundabout way of solving it; here's the code for anyone interested, adapted to the layout of my original question:
Sub Example()
' Variable Definitions
Dim y As Integer
Dim Name(73) As String
Dim Mins(73) As Integer
Dim Maxs(73) As Integer
' Loop Through Name Column (Series)
For Each x In Range("A4:A" & Cells(Rows.Count, "A").End(xlUp).Row)
' Add Values to Arrays, If Not Preexisting
If IsInArray(x.Value(), Name) = False Then
Name(y) = x.Value()
Mins(y) = x.Row()
Maxs(y) = x.Row()
y = y + 1
Else
Maxs(y - 1) = x.Row()
End If
Next x
' Add to Chart
ActiveSheet.ChartObjects("Chart 1").Activate
ActiveChart.SeriesCollection.NewSeries
ActiveChart.FullSeriesCollection(1).Name = Name(0)
ActiveChart.FullSeriesCollection(1).XValues = "=Data!$B$" & Mins(0) & ":$B$" & Maxs(0)
ActiveChart.FullSeriesCollection(1).Values = "=Data!$C$" & Mins(0) & ":$C$" & Maxs(0)
End Sub
' Array Function from #JimmyPena
' http://stackoverflow.com/a/11112305/2141501
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function

Resources