Get Chart (not Easy Chart) mode in script

Hi,

How can I get the chart (not easy chart) mode using script? I want to know if the chart is in X trace, Zoom, Pan or Mark mode.

Also I don’t see ‘propertychange’ invoked when the user changes mode. Is there a way to capture it!

Thank,
Raj

No.

It is readable in my NoteChart module, which offers additional features on top of both the Classic Chart and the EasyChart components.

Thank you @pturmel. I will go over the documentation and get back.

I'm on Ignition 8.1 and I have the exact same needs, any news on capturering the mode change?
I have been experimenting with mouseReleased event and the poupTrigger, but cannot find a way to hook into the popupmenu to get the selected mode.

The component stores this in a private field. You can use reflection to get it. In NoteChart, I simply overrode all the methods that affected it and used my own field.

If Reflection bothers you, note the solution above.

@pturmel's suggestion works, but it's clunky. I tested it from the mouseClicked, mousePressed, and mouseReleased events, and in all cases the event doesn't fire when the mode changes. This is probably because the radio buttons for changing modes are layered in a separate popup above the chart. It takes a second mouse click on the chart itself to trigger the event which is almost certainly undesirable. The above solution makes more sense.

1 Like

I took a look at JFree chart docs and found that the popup get's called from the JFreeChartPanel so far so good. I also found your helper function findJFChart(src) which returns the parent JFreeChartPanel and the targeted JFreeChart but I'm not sure where to go from here.

@klpe Are you still trying to make this work from the classic chart, or have you switched to the module?

It isn't a field in JFreeChart--it is in IA's ChartPanel subclass (Classic Chart) and wrapper (EasyChart).

Thanks, I missed the fact that the ChartPanel was a wrapper to the EasyChart. I'll see where that takes me.

I put the code below in mouseReleased and now I can detect if zoom is selected. The timing is off, I check before the popup is opened, and I need to loop over the rest of the radiobuttons, but I think I'm on the right track.

from org.jfree.chart import ChartPanel, JFreeChart
from org.jfree.chart.plot import Plot

def findJFChart(src):
	"""IA's EasyChart and XYPlot components have their JFreeChart objects at different positions 
	in the Container/Component hierarchy. This helper function scans the hierarchy, given a container or 
	component as a starting point, and returns a tuple containing the parent JFreeChartPanel and 
	the targeted JFreeChart. Note: The hierarchy is searched depth-first! """  
	#from app.scriptmodule import findJFChart 
	try: 
		return (src, src.chart) 
	except: pass 
	try: 
		for x in range(src.componentCount): 
			try: return findJFChart(src.getComponent(x)) 
			except: pass 
	except: pass 
	raise ValueError, "Unable to Find a JFreeChart Instance"

if event.popupTrigger == 1:
	print "Popup triggered!"
	chart = event.source #parent.getComponent("Easy Chart")
	ch = findJFChart(chart)
	print(str(ch[0].getPopupMenu().getSubElements()[0].getComponent().getItem(0).isSelected()))
	

Add the following to the configureChart extension function:

from java.awt.event import ActionListener
class CustomModeItemActionListener(ActionListener):
	def __init__(self,comp):
		self.__comp = comp
	def actionPerformed(self,event):
		if event.source.isSelected():
			self.__comp.mode = event.source.text
	
pMenu = self.getPopupMenu()
	
for menuElement in pMenu.getSubElements():
	if menuElement.getLabel() == 'Mode':
		modeMenu = menuElement.getSubElements()[0]
		
		for item in modeMenu.getSubElements():
			for lis in item.getActionListeners():
				if 'CustomModeItemActionListener' in str(lis.__class__):
					item.removeActionListener(lis)
			item.addActionListener(CustomModeItemActionListener())

Here I have added a custom property to the root container (since that is the parent container of the chart). This script will add an Action listener to each of the items in the Mode Menu, when a new mode is selected it will change the value of the mode property to the selected mode.

It should go without saying to be careful with this, you'll notice that I am removing any actionListeners that we may have added to the items previously. This prevents a memory leak from not managing the life cycle of the listener. It also prevents having multiple listeners resulting in multiple action events.

Also, note that typically you would use isinstance() to check if an object is a certain type, but for whatever reason, I couldn't get isinstance() to work. I'm sure I'm missing something obvious there, but haven't figured out what.

Since the title of this thread says "(not Easy Chart)" I played around with this some more under the impression that we were working with the regular chart, but I see now that we've switched topics to the Easy Chart.

In any case, working with the Basic Chart, I originally went with Phil's suggestion and simply used reflection to get at the radio button positions. Since my original post, I discovered that it works quite well from the mouseEntered event handler, since it fires the moment the popup menu closes after a selection has been made.

Here is the code I used:

checkPan = event.source.getClass().getSuperclass().getDeclaredField("panItem")
checkPan.setAccessible(True)
isPanned = checkPan.get(event.source).isSelected()
checkXtrace = event.source.getClass().getSuperclass().getDeclaredField("xtraceItem")
checkXtrace.setAccessible(True)
isXtraced = checkXtrace.get(event.source).isSelected()
isZoomed = event.source.isDomainZoomable()
if isPanned:
	print "Pan Mode"
elif isZoomed:
	print "Zoom Mode"
elif isXtraced:
	print "XTrace Mode"
else:
	print "Mark Mode"

However, after reading @klpe's post, I realized that my code was MUCH more complicated than it needed to be. I went back and applied his approach to the Classic Chart problem, and I was able to return the same result with only 3 lines of code:

isPanned = event.source.getPopupMenu().getSubElements()[0].getComponent().getItem(1).isSelected()
isXtraced = event.source.getPopupMenu().getSubElements()[0].getComponent().getItem(3).isSelected()
isZoomed = event.source.isDomainZoomable()
if isPanned:
	print "Pan Mode"
elif isZoomed:
	print "Zoom Mode"
elif isXtraced:
	print "XTrace Mode"
else:
	print "Mark Mode"

For the Easy Chart, I tested @pturmel's approach combined with @klpe's method for getting the radio buttons, and it works perfectly. However, if work is to be done with the code, there is no need to do the work every time the mouse enters the chart or every time the mouse enters some component on the chart, so rather than develop listeners, I simply added a custom component to the chart's parent container called oldMode. I then use the mode to evaluate whether or not work needs to be done at the mouseEntered event. Here is the code:

oldMode = event.source.parent.oldMode
def findJFChart(src):
	try: 
		return (src, src.chart) 
	except: pass 
	try: 
		for x in range(src.componentCount): 
			try:return findJFChart(src.getComponent(x)) 
			except: pass 
	except: pass 
	raise ValueError, "Unable to Find a JFreeChart Instance"
chart = event.source
ch = findJFChart(chart)
isPanned = ch[0].getPopupMenu().getSubElements()[0].getComponent().getItem(1).isSelected()
isZoomed = ch[0].getPopupMenu().getSubElements()[0].getComponent().getItem(0).isSelected()
isXtraced = ch[0].getPopupMenu().getSubElements()[0].getComponent().getItem(3).isSelected()
if isPanned:
	newMode = "Pan Mode"
elif isZoomed:
	newMode = "Zoom Mode"
elif isXtraced:
	newMode = "XTrace Mode"
else:
	newMode = "Mark Mode"
if newMode != oldMode:
	print newMode 
	#do work HERE
	event.source.parent.oldMode = newMode
1 Like

I ended up using Justins solution on my Easy Chart and it works perfektly :smiley:
I think I misread the title of this post (not Easy Chart) as in not easy to work with.

I modified the code a little by removing the check with newMode != oldMode.
I don't think it is that many updates in the mouse enter, not for my need's anyway.

On every mouseEnter I reset the background color of my mode buttons and change the color of the active button. Now when I change the mode in the popup Menu the color of the active button changes, simple I wouldn't have thought of that by my self.
An image of my Easy Chart

1 Like

For posterities sake, (and because I too missed that we were talking about an EasyChart), my code which does add listeners to the popup menu, will work nearly as is with an Easy Chart, you just have to get the chart panel object.

With this code you will know the mode has changed when the mode actually changes.

Code should be placed in the configureChart extension function:

	from java.awt.event import ActionListener
	class CustomModeActionListener(ActionListener):
		def __init__(self,comp):
			self.__comp = comp
		def actionPerformed(self,event):
			if event.source.isSelected():
				self.__comp.mode = event.source.text
	
	def getChart(comp):
		if 'PMIEasyChart$EasyChart' in str(comp.__class__):
			return comp
		for c in comp.getComponents():
			return getChart(c)
		raise ValueError, 'Unable to find Easy Chart component'
		
	pMenu = getChart(self).getPopupMenu()
	
	for menuElement in pMenu.getSubElements():
		if menuElement.getLabel() == 'Mode':
			modeMenu = menuElement.getSubElements()[0]
			
			for item in modeMenu.getSubElements():
				for lis in item.getActionListeners():
					if 'CustomModeActionListener' in str(lis.__class__):
						item.removeActionListener(lis)
				item.addActionListener(CustomModeActionListener(self.parent))

As an aside, I am becoming convinced that there is a bug in Jython's implementation of isinstance() because I just can't get it to work, when it should work.

1 Like

@lrose
Your code works quite well. To test it, I added the necessary custom property to my Easy Chart's parent container named mode, and I set up a button named Pan Mode with an expression binding to control its color:If({Root Container.mode}="Pan",'java.awt.Color(0,250,0)','java.awt.Color(150,150,150)')

At first, it wasn't working because, for unknown reasons, the configureChart extension function wasn't being triggered, but after I bound the background color of the chart to the background color of the parent container, the extension began firing and worked as described:
image
image
When Pan Mode is selected, the button turns green, and when some other mode is selected, it turns grey

1 Like

The only gotcha here is, if someone comes along and unknowingly throughs the chart in a group it will break as it will have a new parent.

Providing a path to the property might be a better option but I haven’t quite wrapped my head around that.

It would be nice if we could put the custom properties directly on the chart.

The classic chart has always had that limitation as it uses the custom property infrastructure to supply its datasets.