Easy Chart Power Table

Hi all,
I've put together a script to update a Power Table with some data from an Easy Chart so that it shows a nicer legend than the built in one.

It seems to work OK, but I would like some feedback on how I might make the code clearer or more efficient or if there are any best practices (say for fault handling and robustness) that I should be following. I'm a new to scripting in Python so I appreciate any guidance!

The script is as follows and sits on the Property Change function of the Easy Chart. It uses some data from custom properties on the PowerTable (xTraceValues) which are written by the getXTraceLabel extension function but otherwise it is pretty straight forward.

# Whenever the startDate property on the chart changes update the data on the power chart
# including tooltip/description, current values, xTrace values and engineering units.

if event.propertyName == "startDate":
	# Get all the data from the chart pens dataset
	pensDataSet = event.source.parent.getComponent('Easy Chart').tagPens
	powerTableData = event.source.parent.getComponent('Power Table').data

	toolTipPaths = []
	valuePaths = []
	engUnitPaths = []

	qualifiedToolTips = []
	qualifiedValues = []
	qualifiedEngUnits = []

	toolTips = []
	values = []
	engUnits = []

	xTraceValues = []

	# Scan through the rows of pens from the chart
	# and put the path for the tooltips, values etc. into seperate datasets
	for row in range(pensDataSet.getRowCount()):
		toolTipPaths.append(pensDataSet.getValueAt(row, 1)+".Tooltip")
		valuePaths.append(pensDataSet.getValueAt(row, 1))
		engUnitPaths.append(pensDataSet.getValueAt(row, 1)+".EngUnit")

	# Fetch the qualified values from the tagPaths
	qualifiedToolTips = system.tag.readBlocking(toolTipPaths)
	qualifiedValues = system.tag.readBlocking(valuePaths)
	qualifiedEngUnits = system.tag.readBlocking(engUnitPaths)

	# Get only the value we are interested in
	for row in range(len(qualifiedToolTips)):
		toolTips.append(qualifiedToolTips[row].value)
		values.append(qualifiedValues[row].value)
		engUnits.append(qualifiedEngUnits[row].value)
		xTraceValues.append(event.source.parent.getComponent('Power Table').xTraceValues.getValueAt(row,1))

	# Combine the pens data set with the new columns
	pensDataSet = system.dataset.addColumn(pensDataSet, 1, toolTips, "DESCRIPTION", str)
	pensDataSet = system.dataset.addColumn(pensDataSet, 2, values, "VALUE", float)
	pensDataSet = system.dataset.addColumn(pensDataSet, 3, xTraceValues, "XTRACE", float)
	pensDataSet = system.dataset.addColumn(pensDataSet, 4, engUnits, "UNIT", str)

	# Add the new columns to the power table		
	event.source.parent.getComponent('Power Table').data = pensDataSet

This code looks pretty clear to me. If I had to nitpick the code, I see the following two minor things:
• The the dataset for your power table is assigned to the variable powerTableData at the beginning of the script, but it is never used, so it can be deleted.
• I've read that using the rowCount property is better than invoking the getRowCount() method.

Why just okay? Is there an some specific issue that bugs you?

Thanks Justine,
That is helpful, I'll try out those tips.

My main concern is that the whole thing seems a little convoluted. The need to get three different paths for the meta info on the same tag was annoying. Then after reading the qualified values for each meta data point, having to pull out the raw values separately is a bit of a pain. Not a big deal but perhaps it could be done more efficiently...

My other concern is that perhaps I should be using try/except (or some other structure) to catch any errors.

Mostly I'm just conscious that as a beginner there are probably some best practices which I might not be aware of.

Thanks again for your comments!

You could probably reduce some of the redundant looping and increase the efficiency with comprehension.

Example:

	valuePaths = pensDataSet.getColumnAsList(1)
	toolTipPaths = [item + ".Tooltip" for item in valuePaths]
	engUnitPaths = [item + ".EngUnit" for item in valuePaths]

	toolTips = [toolTipTag.value for toolTipTag in system.tag.readBlocking(toolTipPaths)]
	values = [valueTag.value for valueTag in system.tag.readBlocking(valuePaths)]
	engUnits = [engUnitTag.value for engUnitTag in system.tag.readBlocking(engUnitPaths)]
	xTraceValues = [event.source.parent.getComponent('Power Table').xTraceValues.getValueAt(row, 1) for row in range(len(values))]

Edit:
This could be a cleaner approach to the xTraceValues:

xTraceValues = list(event.source.parent.getComponent('Power Table').xTraceValues.getColumnAsList(1))

Second Edit: Corrected typos per the OP's code testing.

Hi Justin.
I implemented the changes you suggested above, and it has made things significantly tidier! My final code is as follows:

elif event.propertyName == "startDate" or event.propertyName == "selectedXValue":
	# Get all the data from the chart pens dataset
	pensDataSet = event.source.parent.getComponent('Easy Chart').tagPens
	
	# Initialise lists
	toolTipPaths = []
	valuePaths = []
	engUnitPaths = []
	toolTips = []
	values = []
	engUnits = []
	xTraceValues = []

	# Scan through the rows of pens from the chart
	# and put the path for the tooltips, values etc. into seperate datasets
	valuePaths = pensDataSet.getColumnAsList(1)
	toolTipPaths = [item + ".Tooltip" for item in valuePaths]
	engUnitPaths = [item + ".EngUnit" for item in valuePaths]

	# Fetch the values from the tagPaths
	toolTips = [toolTipTag.value for toolTipTag in system.tag.readBlocking(toolTipPaths)]
	values = [valueTag.value for valueTag in system.tag.readBlocking(valuePaths)]
	engUnits = [engUnitTag.value for engUnitTag in system.tag.readBlocking(engUnitPaths)]

	# Fetch xTrace values from the power table custom properties (written by getXTraceLabel extension function)
	xTraceValues = list(event.source.parent.getComponent('Power Table').xTraceValues.getColumnAsList(1)) 
	
	# Combine the pens data set with the new columns
	pensDataSet = system.dataset.addColumn(pensDataSet, 1, toolTips, "DESCRIPTION", str)
	pensDataSet = system.dataset.addColumn(pensDataSet, 2, values, "VALUE", float)
	pensDataSet = system.dataset.addColumn(pensDataSet, 3, xTraceValues, "XTRACE", float)
	pensDataSet = system.dataset.addColumn(pensDataSet, 4, engUnits, "UNIT", str)

	# Add the new columns to the power table		
	event.source.parent.getComponent('Power Table').data = pensDataSet

I did have to correct the system.tag.readBlocking parameters to point to the correct path list and the event.source.parent.getComponent('Power Table').xTraceValues.getColumnAsList(1) function needed to be enclosed in a list() function but apart from that it worked great. So thanks again :slight_smile:

1 Like

All of the readBlocking calls should be combined into a single call.

There may be some other things you can do to help, I’ll look at it in the morning.

I originally typed it up this way because I agree with you, but then, I decided that it looked a little convoluted, so I left it separated:

allValues = system.tag.readBlocking(valuePaths + toolTipPaths + engUnitPaths)
values = [allValues[index].value for index in range(len(valuePaths))]
toolTips = [allValues[index + len(valuePaths)].value for index in range(len(valuePaths))]
engUnits = [allValues[index + 2 * len(valuePaths)].value for index in range(len(valuePaths))]

vs

values = [valueTag.value for valueTag in system.tag.readBlocking(valuePaths)]
toolTips = [toolTipTag.value for toolTipTag in system.tag.readBlocking(toolTipPaths)]
engUnits = [engUnitTag.value for engUnitTag in system.tag.readBlocking(engUnitPaths)]

Edit:
Using a for loop looks a little cleaner with the allValues approach:

allValues = system.tag.readBlocking(valuePaths + toolTipPaths + engUnitPaths)
for index in range(len(valuePaths)):
	values.append(allValues[index].value)
	toolTips.append(allValues[index + len(valuePaths)].value)
	engUnits.append(allValues[index + len(valuePaths) +len(toolTipPaths)].value)

Okay, so here is how I would approach this.

First sometime ago, I helped with a similar problem, and @PGriffith showed this "chunker" function (that he got from stackOverflow), that I will use. It's extremely useful in situations like this because it allows you to read all of the tags in one go and then separate out the "chunks" into the things you care about.

def chunker(seq,size):
    return (seq[pos:pos + size] for pos in xrange(0,len(seq),size))

Original Chunker Post

So define that somewhere in the script prior to using it.

Then your script can look like this:

def chunker(seq,size):
    return(seq[pos:pos + size] for pos in xrange(0,len(seq),size))

pens = event.source.parent.getComponent('Easy Chart').tagPens

paths = pens.getColumnAsList(1)
properties = ['Tooltip','Value','EngUnit']

tagPaths = ['{}.{}'.format(path,prop) for prop in properties for path in paths]
tagValues = system.tag.readBlocking(tagPaths)

columns =  [[qv.value for qv in chunk] for chunk in chunker(tagValues,len(paths))]
columns.insert(2,event.source.parent.getComponent('Power Table').xTraceValues.getColumnAsList(1))

for index,column,desc,colType in zip(xrange(1,5),columns,("DESCRIPTION","VALUE","XTRACE","UNIT"),(str,float,float,str)):
    pens = system.dataset.addColumn(pens,index,list(column),desc,colType)

event.source.parent.getComponent('Power Table').data = pens

EDIT: changes made to reflect the errors @justinedwards.jle pointed out.

3 Likes

Looks good.
Test environment result:
image

Hey, thank you for this post it is really helpful. Can you include how to pull the xtrace values to the xTraceValues dataset?

The easy chart has an extension function getXTraceLabel, you can use this to process the xTraceValues into a dataset.