Microcharts on OLED displays using a Raspberry Pi

When you think of microcharts, you probably think of something like these:

Microcharts from amcharts.com

This is fine if you want to display it on a traditional screen, but what if you don’t want it to take real estate on your computer screen or cellphone? What if you want something nice and bright and readily available?

Enter the OLED microchart powered by a Raspberry Pi and a cheap OLED display. You can get the latter from Ebay for around USD$7.

For this project I used a Raspberry Pi Zero W which is $10 at Microcenter (sometimes they have them on sale for $5).

When we’re done, this is how it’s going to look like:

OLED Microchart

The Hardware

The OLED display connects to the Raspberry Pi via 4 wires: VCC, GND, SCL, SDA. I followed this tutorial to get up and running. It is dead simple.

Workflow

The workflow goes like this:

  1. Draw Title
  2. Read data
  3. Normalize Data
  4. Draw each bar

The Code

After getting up and running, I decided to start modifying one of the examples that came with the Adafruit Python SSD1306 package.

import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
import pandas as pd
import numpy as np

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

# Raspberry Pi pin configuration:
RST = None     # on the PiOLED this pin isnt used

# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Initialize library.
disp.begin()

# Clear display.
disp.clear()
disp.display()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = -2
top = padding
bottom = height-padding
top_row_size = 16
# Move left to right keeping track of the current x position for drawing shapes.
x = 0

# draw device name
def draw_title(text):
    draw.rectangle([0,top,width,top_row_size], outline=0, fill=0)
    font = ImageFont.truetype('WorkSans-Regular.ttf',size=top_row_size)
    draw.text((x, top), text,font=font, fill=255)
    # Display image.
    disp.image(image)
    disp.display()

def draw_chart(data, type='bar'):
    if type == 'bar':

        # get max and min values so we can normalize later
        max_y = data['y'].max()
        data['y'] = np.ceil(data['y'] / max_y * (height - top_row_size)).astype(int) # round up

        padding_x = 1
        bar_width = int(width / len(data.index)) - padding_x
        start_x = 0 # starting position from left

        for index, row in data.iterrows():
            print("Drawing " + str(row['x']) + ',' + str(row['y']))
            start_y = height-row['y'] # we are already considering the top row height in the normalization
            stop_x = bar_width + start_x
            stop_y = height
            draw.rectangle([start_x,start_y,stop_x,stop_y], outline=0, fill=255)
            start_x = start_x + bar_width + padding_x

        # Display image.
        disp.image(image)
        disp.display()


draw_title("Bar Chart") # Draw the title on the display
data = pd.read_csv("data.csv") # load the data into a pandas dataframe
draw_chart(data) # draw the data

Adding MicroStrategy support

We’ll start by installing the mstrio-py package from MicroStrategy.

pip3 install mstrio-py

We will add a few lines of code. Mainly we are going to connect to MicroStrategy and read a report. Look at lines:

  • 11 : Importing the MicroStrategy Package
  • 13-18: MicroStrategy Library API URL
  • 61: Clear the chart area
  • 86-87: Connect to MicroStrategy
  • 93: Get the data
  • 95: Wait

The Code

import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
import pandas as pd
import numpy as np

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from time import sleep
from mstrio import microstrategy

# MicroStrategy Info
mstrUrl = 'https://URL_AND_PORT/MicroStrategyLibrary/api'
userid = 'username'
password = 'password'
projectName = 'project name'
reportId = 'report id'

# Raspberry Pi pin configuration:
RST = None     # on the PiOLED this pin isnt used

# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Initialize library.
disp.begin()

# Clear display.
disp.clear()
disp.display()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = -2
top = padding
bottom = height-padding
top_row_size = 16
# Move left to right keeping track of the current x position for drawing shapes.
x = 0

# draw device name
def draw_title(text):
    draw.rectangle([0,top,width,top_row_size], outline=0, fill=0)
    font = ImageFont.truetype('WorkSans-Regular.ttf',size=top_row_size)
    draw.text((x, top), text,font=font, fill=255)
    # Display image.
    disp.image(image)
    disp.display()

def draw_chart(data, type='bar'):
    draw.rectangle([0,top_row_size,width,height], outline=0, fill=0) # clear the bar area
    if type == 'bar':

        # get max and min values so we can normalize later
        max_y = data['y'].max()
        data['y'] = np.ceil(data['y'] / max_y * (height - top_row_size)).astype(int) # round up

        padding_x = 1
        bar_width = int(width / len(data.index)) - padding_x
        start_x = 0 # starting position from left

        for index, row in data.iterrows():
            print("Drawing " + str(row['x']) + ',' + str(row['y']))
            start_y = height-row['y'] # we are already considering the top row height in the normalization
            stop_x = bar_width + start_x
            stop_y = height
            draw.rectangle([start_x,start_y,stop_x,stop_y], outline=0, fill=255)
            start_x = start_x + bar_width + padding_x

        # Display image.
        disp.image(image)
        disp.display()



conn = microstrategy.Connection(base_url=mstrUrl, username=userid, password=password, project_name=projectName, login_mode=16, ssl_verify=False)
conn.connect()

draw_title("Bar Chart")

while True:
    # read the report data
    data = conn.get_report(report_id=reportId)
    draw_chart(data)
    sleep(5)

The chart will now update every 5 seconds with whatever data is in MicroStrategy.

Animated Microchart

Conclusion

This is an awesome project that shows you how to draw analytics outside of traditional web or app portals. Intelligence is all around us. There are awesome and creative ways to expose analytics to everyone. IOT devices like this allow us to be better informed at a faster pace. What do you think? Think you can do better? I’d like to see what you come up with. Send me your ideas and we can discuss!

Things to think about

  • Maybe a line chart?
  • Maybe reading directly from a MicroStrategy Cube or Dossier?
    • Or maybe something completely different?
  • How would you refresh individual data points instead of the whole screen?

Leave a Reply

Your email address will not be published.