Customizing Perspective Table and json scripting help

I am trying to make a table look good for displaying on some TVs.

I have a query, and I planned to resize the fonts and cells for this display.
I thought if I have time I would add some color changing based on values.

However, I don’t manipulate jsons ever.
I need the ability to set ever other row to a color and the ability to inspect a cell value, then change that color.

I am researching here, but it is confusing for me.
https://docs.inductiveautomation.com/display/DOC81/Perspective+-+Table

For cells can do this
For rows it works too but you could also use a css :nth-child() selector.

1 Like

Finally got past the parsing error.
Set format to dataset.

newJSON = []
styleWhite 		= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#FFFFFF"}
styleBluish 	= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#a0bed8"}
	
count=0
for row in range(value.getRowCount()):
 	row_object = {}
   		row_value = {}
   		row_style = {}
 	for col in range(value.getColumnCount()):  
		row_value[value.getColumnName(col)] = value.getValueAt(row, col)
		row_object['value'] = row_value		
		if (count%2)==0:
					row_style=styleWhite
		else:
					row_style=styleBluish 
		row_object['style'] = row_style 
  	count=count+1
	newJSON.append(row_object)
 
#Return the list
return newJSON 
1 Like

nice^^

Isnt row the same as count though? :stuck_out_tongue:

1 Like

First, does your version not have props.rows.striped ?

Second, good job on the script, but as I know you want to learn, I thought I would show some improvements.

  1. Working with pyDataSets is easier and makes the code more readable. (Note: My opinions have changed on this. Once I would have advocated against the unneeded conversion to a pyDataSet)
  2. Instead of incrementing a variable, use the enumerate() function.
  3. Instead of a conditional if...else you can just use count%2 as an index in a list.
  4. You can use zip() to loop with two variables instead of 1. (That’s a very basic description and not the only use of the zip() function.)

These would change your script to this:

ds = system.dataset.toPyDataSet(value)

newJSON = []
styles = [ {"fontSize": 36, "fontWeight": 500, "backgroundColor": "#FFFFFF"},
		{"fontSize": 36, "fontWeight": 500, "backgroundColor": "#a0bed8"}]

for count,row in enumerate(ds):
	row_object = {}
	row_value = {}
	row_style = {}
	for name,colValue in zip(ds.getColumnNames(),row):
		row_value[name] = colValue
		row_object['value'] = row_value
		row_object['style'] = styles[count%2]
	newJSON.append(row_object)

return newJSON

Now if you wan’t to get fancy you can actually write the majority of that as a comprehension.

Making an equivalent script to your original this:

ds = system.dataset.toPyDataSet(value)
styles = [ {"fontSize": 36, "fontWeight": 500, "backgroundColor": "#FFFFFF"},
		{"fontSize": 36, "fontWeight": 500, "backgroundColor": "#a0bed8"}]

return [{'value':{name:colValue for name,colValue in zip(ds.getColumnNames(),row)},'style':styles[count%2]} for count,row in enumerate(ds)]
1 Like

I didn’t see a props.rows.striped, but yes it is there. I see it now. Wish I had known about that.
I wasn’t able to get the text size, but I see the rows area has a style too that I didn’t see.
Edit: I tried it just now, and the striping is not working, also I am still not able to paste PNG images for some reason here. Just errors and then tried to post a jpeg, and got an error too. Tried to show it not working with the table next to the setting.
edit again: oh I have to put a value in each color, not just one and the other on default. It works if I put colors in both. 8.1.0


Thanks, if only I could double heart for the two different scripts.

I knew that count was redundant with row. I thought it was easier for my coworker to read.

How long did that comprehension take you to make?
Do you think of them that detailed in under two minutes?

I think eventually, I might need to color the cells individually based on the value.


Once @lrose , you had shown me an example comparing speeds of:
netTotal=sum([i for i in values if i%2])

and

netTotal=sum(i for i in values if i%2)

What is the name of this second form?
It is not a list so it is not a list comprehension I think.
What is it called?

Generator Expression

2 Likes

I think it suddenly started to talk about using a test to look at commas, and I don’t know why.

I was doing well, then I got this part, and was completely confused:

The syntax requires that a generator expression always needs to be directly inside a set of parentheses and cannot have a comma on either side. With reference to the file Grammar/Grammar in CVS, two rules change:
The rule:
atom: '(' [testlist] ')'
changes to:

atom: '(' [testlist_gexp] ')'
where testlist_gexp is almost the same as listmaker, but only allows a single test after ‘for’ … ‘in’:

testlist_gexp: test ( gen_for | (',' test)* [','] )
The rule for arglist needs similar changes.
This means that you can write:

sum(x**2 for x in range(10))
but you would have to write:

reduce(operator.add, (x**2 for x in range(10)))
and also:

g = (x**2 for x in range(10))
i.e. if a function call has a single positional argument, it can be a generator expression without extra parentheses, but in all other cases you have to parenthesize it.

The exact details were checked in to Grammar/Grammar version 1.49.

Also, how often do they destroy code?

is legal, meaning:

[x for x in (1, 2, 3)]
But generator expressions will not allow the former version:

(x for x in 1, 2, 3)
is illegal.

The former list comprehension syntax will become illegal in Python 3.0, and should be deprecated in Python 2.4 and beyond.

I wrote a bunch of stuff in comprehensions, who controls if the code is going to break?
Now that I have become aware of this ongoing evolution, I am alarmed.
I used to have to up worry about IQAN updating, so I had always standardized on only one version of IQAN 2.x (can’t recall which one) and IQAN 3.4 in that if I recall correctly.

I'm not sure I would call it "detailed", but yeah I put it together in less than two minutes.

It has to do with the way I write code to begin with. Anytime I write code with a structure where I'm looping through one thing to create another, I ask myself if it can be done with a comprehension. You use them enough and you start to see things in a different way.

For instance take a look at the nested loop in my first script (I'll remove the style for simplisity)

row_object = {}
row_value = {}

for name,colValue in zip(ds.getColumnNames(),row):
    row_value[name] = colValue
    row_object['value'] = row_value

First anytime I'm writing a loop and the first thing I do is declare an empty sequence or map, that is almost a dead giveaway that a comprehension may be possible. Second, I not to use variables just to only use them on the next line. There is no reason for row_value to exists as a variable here. This code could be rewritten as this

row_object = {}
for name, colValue in zip(ds.getColumnNames(),row):
    row_object['value'][name] = colValue

Once you've done that, it's a simple leap the comprehension. So then you're left with:

newJSON[]
for count,row in enumerate(ds):
    row_object = { 'value':{name:colValue for name,colValue in zip(ds.getColumnNames(),row}}
    newJSON.append(row_object)

Using the same logic as before, it's easy to make the leap to a comprehension.

Don't forget. Ignition uses Jython, not Python, and Jython only uses 2.7. Ultimately, Inductive determines what scripting language and version are supported. There will be plenty of notice from IA if that changes, I wouldn't be concerned with it very much at all. It barely crosses my mind unless I'm trying to figure out how to make something work and keep getting Python 3 examples.

2 Likes

I understood part of that.

I have this working right now setting even row colors white and odds blue:

newJSON = []
styleWhite 		= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#FFFFFF"}
styleBluish 	= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#a0bed8"}
	
count=0
for row in range(value.getRowCount()):
 	row_object = {}
   		row_value = {}
   		row_style = {}
 	for col in range(value.getColumnCount()):  
		row_value[value.getColumnName(col)] = value.getValueAt(row, col)
		row_object['value'] = row_value		
		if (count%2)==0:
					row_style=styleWhite
		else:
					row_style=styleBluish 
		row_object['style'] = row_style 
  	count=count+1
	newJSON.append(row_object)
 
#Return the list
return newJSON

New requirements:

Highlight machine name cell red when stopped, gross> 1000, and net >1000.
Highlight the machine name green when running.
Highlight the row red when machine is stopped over 5 minutes.
Set the machine name cell white otherwise.
Keep the even rows white and odd rows blue when not red.

I have tags for if they are running, gross, net, and minutes on current code with one specific code as running.

I am not sure how to access the value of a specific cell.
I am not sure how to both set row colors and cell colors.

I don’t fully understand these lines:

row_value[value.getColumnName(col)] = value.getValueAt(row, col)
row_object['value'] = row_value	

So I want to start with how do these two work?
I tried this:

this=[]
this['value']=5
print this

I got an error that list indices must be integers.
So where is ['value'] being set at?

edit: realized it is defining a dictionary

the same as with a row be splitting it into a {style:...,value:...}

idk the structure/names of your columns but it would be somethign like this

row_value[value.getColumnName(col)] = {"value":value.getValueAt(row, col)}
if value.getColumnName(col)=="machineName" and value.getValueAt(row, "stopped") == True and value.getValueAt(row, "gross") > 1000 and value.getValueAt(row, "net") > 1000:
   row_value[value.getColumnName(col)]["style"]=styleRed
1 Like

Thanks, I see that is how to set the row color with conditions.

I don’t understand how I can set the row color and the cell color, and get the right results.

also this would be easier

1 Like

this is setting the cell color

row_value[value.getColumnName(col)] = the cell
row_value[value.getColumnName(col)][“style”] = the cell style

row_object[‘style’] = row_style

1 Like

That is really confusing to me. I am not sure where to start with my questions.

I think I need to add all the conditional bits to my query, so that on the tag binding I get all the data and don’t have to do additional reads in the script.

I kind of understand:

	def getStyle(row, col):
		if col == 'LED':
			color = value.getValueAt(row, col)
			if color == 'red':
				return {"backgroundColor": "red"}
			elif color == 'green':
				return {"backgroundColor": "green"}
			elif color == 'gray':
				return {"backgroundColor": "gray"}

	return [
		{
			"value": value.getValueAt(row, col)
			"style": getStyle(row, col)
			for col in value.columnNames.sort()
		}
		for row in range(value.rowCount)
	]

I think it would be kind of like:

def getStyle(row, col):
    if row%2==0:
       subStyle= {"backgroundColor": "white"}
    elif color == 'green':
       subStyle= {"backgroundColor": "blue"}

 if col!='Line' and value.getValueAt(row, "running")==0 and value.getValueAt(row, "stateDuration") > 5:
              subStyle= {"backgroundColor": "red"}
   
  if col=='Line' and (value.getValueAt(row, "running")==0 and value.getValueAt(row, "gross") > 1000 and value.getValueAt(row, "net") > 1000:
              subStyle= {"backgroundColor": "green"}
  elif    col=='Line' and value.getValueAt(row, "gross") > 1000 and value.getValueAt(row, "net") > 1000:
              subStyle= {"backgroundColor": "red"}

 return subStyle


I am not sure, but will test it out.

[quote=“zacharyw.larson, post:15, topic:62348”]

return [
		{
			"value": value.getValueAt(row, col)
			"style": getStyle(row, col)
			for col in value.columnNames.sort()
		}
		for row in range(value.rowCount)
	]

it doesnt look like that is goign to make a style separate for the row and columns
its also not parsing

lets try again

newJSON = []
styleWhite 		= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#FFFFFF"}
styleBluish 	= {"fontSize": 36,"fontWeight": 500,"backgroundColor": "#a0bed8"}
	

for row in range(value.getRowCount()):
 	row_object = {}
   	row_value = {}
   	row_style = {}
 	for col in value.getColumnNames():  
		col_object = {}
		col_object["value"] = value.getValueAt(row, col)
		if col=="machineName" and value.getValueAt(row, "stopped") == True and value.getValueAt(row, "gross") > 1000 and value.getValueAt(row, "net") > 1000:
		  col_object["style"]=styleRed               
		row_value[col] = col_object
		row_object['value'] = row_value		
		if (row%2)==0:
					row_style=styleWhite
		else:
					row_style=styleBluish 
		row_object['style'] = row_style 
  	
	newJSON.append(row_object)
 
#Return the list
return newJSON
1 Like
value.getRowCount()

This can be called on any dataset. Gives me the number of the rows.

row_object ={}
This line creates an empty python dictionary.

value.getColumnNames()
This can be called on any dataset, but what does this return?
I tried to search it on the manual, but when I search for .getColumnNames(), I get no results.

headers = ["City", "Population", "Timezone", "GMTOffset"]
data = []
 
data.append(["New York", 8363710, "EST", -5])
data.append(["Los Angeles", 3833995, "PST", -8])
data.append(["Chicago", 2853114, "CST", -6])
data.append(["Houston", 2242193, "CST", -6])
data.append(["Phoenix", 1567924, "MST", -7])
 
cities = system.dataset.toDataSet(headers, data)
print cities.getColumnNames()

[City, Population, Timezone, GMTOffset]

1 Like

Thanks

Where do I read about getColumnNames() and functions like it?

https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.8/com/inductiveautomation/ignition/common/Dataset.html#getColumnNames()

1 Like