Jacob said:
I found SM2DGraphView, but I guess that I am too much of a newbie with
interface builder and pyobjc to figure out how to get SM2DGraphView to
work. Are there any good tutorials (or better yet, examples) of how to
get SM2DGraphView to work?
I don't know pyobjc well at all.
There isn't much to it. Unfortunately, there aren't many good tutorials,
but at least a few examples in the pyobjc repository.
below is my code for implementing the SM2DGraphDataSource category. It
is of course interspersed with my application-logic, but should give you
a start.
Additionally, you need to load the SM2DGraphView-framework. The code to
do so is also below.
Diez
#
# BCGraphViewController.py
# BruceControl
#
# Created by Diez Roggisch on 22.02.08.
# Copyright (c) 2008 __MyCompanyName__. All rights reserved.
#
from Foundation import *
from AppKit import *
import sys
from PyObjCTools import NibClassBuilder
import objc
MODES = "displaySpeed", "displayPosition"
kSM2DGraph_Axis_Y = 0
kSM2DGraph_Axis_X = 1
kSM2DGraph_Axis_Y_Right = 2
kSM2DGraph_Axis_Y_Left = kSM2DGraph_Axis_Y
class BCGraphViewController(NibClassBuilder.AutoBaseClass):
_displayPosition = False
_displaySpeed = False
def displayPosition(self):
return self._displayPosition
def setDisplayPosition_(self, v):
self._displayPosition= v
self.reloadData()
def displaySpeed(self):
return self._displaySpeed
def setDisplaySpeed_(self, v):
self._displaySpeed= v
self.reloadData()
def awakeFromNib(self):
self._displayed_mis = []
self._ts_min = 0.0
self._ts_max = 0.0
self.graphView.setDataSource_(self)
def setMotorInfos_(self, motor_infos):
self._motor_infos = motor_infos
# apparently, it's enough to just
# subscribe once
motor_infos[0].subscribeDisplayChanged(self)
def setConnector_(self, connector):
self._connector = connector
connector.subscribeStateListener(self)
def stateArrived_(self, n):
state = n.object()
ts = float(state.timestamp) / 1000000000.0 # nano to seconds
if self._ts_min == 0:
self._ts_min = ts
self._ts_max = ts
def reloadData(self):
if self.numberOfLinesInTwoDGraphView_(None):
self.graphView.reloadData()
self.graphView.reloadAttributes()
def displayChanged_(self, n):
motor_info = n.object()
motor_id = motor_info.motor_id
if motor_info.display() and motor_info not in self._displayed_mis:
self._displayed_mis.append(motor_info)
else:
try:
self._displayed_mis.remove(motor_info)
except ValueError:
pass
self.reloadData()
@property
def active_modes(self):
return [mode for mode in MODES if getattr(self, mode)()]
@property
def activated_views(self):
return len(self.active_modes)
@objc.signature('I@

')
def numberOfLinesInTwoDGraphView_(self, view):
res = len(self._displayed_mis) * self.activated_views
return res
@objc.signature('@@

I')
def twoDGraphView_dataForLineIndex_(self, view, lineIndex):
av = self.activated_views
# first, compute the mi to be used
offset = lineIndex // av
mi = self._motor_infos[offset]
mode = self.active_modes[lineIndex % av]
return mi[mode]
@objc.signature('d@

Ii')
def twoDGraphView_maximumValueForLineIndex_forAxis_(self, view,
lineIndex, axis):
if not self.numberOfLinesInTwoDGraphView_(view):
return .0
av = self.activated_views
mode = self.active_modes[lineIndex % av]
if axis == kSM2DGraph_Axis_Y:
maxs = [mi.max[mode] for mi in self._displayed_mis]
return max(maxs)
elif axis == kSM2DGraph_Axis_X:
return float(self._ts_max)
return .0
@objc.signature('d@

Ii')
def twoDGraphView_minimumValueForLineIndex_forAxis_(self, view,
lineIndex, axis):
if not self.numberOfLinesInTwoDGraphView_(view):
return .0
av = self.activated_views
mode = self.active_modes[lineIndex % av]
if axis == kSM2DGraph_Axis_Y:
mins = [mi.min[mode] for mi in self._displayed_mis]
return min(mins)
elif axis == kSM2DGraph_Axis_X:
return float(self._ts_min)
return .0
@objc.signature('@@

I')
def twoDGraphView_attributesForLineIndex_(self, view, lineIndex):
try:
if not self.numberOfLinesInTwoDGraphView_(None):
return None
av = self.activated_views
# first, compute the mi to be used
offset = lineIndex // av
mi = self._motor_infos[offset]
res = {NSForegroundColorAttributeName : mi.color}
except:
print sys.exc_info()[1]
return None
return res
def crop_(self, sender):
self._ts_min = self._ts_max
for mi in self._motor_infos:
mi._set_min_max_defaults()
import objc, AppKit, Foundation, os
import logging, sys
from ctypes.util import find_library
logger = logging.getLogger('bundles')
frameworks = set(("SM2DGraphView",))
loaded = set()
loading_exceptions = []
for framework in frameworks:
logger.debug('Trying to load framework <%s>', framework)
bundle_path = find_library(framework)
bundle_path = bundle_path[:bundle_path.index('framework')] +
"framework"
logger.debug('from path <%s>', bundle_path)
try:
objc.loadBundle(framework, globals(), bundle_path=bundle_path)
loaded.add(framework)
continue
except:
loading_exceptions.append(sys.exc_info()[1])
pass
if loaded != frameworks and loading_exceptions:
logger.error("Got exceptions loading frameworks")
for e in loading_exceptions:
logger.error(e)
raise loading_exceptions[0]
del objc, AppKit, Foundation, os, bundle_path