Kivy-speedmeter

Kivy-speedmeter-demo1-1.png

This is the documentation for the my kivy-speedmeter widget.

For now, the source code is on github.

You can install it by downloading it somewhere your python interpreter can find it.

SpeedMeter is a Kivy widget aimed at representing numerical 'physical' values in a "clock-face" or dial manner. Minimum and Maximum value, and the start and end angle, notably, are fully configurable. For the sake of readability, the "labels" (intermediate values represented on the dial) are integers, but you can override the display function to represent whatever string fits your needs, as illustrated with PI fractions in demo1.

SpeedMeter is clickable, so you can click on it, and the widget is able to compute the corresponding physical value.

All default values are "reasonable", so if you do nothing, you get a working widget for the range 0..100 (asymmetric, because I think it's cool).

For now, SpeedMeter has been tested under Linux and Android (with buildozer).

Speedmeter functionalities were heavily inspired by Andrea Gavana's SpeedMeter for wxPython.

Demos

demo1

demo1 is basically the wxPython speedmeter demo rewritten with my widget. This was my guide when writing it, I basically declared mission accomplished when the widget was good enough to replicate wxPython's demo.

demo2

demo2 as the control to change almost all parameters of the widget. You can play with it to see the effect of changing this or that parameter. When starting, all the parameters are set to the widget default parameter.

API

class SpeedMeter(**kwargs)

Bases: kivy.uix.widget.Widget

Physical values and their representation

value

The physical value. Setting it moves the hand.

value is a NumericProperty, float values are ok, and default to 0. If value if lower than min or greater than max, behavior is undefined (it's up to you to catch the value and apply some behaviour if you're so inclined).

min

The minimum physical value.

min is a NumericProperty, must be an integer and defaults to 0.

max

The minimum physical value.

max is a NumericProperty, must be an integer and defaults to 100.

Note that you must have min < max, or undefined results will occur.

tick

Tick is the physical value between ticks. So for example, if tick is 15, with min = 0 and max = 100, the value represented on the dial will be 0, 15, 30, 45, 60, 75 and 90. Note that, since in this case 15 doesn't evenly divide the 0..100 interval, the last value isn't represented, so it is preferable if there is a coherence between min and max and tick, but it won't prevent the widget from working otherwise.

tick is a NumericProperty, must be an integer and defaults to 10.

subtick

subtick is the number of sub-divisions between 2 ticks (without label), thus it is the number of divisions, not a physical value. If it is 0 or 1, there are no subticks.

subtick is a NumericProperty, must be an integer and defaults to 0 (no subtick).

display_first

If True (the default), min label isn't drawn on the dial. This is useful mainly if you draw a (real) clock where min = 0 and max = 12, but you draw only 12 on the clock face and don't want it to be cluttered by the 0.

You could get a similar result by overriding [#value_str], but drawing clock is common enough to deserve this convenience value.

display_first is a BooleanProperty, and defaults to True.

display_last

If True (the default), mwx label isn't drawn on dial. This function exists mostly by symmetry with display_first.

display_last is a BooleanProperty, and defaults to True.

start_angle & end_angle

These are the angle at which the dial starts and ends. The basis for the frame is Kivy's, that is the angles go from 0 to 360 (actually -360 to 360), 0 is up, 90 is right 180 is down and so on, like a map. If start_angle < end_angle, the physical values increase clockwise, they increase counter-clockwise if end_angle > start_angle. So for example, if start_angle = -90 and end_angle = 90, the dial is a half-circle on the upper half, and values are increasing clockwise. If start_angle = 270 (same position as -90 geometrically) and end_angle = 90, the dial is a half-circle on the lower half, and values are increasing counter-clockwise (since start_angle > end_angle). You can play with demo2 to get a feeling of how things influence each others and find the value suiting your needs if you find reasoning on the value too tedious.

start_angle = end_angle is a special case, in that case, the dial is a full circle, and the values increase clockwise. There is no way to have counter-clockwise value increases yet !

start_angle and end_angle are NumericProperty. start_angle defaults to -90 and end_angle defaults to 135.

value_font_size

Quite self-explanatory. Most likely, you don't want to change this.

value_font_size is a NumericProperty, and defaults to 15.

Indication on the dial

label

If set, the corresponding text is drawn on the dial. By default, the text is at equal distance from min and max and at about 1/3 in the dial. This can be changed with #label_radiu_ratio and #label_angle_ratio.

label is a StringProperty, and defaults to (nothing to display).

label_font_size

Quite self-explanatory.

label_font_size is a NumericProperty, and defaults to 15.

label_icon

If set, this is the name of an image (file or atlas part) put on the dial. By default, the text is at equal distance from min and max and at about 1/3 in the dial. This can be changed with #label_radius_ratio and #label_angle_ratio.

By default, the icon size is half the radius of the dial. This can be changed with #label_icon_scale.

If both label and label_icon are set, label_icon takes precedence.

label_icon is a StringProperty, and defaults to '' (nothing to display).

label_icon_scale

The ratio between the radius of the dial and the size of the icon. Default to 0.5, which is more or less balanced. Useful values run from 0 (icon reduced to a single point) to 2.0 (icon covers the surface of the dial).

label_icon_scale is a NumericProperty, and defaults to 0.5 (icon is half of the radius).

label_radius_ratio

The position of the icon or label from the center of the dial. 0 means the center of the icon is at the center of the dial, 1 that the icon is on the border of the dial, -1 on the border of the dial on the opposite direction. Defaults to 0.3, which puts the icon at a readable place on the dial.

label_radius_ratio is a NumericProperty, and defaults to 0.3.

label_angle_ratio

The position of the icon or label between the min and max position. Defaults to 0.5, meaning that the icon is at equal distance from the min and max labels. 0 means the icon is on the min label, 1 that it is on the max label. You can't use this property to go outside of the dial, but you can use a negative label_radius_ratio for that purpose.

label_angle_ratio is a NumericProperty, and defaults to 0.5.

needle_color

The hand ("needle") color. This is a string interpreted as an hex_color.

needle_color is a StringProperty, and defaults to #6bf2ff, which is close to a slider default color on Linux.

needle_image

The hand ("needle") image.

needle_image is a StringProperty, and defaults to 'needle.png', a needle picture liberally stolen from the original kivy speedmeter widget ! The widget comes with 2 other images needle2 and needle3.

shadow_color

If present, this is the #RGB description of the color of the "shadow" (an arc of circle marking the position of the needle). If it's not present, there is no shadow. Currently, the shadow width can't be specified.

Sectors

Color sectors can be drawn on the dial, for example to symbolize the cool, warm and overheat part of a dial. This is driven by the sectors and sector_width properties described below.

sectors

This is a list of alternating values and colors. The values are expressed in "physical" terms (a value between min and max), colors are expressed as hex_color strings. If the last value is omitted, it defaults to max. So for example, if your physical values represent the body of human temperature with min=34, max=44, a white arc for hypothermia between 34 and 36 C, green arc between 36 and 38, red between 38 and 42, and nothing after 42 to mean death (!), you do it by putting [34, '#ffffff', 36, '#00ff00', 38, '#ff0000', 42] in sectors. The physical values must be between min and max included, must be sorted in increasing order, and don't have to be integer values.

sectors is a ListProperty, and defaults to [], meaning no sector is drawn.

sector_width

This is the width of the sectors. The special value 0 (default) means the whole sector is drawn. Other values indicate the width of the arc, in that case only an arc is drawn.

sector_width is a NumericProperty, and defaults to 0.

Other methods

get_value(pos)

pos is a pair (x, y). get_value returns the corresponding physical value. The x,y position has to be inside the dial or None is returned. For example, if you get a MotionEvent, you can find the corresponding physical value (in the min..max range) with get_value(*motionevent.pos).

collide_point(x, y)

This standard widget function is implemented.

value_str(n)

This is the method called to convert data n into the string represented on the dial. It defaults to str(int(n)). If you want to have more control over what's written on the dial, you can derive your own class from SpeedMeter and override this function. See demo1 for an example using fractions of PI.

Notable differences with wxPython SpeedMeter

  • For now, rotated values on the dial are not supported
  • Gradient color from center to the outside is not supported

TODO

  • The "Label" (central text or icon) should be a real Kivy Label (meaning the SpeedMeter acts as a Layout).
  • More hands !
  • Rotation of displayed values on the cadran.

History

I need (a lot of) different gauges for a robotic project I'm doing, to monitor voltage, current, motors rpm and many other parameters related to the inner thinking of the robot. When troubleshooting, the robot console has to run on a "device", and I was undecided when starting which kind of device it would be, PC, tablet or other. So I just started writing the console using the (excellent) wxPython, meaning using a PC as the console in the field. I used Andrea Gavana SpeedMeter for the gauges and was very happy with it, in particular its flexibility in representing various range of data.

However, I finally decided that, in the field, a tablet was better suited to be able to "travel lightly", so to speak. This ruled out wxPython, and I started to use Kivy for its ability to run both under Linux and Android, which I must say has been very satisfactory so far. However, in switching to Kivy I lost my beloved speedmeter, so I took a few weeks to rewrite it under Kivy, learning Kivy along the way. I saw that Kivy graphical model was based on GL, and I had used it back in the 90s (not openGL, the original GL on Silicon Graphics workstations), so I wasn't in completely unknown territory.