I am trying to read the category names of a waterfall chart in a PowerPoint VSTO project.
So far, I was unable to do so.
Here is what I tried:
chart.SeriesCollection(x).Axes(y).CategoryNames - not available for this chart type
chart.SeriesCollection(x).XValues - not available for this chart type
chart.SeriesCollection(x).Points(y).DataLabel.Text / .Caption - this returns the point value, not the category name, e.g. -130
chart.SeriesCollection(x).DataLabels(y).Text / .Caption - same as previous: It returns the point values
Then, I tried reading the source data directly via chart.ChartData.Workbook but this is also not available.
So, how can I read the category names?
It appears that, as of this writing, the XlChartType enumeration is missing a member for Waterfall. (Waterfall has a ChartType integer value of 119, which is simply missing in the enumeration.)
As the missing enumeration creates all sorts of issues, I decided to write code that would convert the chart to an enumerated type, place the Category Names into an array, and then use PowerPoint's Undo functionality to restore the chart.
PowerPoint.Chart myChart = Globals.ThisAddIn.Application.ActivePresentation.Slides[1].Shapes[2].Chart;
myChart.ChartType = Office.XlChartType.xlBarStacked;
PowerPoint.Axis CategoryAxis = Globals.ThisAddIn.Application.ActivePresentation.Slides[1].Shapes[2].Chart.Axes(PowerPoint.XlAxisType.xlCategory, PowerPoint.XlAxisGroup.xlPrimary);
Array CatNames = (Array)((object)CategoryAxis.CategoryNames);
Globals.ThisAddIn.Application.CommandBars.ExecuteMso("Undo");
//Do something here with the CatNames array
Related
I'm tearing my hair out trying to work with generatedpoints in draft view. I have a 3D model with points that are named in a particular way, per the picture below:
point names
Then on the CATDrawing, I have generated views that show those points. if I click those points in 2D, they are named in the following manner, "GeneratedPoint (insert 3D point name here)". You can see this naming example below:
2D generated naming
Now, what I'm trying to do in VBA is run a code that will select those generated points in a view, duplicate geometry on them, and name that duplicated geometry to match the 3D point names. My problem is, I can't seem to access the "GeneratedPoint" names that show up when I click the points in 2D. Below is a snippet of my code where I'm trying to access the name of the selected points:
For j = 1 To totalcnt
Set bSel = aDoc.Selection
bSel.Add ActiveView
bSel.Search "Name='*GeneratedPoint *',sel"
Set nm = bSel.Item(j)
desc = nm.Name
desc = nm.Value.Name
Usually when I have a selection, and I make an object from one of those selections, I can access the name through selection.Item(j).Name or selection.Item(j).Value.Name, but in this case neither one works.
at the desc=nm.Name line above, it gives me a name of "CATIASelectedElement16", not the actual name I see in the status bar when I click it in the drawing. And when I use desc=nm.Value.Name it gives me "Front View", which is the name of the view these points are in. I know it's selecting the points correctly, I can see them get selected, and I can see the count on the selection object matches my number of points. What am I missing? For reference, when I run the line Set nm=bSel.Item(i), that object looks like this in the Locals window.
Object in Locals window
As you can see in that picture, the object Type is DrawingView, whereas I would expect it to be a point. Does anyone have any ideas on how to access the name of a generated item in 2D? So far the only way I can interact with it at all is by using selection.Search, which will find them by name, but I have no way of then actually using the specific names of those points it found. Any insight would be appreciated!
I want to change a label width in Excel chart by VBA code:
set lbl = SERIES1. points(1).datalabel
msgbox lbl.width 'this is working
lbl.width = 40 ' compile error: wrong number of arguments or invalid property assignment
I can get the label width but cannot change it. What am I doing wrong?
AFAIK, the width of a chart data point label is not configurable. Even though the width can be retrieved with VBA, it's not possible to set or change it with a VBA command or property.
According to the documentation, the DataLabels.Width property is read only.
Returns the width, in points, of the object. Read-only.
Source
Excel chart labels remain stubbornly uncooperative and resist formatting attempts, be it with VBA or with the UI.
That's (unfortunately) just how Excel works.
Don't shoot the messenger.
If you want to make a difference, consider raising an idea at excel.uservoice.com
This works in Excel 2016, you'll just need to adjust the series collection and point numbers to fit your needs. ActiveChart.FullSeriesCollection(1).Points(1).DataLabel.Width = 363.314.
I have a x,y scatter chart with lines connecting the dots from one source. I have between 1 and 8 lines and I need to have the MarkerStyle assigned to each line. Since the lines are not fixed and depend on the current data, I can't say which lines are there.
In general I could just assign
ActiveChart.FullSeriesCollection(i).MarkerStyle = xlMarkerStyleAutomatic
But that also assigns unwanted/unreadable Markers. So could I create a Collection with the Markerstyles I want and than assign that?
I tested
Dim colMarker As Collection
Set colMarker = New Collection
colMarker.Add "xlMarkerStyleCircle"
colMarker.Add "xlMarkerStyleSquare"
colMarker.Add "xlMarkerStyleTriangle"
With ActiveChart.FullSeriesCollection(i)
.MarkerStyle = colMarker(1)
End With
But the error msg is wrong type
What type do I need?
Thanks Kaz
You can set the marker style for each series individually:
Dim s as Series
s = ActiveChart.SeriesCollection.NewSeries
s.MarkerStyle = xlMarkerStyleCircle
Edit:
To assign different marker styles use the approach you suggested, but when adding styles to the collection it should be done like this:
colMarker.Add xlMarkerStyleDiamond
etc, ie without quotation marks.
I am trying to add data labels to all my series in a bar chart but the amount of series varies from chart to chart because this is a function used for many charts. So, I used a For Loop to add them, but I need to determine the size of the SeriesCollection array. Would there be a function that would do so? When I try to use .Size it gives me an error.
Below is what I tried:
With Chart
For i = 1 To .SeriesCollection.Size
.SeriesCollection(i).Points(i).HasDataLabel = True
Next i
End With
Some of the stuff in the Chart object model isn't properly hooked up to Intellisense. You'll notice that you get no suggestions when you type .SeriesCollection.
.SeriesCollection.Count will give you what you're after, I think.
I am in the process of continuously updating and improving a financial model built in Excel with VBA macros enabled that is in active use by multiple people. Primarily, these templates are used as budgets for different projects, so there are many created all the time while older budgets are re-visited.
I am the "keeper" of the template while the other users simply use the document. Whenever I need to push out an update to everyone, it creates an issue because they already have created the budget in an older version of the template and to re-create the budget in the new template would take an inordinate amount of time.
I have gotten around this problem on smaller-scale templates by naming ranges and then applying those named ranges to an old version and then using the named ranges to copy into the same named range in the new version of the template. However, this was done with individual lines of code to copy each named range.
Is there a way to aggregate a group of named ranges into a class so that Excel can just loop through all of the items in the class and copy the data rather than me needing to code out each line to perform a copy?
Here is a sample of the code that I am currently running:
Workbooks(WB_Active_Name).Sheets("Office Staff Input").Range("Office_Employee_Names").Value = Workbooks(WB_Secondary_Name).Sheets("Office Staff Input").Range("Office_Employee_Names").Value
Workbooks(WB_Active_Name).Sheets("Office Staff Input").Range("Office_Employee_Positions").Value = Workbooks(WB_Secondary_Name).Sheets("Office Staff Input").Range("Office_Employee_Positions").Value
Workbooks(WB_Active_Name).Sheets("Office Staff Input").Range("Office_Employee_Numbers").Value = Workbooks(WB_Secondary_Name).Sheets("Office Staff Input").Range("Office_Employee_Numbers").Value
Workbooks(WB_Active_Name).Sheets("Office Staff Input").Range("Office_Employee_Bonus_Sharing").Value = Workbooks(WB_Secondary_Name).Sheets("Office Staff Input").Range("Office_Employee_Bonus_Sharing").Value
There are dozen of more lines of code similar to this for each named range. Inside the template, the ranges refer to lists of names of staff, ID numbers, hours worked, etc. and they also span multiple sheets within the workbook with each range of different size.
I am wondering if there is some sort of class that I could place in front of each named range when I define it so that they are treated as a class together and can be looped through. For example:
Office_Employee_Names
becomes
GroupClass.Office_Employee_Names
Then the code could loop through everything in GroupClass
IF an MVC pattern would exist in VBA, then a named range could represent a View object.
I personally have some experience with an MV* pattern where Views also implement events (which are normally delegated to Controller objects).
The benefit of using this approach, is that you will start programming in a much more modular fashion.
I provide an example below:
The structure of an MV* implementation of a simple named range "persons" could have the following class structure:
cls_view_persons
cls_model_person
Imagine that cls_view_persons represents a view object, then this would mean that you have to instantiate it from a base sub, which will simply be:
dim view_persons as cls_view_persons
set cls_view_persons = new cls_view_persons
1. persons view (example)
The cls_view_persons class will have a property that defines the range of the class.
For example:
private pRange as new Range
You can define the private pRange property in the class constructor.
One of the things that makes Excel buggy, is the fact that you don't know in advance the size of the range, and the fact that mistakes happen, such as a range that was not properly cleared the last time.
This is why it is important to also define the following properties:
_oRange as Excel.Range
_sNamed_range as string;
_lNr_rows as long;
_iNr_cols as integer;
_iOffset_x as long;
_lOffset_y as integer;
oCollection as Collection (you can also use a dictionary for this)
Note: oCollection is an object that will consist of the different cls_view_persons instances;
and methods:
Init: class constructor: defining a default range object, or you could pass a range on creation time if you wish to do so;
get_named_range: returning a range object;
set_named_range: setting the range object property and creating the named range in the sheet object;
collection_to_array: converts the collection object into an array;
clear_range: clearing the range;
A render method that uses the previous three methods and writes data from the created array to the range in a single statement:
set pRange = vPersons
Where vPersons is an array, containing the different persons (see later).
(A "read" method that reads from the range would be useful as well).
2 person model (example)
A model represents the data logic of your application and is on itself, not necessarily related to one specific view. In this case it is, but a model (or Collection of models) is in principle independent).
A person model could be a class that defines the following properties:
firstname
lastname
address
country
Either these models are fetched from a database, from an Excel sheet (the latter being the worst scenario, which unfortunately happens the most) or any other source.
Whatever you do, you need to see that you end up with a Collection object that you can feed to the View object.
Once this is done, the View object should manage its own. All interpreting and rendering is delegated to this object from that moment on.
This means:
Verify the dimensions;
Clear the previous results;
Render the range on the screen (ie. setting the new dimensions, creating the named range in the sheet, converting the collection into an array, and writing the array to the screen).
You will see that this approach has many benefits in terms of:
Maintenance;
Less bugs;
Modular (and transferrable) / encapsulated properties / methods;
Dynamically adaptable;
You can read from any data source, if you only write an intermediary "translator" module.
I would create a new worksheet in your template that list all these named ranges; read the list from VBA and loop through them.
I often retrieve data from Names by:
Workbook.Names contains all Name Objects, and each Name Object its properties
Dim WBook As Workbook
Dim WName As Name
Set WBook = ActiveWorkbook
For Each WName In WBook.Names
Debug.Print WName.Name, WName.RefersToLocal
Next WName