Is it possible to add an additional value to a gauge? I want to add another needle.
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.
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.
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:
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.