microbit-foundation / micropython-microbit-v2

Temporary home for MicroPython for micro:bit v2 as we stablise it before pushing upstream
MIT License
47 stars 27 forks source link

Feature request: bar graph #10

Open microbit-giles opened 4 years ago

microbit-giles commented 4 years ago

(copied from https://github.com/microbit-foundation/micropython-microbit/issues/45)

MakeCode has a 'plot bar graph of X up to Y' block that we use in several projects on the website, and will be using for a sound-level meter for v2. At the moment we have to use fudges for Python versions of the same projects like making LEDs brighter or dimmer.

It would be great if micro:bit microPython had the same functionality so we can publish projects that look and work the same way in MakeCode and Python and are easy to code.

image

the implementation in MakeCode is in https://github.com/microsoft/pxt-microbit/blob/26bdec613265dbea3c4d1ac1586b14024df174d2/libs/core/led.ts#L34

    // what's the current high value
    let barGraphHigh = 0;
    // when was the current high value recorded
    let barGraphHighLast = 0;

    /**
     * Displays a vertical bar graph based on the `value` and `high` value.
     * If `high` is 0, the chart gets adjusted automatically.
     * @param value current value to plot
     * @param high maximum value. If 0, maximum value adjusted automatically, eg: 0
     */
    //% help=led/plot-bar-graph weight=20
    //% blockId=device_plot_bar_graph block="plot bar graph of %value up to %high" icon="\uf080" blockExternalInputs=true
    //% parts="ledmatrix"
    export function plotBarGraph(value: number, high: number): void {
        const now = input.runningTime();
        console.logValue("", value);
        value = Math.abs(value);

        // auto-scale "high" is not provided
        if (high > 0) {
            barGraphHigh = high;
        } else if (value > barGraphHigh || now - barGraphHighLast > 10000) {
            barGraphHigh = value;
            barGraphHighLast = now;
        }

        // normalize lack of data to 0..1
        if (barGraphHigh < 16 * Number.EPSILON)
            barGraphHigh = 1;

        // normalize value to 0..1
        const v = value / barGraphHigh;
        const dv = 1 / 16;
        let k = 0;
        for (let y = 4; y >= 0; --y) {
            for (let x = 0; x < 3; ++x) {
                if (k > v) {
                    unplot(2 - x, y);
                    unplot(2 + x, y);
                } else {
                    plot(2 - x, y);
                    plot(2 + x, y);
                }
                k += dv;
            }
        }
    }
jaustin commented 4 years ago

It is quite attractive to have this as a python module not C/C++ - I think a good starting point would just be a simple Python implementation of the same. Anyone approaching this bug interested in helping out, building a MicroPython implementation of plot_bar_graph() would be a nice start. That way it could be included in a script for the mean time.

rcolistete commented 3 years ago

See my repository : https://github.com/rcolistete/Plots_MicroPython_Microbit function "plot_bars(vector, pixelscale = 1)".

jaustin commented 3 years ago

@rcolistete thanks! That's much more similar to an actual 'bar graph' - in many ways the code described above is not really a bar graph but a 'magnitude indicator' I guess.

@microbit-matt-hillsdon did you also do something similar that could be brought in as an example here?

microbit-matt-hillsdon commented 3 years ago

@microbit-matt-hillsdon did you also do something similar that could be brought in as an example here?

I ported the MakeCode code Giles referenced in the first comment. It hasn't really been tested beyond a quick play as I just did it for an easy example of a Python module. Easiest to just copy/paste for now:

# microbit-module: bargraph@0.1.0
"""
Port of the MakeCode bar graph.
"""
from microbit import display, running_time

# what's the current high value
__bar_graph_high = 0
# when was the current high value recorded
__bar_graph_high_last = 0 

def __unplot(x, y):
    display.set_pixel(x, y, 0)

def __plot(x, y):
    display.set_pixel(x, y, 9)

def plot_bar_graph(value, high = 0):
    """Displays a vertical bar graph based on the `value` and `high` value.

    If `high` is 0, the chart gets adjusted automatically.
    Parameters:
    :param value: current value to plot
    :param high: maximum value. If 0, maximum value adjusted automatically.
    """
    global __bar_graph_high
    global __bar_graph_high_last

    now = running_time()
    value = abs(value)

    if high > 0:
        __bar_graph_high = high
    elif value > __bar_graph_high or now - __bar_graph_high_last > 10000:
        __bar_graph_high = value
        __bar_graph_high_last = now

    # normalize lack of data to 0..1
    epsilon = abs(7./3 - 4./3 - 1)
    if __bar_graph_high < 16 * epsilon:
        __bar_graph_high = 1

    # normalize value to 0..1
    v = value / __bar_graph_high
    dv = 1. / 16
    k = 0
    for y in range(4, -1, -1):
        for x in range(0, 3):
            if k > v:
                __unplot(2 - x, y)
                __unplot(2 + x, y)
            else:
                __plot(2 - x, y)
                __plot(2 + x, y)
            k += dv