How can I programmatically modify an embedded bar/column chart with VBA? - vba

I'm creating a ProcessBook display that populates an embedded Microsoft Office 11.0 Spreadsheet object (Office 2003) with a set of entries. I'm then calculating aggregate data about them; this aggregate data should not be visible in spreadsheet form onscreen, but does need to be used to generate a bar chart. The data is currently being used to populate a separate Microsoft Office 11.0 Spreadsheet object. It's organized such that the title of each bar chart is in column A and the corresponding value is in column B.
Since this is ProcessBook, I've had some difficulty even gaining access to embedded objects, but I've managed to embed and gain access to a ChartSpace object, as well as a child ChChart object. Unfortunately, I can't figure out how to either manually set the values of the bars or how to use the .SetData or .SetSpreadsheetData methods to point it to an object that I've populated.
Accessing the ChartSpace object is pretty straightforward: ThisDisplay.ChartSpace1
I can then add a Chart and access it fairly easily:
Dim objChart as ChChart
Set objChart = ThisDisplay.ChartSpace1.Charts.Add(0)
I can access my spreadsheet values pretty easily as well:
strBarName = ThisDisplay.sstChartData.Range("A2").Value
intBarVal = ThisDisplay.sstChartData.Range("B2").Value
How do I actually set the data source or manually set the values of the bars in the ChChart object? Alternatively, how do I use a different object to accomplish the same goal?

Apparently persistence is the key here. I managed to determine how to both add values manually and how to refer to the existing spreadsheet object. Both examples take heavily from the online ChChart documentation; I guess I was just tired when I attempted it in the first place and must have mistyped something somewhere.
Public Sub AddValuesToChartManually()
Dim objChart As ChChart
With ThisDisplay.ChartSpace1
Do While .Charts.Count > 0
ThisDisplay.ChartSpace1.Charts.Delete (0)
Loop
Set objChart = .Charts.Add
objChart.HasTitle = True
objChart.Title.Caption = "Chart Title"
objChart.Axes.Item(0).HasTitle = True
objChart.Axes.Item(1).HasTitle = True
objChart.Axes.Item(0).Title.Caption = "Axis 0"
objChart.Axes.Item(1).Title.Caption = "Axis 1"
objChart.HasLegend = False
Dim astrHeaders(0 To 4) As String
Dim aintValues1(0 To 4) As String
Dim aintValues2(0 To 4) As String
Dim astrSeries1(0) As String
Dim astrSeries2(0) As String
astrHeaders(0) = "AL1"
astrHeaders(1) = "AL2"
astrHeaders(2) = "AL3"
astrHeaders(3) = "AL4"
astrHeaders(4) = "AL5"
astrSeries(0) = "Series Name"
aintValues(0) = 1
aintValues(1) = 3
aintValues(2) = 17
aintValues(3) = 1
aintValues(4) = 7
objChart.Type = .Constants.chChartTypeColumnClustered
Call objChart.SetData(chDimSeriesName, .Constants.chDataLiteral, astrSeries)
Call objChart.SetData(chDimCategories, .Constants.chDataLiteral, astrHeaders)
Call objChart.SeriesCollection(0).SetData(.Constants.chDimValues, .Constants.chDataLiteral, aintValues)
End With
End Sub
Public Sub AddValuesFromSpreadsheet()
Dim objChart1 As ChChart
With ThisDisplay.ChartSpace1
Do While .Charts.Count > 0
ThisDisplay.ChartSpace1.Charts.Delete (0)
Loop
Set .DataSource = ThisDisplay.sstChartData
Set objChart1 = .Charts.Add
' Set the chart type.
objChart1.Type = .Constants.chChartTypeColumnClustered
' Display titles
objChart1.HasTitle = True
objChart1.Title.Caption = "Chart Title"
' Bind the series name to cell B1 in the first sheet of the spreadsheet
objChart1.SetData chDimSeriesNames, .Constants.chDataBound, "B1"
' Bind the category axis to cell A2:A28 in the first sheet of the spreadsheet.
objChart1.SetData chDimCategories, .Constants.chDataBound, "A2:A6"
' Bind the values of the data series to cells B2:B28 in the first sheet of the spreadsheet.
objChart1.SeriesCollection(0).SetData chDimValues, .Constants.chDataBound, "B2:B6"
End With
End Sub

Related

How do I deactivate and reactivate several geometrical sets and objects automatically?

I wrote a macro that hides everything in several geometrical sets and the objects and geometrical sets in these first sets except one specific branch. I use this for saving a defined object of a huge and complicated specification tree as a STP file. (See attached below.)
(Small complication in this “Hide_and_Save” macro: adding bodies to my hide-selection works well but for my show-selection it didn’t work the same way. Why would this happen?)
I also wrote a macro that does iterative adjustments. For the iterations I use a Do While Loop and some parameters and measurements. To update these values, I have to update the part/object in every cycle. But there are some construction elements that issue errors until the loop is successfully completed. Therefore I deactivate all the geometrical sets that I don’t need for the iterations (inclusively all children) and later I reactivate them manually.
My goal is to improve automation, so I tried to use my “Hide_and_Save” macro for deactivation and reactivation. This didn’t work. When I record the process, each object is listed in a separate line and deactivated. Since there are more than 350 elements, I would like to avoid this.
How do I deactivate all subelements in a geometrical set (preferably with children) without addressing each element individually?
Attribute VB_Name = "Hide_and_Save"
'_______________________________________________________________________________________
'Title: Hide_and_Save
'Language: catvba
'_______________________________________________________________________________________
Sub CATMain()
'---------------------------------------------------------------------------------------
'Select active Part/Document
Dim myDocument As Document
Set myDocument = CATIA.ActiveDocument
Dim myPart As part
Set myPart = CATIA.ActiveDocument.part
'--------------------------------------------------------------
' Enter file path
Dim filepath As String
filepath = InputBox("Please select memory location", "Input filepath", "...")
If filepath = "" Then 'cancle, abort or empty input
MsgBox "No valid input / cancle !"
Exit Sub
End If
'--------------------------------------------------------------
' Hide/show Objects of Part/Products and save as STEP
' Update Model
CATIA.ActiveDocument.part.Update
' Deklaration of Selections and Properties
Dim selectionShow, selectionHide As Selection
Set selectionShow = myDocument.Selection
Set selectionHide = myDocument.Selection
Dim visPropertySetShow, visPropertySetHide As VisPropertySet
Set visPropertySetShow = selectionShow.VisProperties
Set visPropertySetHide = selectionHide.VisProperties
' Definition of the collection of geometric sets - HybridBodies
Dim hybridBodiesInPart, hybridBodiesInProcess As HybridBodies
Dim hybridBodiesInRS, hybridBodiesInHuelle As HybridBodies
' Definition of individual geometric sets - HybridBody
Dim hybridBodyInPart, hybridBodyProcess, hybridBodyInProcess As HybridBody
Dim hybridBodyRS, hybridBodyInRS As HybridBody
Dim hybridBodyHuelle, hybridBodyInHuelle As HybridBody
' Definition of the collection of 3D-objects - HybridShapes
Dim hybridShapesInHuelle As HybridShapes
' Definition of individual 3D-objects - HybridShape
Dim hybridShapeInHuelle, hybridShapeForm As HybridShape
' Hide objects
Set hybridBodiesInPart = myPart.HybridBodies
For Each hybridBodyInPart In hybridBodiesInPart
selectionHide.Add hybridBodyInPart
Next
Set hybridBodyProcess = hybridBodiesInPart.Item("Process")
Set hybridBodiesInProcess = hybridBodyProcess.HybridBodies
For Each hybridBodyInProcess In hybridBodiesInProcess
selectionHide.Add hybridBodyInProcess
Next
Set hybridBodyHuelle = hybridBodiesInProcess.Item("Huelle")
Set hybridBodiesInHuelle = hybridBodyHuelle.HybridBodies
For Each hybridBodyInHuelle In hybridBodiesInHuelle
selectionHide.Add hybridBodyInHuelle
Next
Set hybridShapesInHuelle = hybridBodyHuelle.HybridShapes
For Each hybridShapeInHuelle In hybridShapesInHuelle
selectionHide.Add hybridShapeInHuelle
Next
Set hybridShapeForm = hybridShapesInHuelle.Item("Form")
visPropertySetHide.SetShow 1 'hide
selectionHide.Clear
' Show objects
selectionShow.Add hybridBodyProcess
selectionShow.Add hybridBodyHuelle
selectionShow.Add hybridShapeForm
visPropertySetShow.SetShow 0 'show
selectionShow.Clear
' Data export as STP
stepAnswer = MsgBox("Should the displayed elements be saved as STEP?", 3 + 0, "Export: Form")
If stepAnswer = 6 Then
myDocument.ExportData filepath & "Form" & ".stp", "stp"
ElseIf stepAnswer = 3 Or stepAnswer = 2 Then 'cancle or abort
MsgBox "cancle !"
Exit Sub
End If
'---------------------------------------------------------------------------------------
MsgBox "Finished !" & vbCrLf & s
End Sub
(Usually I work with Generative Shape Design and use VBA for Macros.)
Each feature has an "Activity" parameter aggregated to it.
Dim oShape as HybridShape
For Each oShape In oGS.HybridShapes
Dim oActivity as Parameter
Set oActivity = oPart.Parameters.SubList(oShape,False).Item("Activity")
Call oActivity.ValuateFromString("False")
Next
Let me add that screwing with Activity of features is not a best practice. I NEVER do this myself. If you have access KBE (Specifically Knowledge Advisor Workbench) you can probably do what you want with Rules/Actions/Reactions, less coding and have a more robust model in the end.

Adding combo box across multiple cells

I need to add combo box(ActiveX Control) or Data Validation as drop down list.
I have a range of 15 values like, high, low, medium,etc...
Have created named range called "priorityvalue".
I can create a dropdown list using combo box by adding named range under ListFillRange in the properties or data validation list by giving named range.
But my concern, I need to dropdown list for 58cells with same values mentioned above. Its tedious job to create combo box for all cells. Please suggest me better option here.
Data validation list serves the purpose. However, it makes user to scroll through dropdown list on each cell unlike combo box it has no input box..
Please suggest
Paste the below code in 'ThisWokbook'
Private Sub Workbook_Open()
Dim oItem As Object
For Each oItem In Worksheets(1).OLEObjects
If TypeName(oItem.Object) = "ComboBox" Then
If Len(oItem.Object.Value) > 0 Then
oItem.Object.Value = ""
End If
End If
Next
Set oItem = Nothing
End Sub
NOTE: There are caveats to this. Above code will reset all comboboxes in your worksheet (also, I've set the worksheet to the first worksheet in the workbook, you might want to make that dynamic). If you don't want it to reset all comboboxes and only do the ones you added via the function, you can use the name format to filter the ones you want to clear
Hope this helps
Try this:
Sub AddComboBoxToColumns(ByVal oRange As Excel.Range)
Dim oOLE As OLEObject
Dim oCell As Object
' Loop through all the cells in the range
For Each oCell In oRange.Cells
' Add ComboBox in each cell
With oCell
Set oOLE = .Parent.OLEObjects.Add("Forms.combobox.1")
oOLE.Top = .Top
oOLE.Left = .Left
oOLE.Width = .Width
oOLE.Height = .Height
oOLE.Name = "ComboBox" & .Address(False, False)
oOLE.Object.List = Array("Test1", "Test2")
End With
Next
Set oOLE = Nothing
End Sub
NOTE: Call the above function with the range of cells you want to add ComboBox to. You will have to change the Array to use the values you want (you can type them in there or give the range where your existing values are)

Create an average of multiple excel chart without the data source

Thought provoking problem (for me at least). Normally when creating a chart you have your data and then use it to create the chart. If you then copy the chart to another workbook, the values on the chart stay the same but there is "no available" data source in the new workbook. I want to create a new chart which is the average of multiple copied charts. Is this possible in excel/vba?
I can't even try recording a macro and going from there as I don't know if its possible to "average" multiple charts.
EDIT : Been doing some more thinking and am thinking if it is possible to instead of extract data into a new sheet for each chart, is it possible to average data upon extraction. If on the chart you Right click -> select data, you can see the reference to the data in the original worksheet. Is it possible to average this and print just the outcome without having to store all the data? Would still be easier to directly average charts if possible!
EDIT 2: I have reworked my data template so that matching time series data ranges is no longer an issue. Also as per the comment on averages-of-averages, the data is all of equal weight and quantity so this should not be a problem. It literally just comes down to: is there a way to take the face values of multiple charts (or graphs), and average them to form a new chart (or graph) without massive data manipulation in the original (or new) workbook?
Bounty Summary (with round numbers): Looking for a quick'ish way in VBA to create a chart which is the average of multiple charts. I have 10 types of chart on 50 separate worksheets. I'm looking to create a summary sheet with 10 charts that average the data from the same respective chart on the other 50 sheets. The key difficulty is that this is a 'presentation Workbook that all charts are copied into, all the data for each chart is in a different workbook.
EDIT 4: Data is stored in multiple time series tables that are all side by side in a main data sheet. It appears to be at the moment (as per Scott's comment) that there is no way to directly manipulate and the most likely solution will be that of data extraction/manipulation. Search still continues though :)
I want to create a new chart which is the average of multiple copied charts. Is this possible in excel/vba?
It is possible but there is no magic formula for this task.
I would first iterate each workbook, each worksheet, each shape and aggregate the values in an array, with one array for each type of chart.
To avoid storing all the data, the averages will have to be computed upon each extraction like this:
Average = ((PreviousAverage * N) + Value) / (N + 1)
Next, to expose the data in your dashboard, I would duplicate the missing charts from the aggregated workbooks and reuse the one already present.
This way, the customisation of the dashboard will remain untouched if all the charts are already there.
Finally, I would directly insert the aggregated values in the charts without storing them in a sheet.
I've assemble a working example that aggregates all the charts from the current workbook and displays the results in the sheet "Dashboard":
Sub AgregateCharts()
Dim ws As Worksheet, wsDashboard As Worksheet, sh As Shape, ch As chart
Dim xValues(), yValues(), yAverages(), weight&, key
Dim items As Scripting.dictionary, item As Scripting.dictionary
Set items = CreateObject("Scripting.Dictionary")
' define the dashboard sheet
Set wsDashboard = ThisWorkbook.sheets("Dashboard")
' disable events
Application.ScreenUpdating = False
Application.EnableEvents = False
' iterate worksheets '
For Each ws In ThisWorkbook.Worksheets
' if not dashboard '
If Not ws Is wsDashboard Then
' iterate shapes '
For Each sh In ws.Shapes
If sh.type = msoChart Then ' if type is chart '
Debug.Print "Agregate " & ws.name & "!" & sh.name
' check if that type of chart was previously handled
If Not items.Exists(sh.chart.chartType) Then
' extract the values from the first serie
xValues = sh.chart.SeriesCollection(1).xValues
yValues = sh.chart.SeriesCollection(1).values
' duplicate the chart if it doesn't exists in the dashboard
Set ch = FindChart(wsDashboard, sh.chart.chartType)
If ch Is Nothing Then
Set ch = DuplicateChart(sh.chart, wsDashboard)
End If
' store the data in a new item '
Set item = New Scripting.dictionary
item.Add "Chart", ch
item.Add "Weight", 1 ' number of charts used to compute the averages
item.Add "XValues", xValues
item.Add "YAverages", yValues
items.Add ch.chartType, item ' add the item to the collection '
Else
' retreive the item for the type of chart '
Set item = items(sh.chart.chartType)
weight = item("Weight")
yAverages = item("YAverages")
' update the averages : ((previous * count) + value) / (count + 1) '
yValues = sh.chart.SeriesCollection(1).values
UpdateAverages yAverages, weight, yValues
' save the results '
item("YAverages") = yAverages
item("Weight") = weight + 1
End If
End If
Next
End If
Next
' Fill the data for each chart in the dashboard
For Each key In items
Set item = items(key)
Set ch = item("Chart")
' Add the computed averages to the chart
ch.SeriesCollection(1).xValues = "={" & Join(item("XValues"), ";") & "}"
ch.SeriesCollection(1).values = "={" & Join(item("YAverages"), ";") & "}"
Next
' restore events
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
Private Sub UpdateAverages(averages(), weight&, values())
Dim i&
For i = LBound(averages) To UBound(averages)
averages(i) = (averages(i) * weight + values(i)) / (weight + 1)
Next
End Sub
Private Function DuplicateChart(ByVal source As chart, target As Worksheet) As chart
' clone the chart to the target
source.Parent.Copy
target.Paste
Application.CutCopyMode = 0
' clear the data '
With target.Shapes(target.Shapes.count).chart.SeriesCollection(1)
Set DuplicateChart = .Parent.Parent
.name = CStr(.name)
.xValues = "={0}"
.values = "={0}"
End With
End Function
Private Function FindChart(source As Worksheet, chartType As XlChartType) As chart
' iterate each shape in the worksheet to fin the corresponding type
Dim sh As Shape
For Each sh In source.Shapes
If sh.type = msoChart Then
If sh.chart.chartType = chartType Then
Set FindChart = sh.chart
Exit Function
End If
End If
Next
End Function
Some data manipulation will probably be necessary. However, you can do it all in memory (or in a hidden worksheet if you prefer).
To extract data from a chart, example code:
Sub chartTest()
Dim ch As ChartObject
Set ch = Worksheets(1).ChartObjects(1)
Dim nr As Variant, var As Variant, var 2 As Variant
nr = UBound(ch.Chart.SeriesCollection(1).Values)
' Paste the values back onto the sheet
Range(Cells(1, 1), Cells(nr, 1)) = Application.Transpose(ch.Chart.SeriesCollection(1).XValues)
Range(Cells(1, 2), Cells(nr, 2)) = Application.Transpose(ch.Chart.SeriesCollection(1).Values)
' Pull the values into a variable (will be in array format)
var = ch.Chart.SeriesCollection(1).XValues
var2 = ch.Chart.SeriesCollection(1).Values
' Retrieval example
For i = 1 To UBound(var)
Range("A" & i).Value = var(i)
Range("B" & i).Value = var2(i)
Next i
End Sub
Whether you use Chart or ChartObjects as a first stop seems to depend on how the chart is created. The code in this example worked for a chart created by right-clicking some data in a sheet and inserting the chart.
See the Chart.SeriesCollection and the Series Properties pages on MSDN for more information.
So basically, extract all the data from the charts using code similar to the above, compare them, and create a new chart based on this data.

Insert image from VBA to EXCEL in a special case (=INCORPORER("Forms.Image.1";"")

I insert an image from VBA to Excel in Cell F31.
With Worksheets("CheckListIndustrialisation").Pictures.Insert(image)
'.Top = [F31].Top
'.Left = [F31].Left
'.Width = [F31].Width
'.Height = [F31].Height
But what I would like to do is to insert my image in the same sheet of excel but in one special case for image and not F31. The code inside the special case of image is:
=INCORPORER("Forms.Image.1";"")
I don't find how to change my code.
What you're trying to do is add an OleObject in the worksheet.
Try below:
'First add the OleOject
Dim img As OLEObject, sh As Worksheet
Set sh = Worksheets("CheckListIndustrialisation")
Set img = sh.OLEObjects.Add(ClassType:="Forms.Image.1", Link:=False, _
DisplayAsIcon:=False, Left:=147, Top:=42, Width:=85.5, Height:=37.5)
'Second add image to your OleObject
Dim iobj As MSForms.Image
Set iobj = img.Object
iobj.Picture = LoadPicture("C:\Users\User.Name\Pictures\SamplePicture.gif")
I declared and set the object types so you'll know what type of object you're working on.
In VBA, certain type of objects belong to certain collection of objects.
You'll need to check the specific type of object you want so work on so you can view the available properties and method for it and thus execute the correct code. HTH.

How can I dynamically construct a textbox object reference?

I asked a similar question Here and now I need to do the same thing again but this time using VBA in Excel 2010.
Essentially I have numerous text boxes with generic names (i.e. textbox1,textbox2 etc). How can I programically construct the object reference so that I can create a loop?
EDIT:
It is a regular textbox on a worksheet. When I start a sub for this worksheet I can reference the textboxes with the following line:
TextBox1.LinkedCell = "B2"
This is what your after:
Dim oleObj As OLEObject
'Dim oleTxtBox As TextBox
For Each oleObj In Sheet1.OLEObjects
If oleObj.OLEType = xlOLEControl Then
If Mid(oleObj.progID, 1, 14) = "Forms.TextBox." Then
Set oleTxtBox = oleObj.Object
oleTxtBox.PasswordChar = "*"
End If
End If
Next
Just using PasswordChar as an example field from the TextBox object, but it wouldn't actually left me Dim as a TextBox