Created
June 5, 2016 10:21
-
-
Save akorotkov/2c7011ac30de27b9c5631f1bf418f0a1 to your computer and use it in GitHub Desktop.
Draw psql output as iTerm2 v3 inline graph using matplotlib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Draw psql output as iTerm2 v3 inline graph using matplotlib | |
# Author: Alexander Korotkov <a.korotkov@postgrespro.ru> | |
import sys | |
import re | |
import warnings | |
import matplotlib | |
matplotlib.use("Agg") | |
import matplotlib.pyplot as plt | |
import itertools | |
import numpy as np | |
import StringIO | |
import base64 | |
import os | |
# TODO: | |
# * handle newlines in data | |
# * support more output format variations: line styles, spaces etc. | |
# Couple of escape sequences printing functions | |
def print_osc(): | |
if os.environ.get('TERM') == 'screen': | |
sys.stdout.write("\033Ptmux;\033\033]") | |
else: | |
sys.stdout.write("\033]") | |
def print_st(): | |
if os.environ.get('TERM') == 'screen': | |
sys.stdout.write("\a\033\\") | |
else: | |
sys.stdout.write("\a") | |
enc = 'utf-8' | |
# Pattern for record header in extended format | |
extRecPattern = re.compile(u'^([─-]\[ RECORD \d+ \](?:-*|─*))(?:\+-*|[┬┼]─*)$') | |
data = [] | |
colors = itertools.cycle(['b', 'g', 'r', 'c', 'm', 'y', 'k']) | |
# Read table from psql | |
header = sys.stdin.readline().decode(enc) | |
m = extRecPattern.match(header) | |
if m: | |
# Extended format | |
nameLen = len(m.group(1)) - 1 | |
valueOffset = len(m.group(1)) + 2 | |
i = 0 | |
colNames = [] | |
row = [] | |
line = sys.stdin.readline().decode(enc) | |
while line: | |
if not extRecPattern.match(line): | |
name = line[:nameLen].rstrip(' ') | |
value = line[valueOffset:].rstrip('\n') | |
if i == 0: | |
colNames.append(name) | |
row.append(value) | |
else: | |
i = i + 1 | |
data.append(row) | |
row = [] | |
line = sys.stdin.readline().decode(enc) | |
data.append(row) | |
else: | |
# Basic format | |
colNames = [name.strip(' ') for name in re.split(u'[|│]', header.rstrip('\n'))] | |
line = sys.stdin.readline().decode(enc) | |
while line: | |
parts = re.split(u'[|│]', line.rstrip('\n')) | |
if len(parts) == len(colNames): | |
row = [value.strip(' ') for value in parts] | |
data.append(row) | |
line = sys.stdin.readline().decode(enc) | |
# Setup graph colored white on black | |
fig = plt.figure(figsize = (8, 6)) | |
ax = fig.add_subplot(1, 1, 1, axisbg = 'k') | |
ax.title.set_color('white') | |
ax.yaxis.label.set_color('white') | |
ax.xaxis.label.set_color('white') | |
ax.tick_params(axis = 'x', colors = 'white') | |
ax.tick_params(axis = 'y', colors = 'white') | |
# Autodetect plot type. Use plot if all the labels are floats. | |
x = [] | |
try: | |
for row in data: | |
x.append(float(row[0])) | |
chartType = 'plot' | |
except ValueError: | |
chartType = 'bar' | |
if chartType == 'bar': | |
for row in data: | |
x.append(row[0]) | |
index = np.arange(len(data)) | |
barWidth = 1.0 / float(len(colNames)) | |
# Do draw | |
for i in range(1, len(colNames)): | |
color = colors.next() | |
y = [] | |
for row in data: | |
y.append(float(row[i])) | |
if chartType == 'plot': | |
ax.plot(x, y, label=colNames[i], color = color) | |
if chartType == 'bar': | |
ax.bar(index + barWidth * (float(i) - 0.5), | |
y, | |
barWidth, | |
label = colNames[i], | |
color = color) | |
if chartType == 'bar': | |
plt.xticks(index + 0.5, x) | |
# Setup legend | |
legend = ax.legend(loc = 'best', fancybox = True, framealpha = 0.5) | |
legendFrame = legend.get_frame() | |
legendFrame.set_color('white') | |
legendFrame.set_facecolor('black') | |
legendFrame.set_edgecolor('white') | |
for text in legend.get_texts(): | |
text.set_color('white') | |
plt.xlabel(colNames[0]) | |
ax.grid(True, color = 'w') | |
for spine in ax.spines: | |
ax.spines[spine].set_edgecolor('white') | |
# Supress warnings in tight_layout | |
with warnings.catch_warnings(): | |
warnings.simplefilter("ignore") | |
plt.tight_layout() | |
# Put inlined image into iTerm2 v3 | |
imgdata = StringIO.StringIO() | |
plt.savefig(imgdata, format = 'png', transparent = True) | |
size = imgdata.tell() | |
imgdata.seek(0) # rewind the data | |
print_osc() | |
sys.stdout.write('1337;File=') | |
sys.stdout.write('name=' + base64.b64encode('graph.png') + ';') | |
sys.stdout.write('size=' + str('size') + ';') | |
sys.stdout.write('inline=1:') | |
sys.stdout.write(base64.b64encode(imgdata.buf)) | |
print_st() | |
sys.stdout.write("\n") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment