• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /macosx-10.10.1/pyobjc-45/2.5/pyobjc/pyobjc-framework-Quartz/Examples/QuartzComposer/Chart/
1from Cocoa import *
2from Quartz import *
3
4# APPLICATION DATA STORAGE NOTES:
5# - This application uses a simple data storage as an array of entries,
6#   each containing two attributes: a label and a value
7# - The array is represented as a NSMutableArray, the entries as
8#   NSMutableDictionaries, the label as a NSString with the "label" key and
9#   the value as a NSNumber with the "value" key
10#
11#
12# QUARTZ COMPOSER COMPOSITION NOTES:
13# - The enclosed Quartz Composer composition renders a 3D bars chart and is
14#   loaded on the QCView in the application's window
15# - This composition has three input parameters:
16#   * "Data": the data to display by the chart which must be formatted as a
17#     NSArray of NSDictionaries, each NSDictionary containing "label" / NSString
18#     and "value" / NSNumber value-key pairs
19#   * "Scale": a NSNumber used to scale the chart bars
20#   * "Spacing": a NSNumber indicating the extra spacing between the chart bars
21# - The "Data" and "Scale" input parameters are set programmatically while the
22#   "Spacing" is set directly from the UI through Cocoa bindings
23# - Note that this composition is quite simple and has the following
24#   limitations:
25#   * it may have rendering artifacts when looking at the chart from some angles
26#   * it does not support negative values
27#   * labels are not truncated if too long
28# - Basically, the composition performs the following:
29#   * renders a background gradient
30#   * draws three planes on the X, Y and Z axes
31#   * uses an Iterator patch to loop on the chart data, which is available as
32#     a Structure, and for each member, retrieves the label and value, then
33#     draws them
34#   * the chart rendering is enclosed into a Camera macro patch used to center
35#     it in the view
36#   * the Camera macro patch is itself enclosed into a TrackBall macro patch
37#     so that the user can rotate the chart with the mouse
38#   * the TrackBall macro patch is itself enclosed into a Lighting macro patch
39#     so that the chart is lighted
40# - This composition makes uses of transparency for a nicer effect, but
41#   neither OpenGL nor Quartz Composer handle automatically proper rendering
42#   of mixed opaque and transparent 3D objects
43# - A simple, but not fail-proof, algorithm to render opaque and transparent
44#   3D objects is to:
45#   * render opaque objects first with depth testing set to "Read / Write"
46#   * render transparent objects with depth testing set to "Read-Only"
47#
48#
49#  NIB FILES NOTES:
50# - The QCView is configured to start rendering automatically and forward user
51#   events (mouse events are required to rotate the chart)
52# - An AppController instance is connected as the data source for the
53#   NSTableView
54# - The NSTableView is set up so that the identifiers of table columns match
55#   the keys used in the data storage
56# - The "Value" column of the NSTableView has a NSNumberFormatter which
57#   guarantees only positive or null numbers can be entered here
58# - The "Label" column of the NSTableView simply contains text
59#
60
61# Keys for the entries in the data storage
62kDataKey_Label = "label" # NSString
63kDataKey_Value = "value" # NSNumber
64
65# Keys for the composition input parameters
66kParameterKey_Data      = "Data"    # NSArray of NSDictionaries
67kParameterKey_Scale	= "Scale"   # NSNumber
68kParameterKey_Spacing	= "Spacing" # NSNumber
69
70class AppController (NSObject):
71    tableView = objc.IBOutlet()
72    view = objc.IBOutlet()
73
74    _data = objc.ivar()
75
76    def init(self):
77        # Allocate our data storage
78        self = super(AppController, self).init()
79        if self is None:
80            return None
81
82        self._data = []
83
84        return self
85
86    def awakeFromNib(self):
87        # Load the composition file into the QCView (because this
88        # QCView is bound to a QCPatchController in the nib file, this
89        # will actually update the QCPatchController along with all the
90        # bindings)
91        if not self.view.loadCompositionFromFile_(NSBundle.mainBundle().pathForResource_ofType_("Chart", "qtz")):
92            NSLog("Composition loading failed")
93            NSApp.terminate_(None)
94
95        # Populate data storage
96        self._data.extend([
97            {
98                kDataKey_Label:"Palo Alto",
99                kDataKey_Value: 2,
100            },
101            {
102                kDataKey_Label: "Cupertino",
103                kDataKey_Value: 1,
104            },
105            {
106                kDataKey_Label: "Menlo Park",
107                kDataKey_Value: 4,
108            },
109            {
110                kDataKey_Label: "Mountain View",
111                kDataKey_Value: 8,
112            },
113            {
114                kDataKey_Label: "San Francisco",
115                kDataKey_Value: 7,
116            },
117            {
118                kDataKey_Label: "Los Altos",
119                kDataKey_Value: 3,
120            },
121        ])
122
123        #Initialize the views
124        self.tableView.reloadData()
125        self.updateChart()
126
127    def updateChart(self):
128        #Update the data displayed by the chart - it will be converted to a
129        # Structure of Structures by Quartz Composer
130        self.view.setValue_forInputKey_(self._data, kParameterKey_Data)
131
132        #Compute the maximum value and set the chart scale accordingly
133        max = 0.0
134        for obj in self._data:
135            value = obj[kDataKey_Value]
136            if value > max:
137                max = value
138
139        if max == 0.0:
140            scale = 1.0
141        else:
142            scale = 1/max
143        self.view.setValue_forInputKey_(scale, kParameterKey_Scale)
144
145    @objc.IBAction
146    def addEntry_(self, sender):
147        #Add a new entry to the data storage
148        self._data.append({
149            kDataKey_Label: "Untitled",
150            kDAtaKey_Value: 0,
151        })
152
153        #Notify the NSTableView and update the chart
154        self.tableView.reloadData()
155        self.updateChart()
156
157        #Automatically select and edit the new entry
158        self.tableView.selectRow_byExtendingSelection(len(self._data)-1, False)
159        self.tableView.editColumn_row_withEvent_select_(
160                self.tableView.columnWithIdentifier_(kDataKey_Label),
161                len(self._data)-1, None, True)
162
163    @objc.IBAction
164    def removeEntry_(self, sender):
165        #Make sure we have a valid selected row
166        selectedRow = self.tableView.selectedRow()
167        if selectedRow < 0 or tableView.editedRow() == selectedRow:
168            return
169
170        #Remove the currently selected entry from the data storage
171        del self._data[selectedRow]
172
173        #Notify the NSTableView and update the chart
174        self.tableView.reloadData()
175        self.updateChart()
176
177
178    def numberOfRowsInTableView_(self, aTableView):
179        # Return the number of entries in the data storage
180        return len(self._data)
181
182    def tableView_objectValueForTableColumn_row_(self, aTableView, aTableColumn, rowIndex):
183        # Get the "label" or "value" attribute of the entry from the data
184        # storage at index "rowIndex"
185        return self._data[rowIndex][aTableColumn.identifier()]
186
187    def tableView_setObjectValue_forTableColumn_row_(
188            self, aTableView, anObject, aTableColumn, rowIndex):
189
190        # Set the "label" or "value" attribute of the entry from the data
191        # storage at index "rowIndex"
192        self._data[rowIndex][aTableColumn.identifier()] = anObject
193
194        # Update the chart
195        self.updateChart()
196