Gauge properties

Is it possible to add an additional value to a gauge? I want to add another needle.
image

1 Like

I love this idea. What kind of gauge is this? Is this Perspective?

You can do, just about anything you want with an SVG in perspective. And Vision is possibly easier.

Perspective:

Vision:

Perhaps if I have some free time, I'll build one with two needles. Sounds fun.

2 Likes

yes

Okay, so I may have gotten a little sniped with this, but it was fun once I got into it.

The below image shows two SVG elements that are exactly the same as far as the programming goes, but the configuration is different using custom properties.

This was accomplished with a property change event on a custom property and a couple of property bindings with script transforms. If I were planning to do this in a production environment I would move the scripting to a project library at a minimum, there are probably some other optimizations that could be had.

The property change refreshes the bindings so any change to custom property results in a change to the SVG.

Here is the structure for the custom property. Be aware that as you add things to the config you will see some errors until the entire structure for each matches. The simplest thing is to duplicate the objects and then change the values to your needs.

Property Change Script
	import math
	majTickDegrees = self.custom.config.gauge.ticks.major.degreesPerTick
	tickRange = self.custom.config.gauge.ticks.scale.max - self.custom.config.gauge.ticks.scale.min
	degBetweenTicks = 1
	
	center = {'x':self.custom.config.gauge.center.x,'y':self.custom.config.gauge.center.y}
	
	tickStartY = center['y'] + self.custom.config.gauge.radius
	tickEndY = tickStartY + self.custom.config.gauge.ticks.major.length
	
	minTickDegrees = majTickDegrees / float(self.custom.config.gauge.ticks.minor.graduations)
	gaugeHeight = self.custom.config.gauge.radius * math.cos(math.radians(self.custom.config.gauge.endAngle) - math.pi/2) + center['y']
	
	if majTickDegrees and majTickDegrees != self.custom.config.gauge.ticks.scale.max:
		degBetweenTicks = (self.custom.config.gauge.endAngle - self.custom.config.gauge.startAngle) / (tickRange / float(majTickDegrees))
	
	if self.custom.config.gauge.ticks.major.visible:
		self.props.elements[1].elements[0].elements = [{
														'type':'line',
														'stroke':{'paint': self.custom.config.gauge.ticks.major.color},
														'x1':center['x'],
														'y1':tickStartY,
														'x2':center['x'],
														'y2':tickEndY,
														'style':{'transform':'rotate({}deg)'.format((degBetweenTicks * thisTick) + self.custom.config.gauge.startAngle),
																 'transform-origin':'{x}px {y}px'.format(**center)}
														} for thisTick in range((tickRange / majTickDegrees) + 1)]
	else:
		self.props.elements[1].elements[0].elements = []
	
	if self.custom.config.gauge.ticks.text.visible:
		self.props.elements[1].elements[2].elements = [{
														'type':'text',
														'fill':{'paint':self.custom.config.gauge.ticks.text.color},
														'text': '{:d}'.format(int(thisTick * majTickDegrees + self.custom.config.gauge.ticks.scale.min)),
														'text-anchor':'middle',
														'text-align':'center',
														'x': center['x'],
														'y': center['y'] + self.custom.config.gauge.radius,
														'style':{'transform':'translate({}px, {}px)'.format(-(self.custom.config.gauge.radius+self.custom.config.gauge.ticks.text.offset) * math.cos(math.radians((degBetweenTicks * thisTick) + self.custom.config.gauge.startAngle) - math.pi/2),-self.custom.config.gauge.radius + 5 - (self.custom.config.gauge.radius+self.custom.config.gauge.ticks.text.offset)*math.sin(math.radians((degBetweenTicks * thisTick) + self.custom.config.gauge.startAngle) - math.pi / 2)),
																 'transform-origin': '{x}px {y}px'.format(**center),
 																 'font-family':self.custom.config.gauge.ticks.text.font.family,
 																 'font-size': self.custom.config.gauge.ticks.text.font.size}
														} for thisTick in range((tickRange/majTickDegrees) + 1)]
	else:
		self.props.elements[1].elements[2].elements = []
		
	
	tickEndY = tickStartY + self.custom.config.gauge.ticks.minor.length
	
	if minTickDegrees and minTickDegrees != self.custom.config.gauge.ticks.scale.max:
		degBetweenTicks = (self.custom.config.gauge.endAngle - self.custom.config.gauge.startAngle) / (tickRange / float(minTickDegrees))
		
	if self.custom.config.gauge.ticks.minor.visible:
		self.props.elements[1].elements[1].elements = [{
														'type':'line',
														'stroke':{'paint':self.custom.config.gauge.ticks.minor.color},
														'x1':center['x'],
														'y1':tickStartY,
														'x2':center['x'],
														'y2':tickEndY,
														'style':{'transform':'rotate({}deg)'.format((degBetweenTicks * thisTick) + self.custom.config.gauge.startAngle),
																 'transform-origin':'{x}px {y}px'.format(**center)}
														} for thisTick in range(int((majTickDegrees/minTickDegrees) * (tickRange/majTickDegrees)) + 1) if (thisTick % self.custom.config.gauge.ticks.minor.graduations)]
	else:
		self.props.elements[1].elements[1].elements = []
		
	degBetweenTicks = (self.custom.config.gauge.endAngle - self.custom.config.gauge.startAngle)/float(tickRange)
	
	if self.custom.config.gauge.needles:
		elems = []
		for needle in self.custom.config.gauge.needles:
			elems.append({
					'type':'circle',
					'fill':{'paint':needle.color},
					'cx':center['x'],
					'cy':center['y'],
					'r':3										
					})
			elems.append({'type':'path',
					'fill':{'paint':needle.color},
					'stroke':{'paint':needle.color,'width':1.0},
					'd':'M{},{} m{},{} L{},{} L{},{}'.format(center['x'],center['y'],0,self.custom.config.gauge.radius,center['x'] + 1.5,center['y'],center['x'] - 1.5,center['y']),
					'style':{'transform':'rotate({}deg)'.format((degBetweenTicks * needle.value) + self.custom.config.gauge.startAngle),
							 'transform-origin':'{x}px {y}px'.format(**center)}
					})
		self.props.elements[3].elements = [{'type':'group',
											'elements':elems
											}]
	else:
		self.props.elemenst[3].elements = []
	self.refreshBinding('props.elements[0].elements')
	self.refreshBinding('props.elements[2].d')

Binding on props.elements[0].elements. Bound to this.custom.config.gauge.fill this draws the filled areas in the gauge.

Script Transform for Blocks
	import math
	blocks = []
	startAngle = self.custom.config.gauge.startAngle
	gaugeRadius = self.custom.config.gauge.radius - value.gap
	gaugeScale = (self.custom.config.gauge.endAngle - self.custom.config.gauge.startAngle) / float(self.custom.config.gauge.ticks.scale.max - self.custom.config.gauge.ticks.scale.min)
	for block in value.blocks:
		l1x1 = value.insideRadius * math.cos(math.radians(block.startValue * gaugeScale + startAngle) + math.pi/2)
		l1y1 = value.insideRadius * math.sin(math.radians(block.startValue * gaugeScale + startAngle) + math.pi/2)
		l1x2 = gaugeRadius * math.cos(math.radians(block.startValue * gaugeScale + startAngle) + math.pi/2)
		l1y2 = gaugeRadius * math.sin(math.radians(block.startValue * gaugeScale + startAngle) + math.pi/2)
		l2x1 = gaugeRadius * math.cos(math.radians(block.endValue * gaugeScale + startAngle) + math.pi/2)
		l2y1 = gaugeRadius * math.sin(math.radians(block.endValue * gaugeScale + startAngle) + math.pi/2)
		l2x2 = value.insideRadius * math.cos(math.radians(block.endValue * gaugeScale + startAngle) + math.pi/2)
		l2y2 = value.insideRadius * math.sin(math.radians(block.endValue * gaugeScale + startAngle) + math.pi/2)
		
		blocks.append( 'M{12},{13} m{0},{1} l{2},{3} a{4},{4} 0 0,1 {5},{6} l{7},{8} a{9},{9} 0 0,0 {10},{11}'.format(
			l1x1,
			l1y1,
			l1x2-l1x1,
			l1y2-l1y1,
			gaugeRadius,
			l2x1 - l1x2,
			l2y1 -l1y2,
			l2x2 - l2x1,
			l2y2 - l2y1,
			value.insideRadius,
			l1x1 - l2x2,
			l1y1 - l2y2,
			self.custom.config.gauge.center.x,
			self.custom.config.gauge.center.y))
	return	[{'type':'path',
			'fill':{'paint':value.blocks[i].color},
			'd':block} for i,block in enumerate(blocks)]

Binding on props.elements[2].d. Bound to this.custom.config.gauge this draws the arc that is the base for the tick marks.

Script Transform for Baseline
	import math
	
	startAngle = self.custom.config.gauge.startAngle
	gaugeRadius = self.custom.config.gauge.radius
	
	x1 = -gaugeRadius * math.cos(math.radians(startAngle) - math.pi/2)
	y1 = -gaugeRadius * math.sin(math.radians(startAngle) - math.pi/2)
	x2 = -gaugeRadius * math.cos(math.radians(self.custom.config.gauge.endAngle) - math.pi/2)
	y2 = -gaugeRadius * math.sin(math.radians(self.custom.config.gauge.endAngle) - math.pi/2)
	
	return 'M{0},{1} m{2},{3} a{4},{4} 0 1,1 {5},{6}'.format(self.custom.config.gauge.center.x,self.custom.config.gauge.center.y,x1,y1,gaugeRadius,x2-x1,y2-y1)

I don't think there's need to develop anything special.
This demo uses three gauges superimposed in a coordinate container.

Triple gauge

Just set the color of anything causing a problem to transparent by adding a 00 to the end of the hex color. e.g., #aa000000 will give transparent red.

I'd suggest going a bit less garish with your colors. Have a look at high-performance HMI standards to give you some ideas. i.e., your gauge would have been considered great in the early 1980s but looks way out of date now. Search on web or YouTube for ISA101. They have fascinating guidelines on the use (and misuse) of color and how it affects situational awareness.

For example:
HP HMI guages

Image source: World of Instrumentation.

How long did it take you to read six gauges and spot the tank running low and the tank empty?

Be cool, man. Be cool!

Except for the performance loss, particularly if you're going to have many of these on a single view.