Skip to content

EvansWinner/Piltdown

Repository files navigation

Piltdown Man

Piltdown

Generic badge

"Data visualiation for your tweets... making your mindless propaganda look more scientific since 2021!"

"We increased our conversion rate 549% with Piltdown!" -- Joe Blip, Fake Testimonials 'Я' Us

"Piltdown cured my son's cancer!" --Not the same guy as previous testimonial

OBLIGATORY SCREENSHOT

screenshot

YE OLDE TABLE OF CONTENTS

Introduction

or, What is it? and what's it for?

Piltdown is a tiny, nay, even trivial Python library that generates Unicode strings of bar charts and some other data visualization thingies which are meant to be included in your Twitter posts.

See the Documentation section below for examples.

What kind of charts, you ask? Also see the Documentation section for examples.

But see all the --

Caveats

I mean, they're pretty obvious.

  1. There is only so much you can do with nothing but text that is always left-justified, displayed in a very narrow column, and in a variable-width font--even with Unicode. If you think you can pull off a scatter plot with variable width Unicode in the space of a tweet, by all means, send me a PR.

  2. Screen readers. I don't know how many times I mention it below, but Unicode hacks are inherently unfriendly to the blind. Beware.

  3. You will see weird font compatibility issues. Github won't even render everything right every time in this README on Qutebrowser under Linux for me. Sometimes the column sparkline glyphs are weirdly off-center, sometimes the tally marks are missing.... Who knows? Some platform/font combinations render this stuff correctly, some don't. That's just all there is to it.

I wrote this for the fun of it, on a lark. Don't get all testy with me.

The Basic Idea

For most of the types of plot, you will specify your data set, (typically a list, list of tuples, dict, etc.). Generating this from a dataframe or other form is your problem--Piltdown just generates strings. That's all. There are a million utilities that could be included to post your string to Twitter for you, or to do other Unicode tricks, like bold or italics, but at least for the moment Piltdown wants to do one thing and do it (tollerably) well: generate strings that work as data viz in the space of a tweet. That means generally just the graphic elements and sometimes axis labels. Titles and keys are mostly your problem. But the idea is that all you have to do is concatenate any string you want with the output of your Piltdown graph, and thus becometh Robert your parent's brother.

Installation

Option 1

pip3 install piltdown

or your local equivalent (eg. pip3 install piltdown )

Option 2

Clone the repository, cd to your new piltdown directory, and then do --

pip install .

or your local equivalent.

Option 3

Just, like, load up the files any old way you want. It's pretty simple.

Anyway, if you do the pip thing, then import things as you need them as per the examples in, you guessed it, the Documentation section below.

Documentation by Example

Ye Olde Plots

In alphabetical order:

Column Sparklines

The Twitter plot that started it all! Lavishly praised by none other than the great Edward Tufte as "...better than nothing..."!

At any rate, I noticed them and that inspired the rest of this.

Example

Note that there are only 8 levels available, and all your numbers will be scaled based on your highest value. This isn't high-precision stuff here.

import piltdown.column_sparkline as cspar

# Data channeled by me from the spirit of an ancient cat.
print("Daily how much I like Twitter the last 10 days: " +
     cspar.column_sparkline([2,4,8,10,0,3,4,5,1,4]) +
     " on a scale of 1 to 10...\nso don't get your hopes up about another post.")
Daily how much I like Twitter the last 10 days: ▂▄▇█ ▃▄▄▁▄ on a scale of 1 to 10...
so don't get your hopes up about another post.

Comparison Charts

Comparison charts are, at least minimally, just tables with checkmarks and X's in the cells. See Tables. There is no Unicode fullwidth checkmark, so you need to use something else that is basically something in the ASCII subset that is represented by the Unicode fullwidth set. X's and O's or T's (for True) and F's (for False) are probably the best bets.

The same caveat applies. Column and row headers are best kept very short.

Example
import piltdown.table as tbl
import piltdown.literals as lit

# Data based on, well duh, obviously.
print("\n" +
    tbl.table([["",         "Com", "Ver", "Awe"],
               ["Mplib",    "T",   "T",   "F"],
               ["Ggplot2",  "T",   "T",   "F"],
               ["Piltdown", "F",   "F",   "T"]]
              ) +
                "\nMplib = Matplotlib\n" +
                "Com = Comprehensive; Ver = Well-verified; Awe = Awesome"
)
         Com Ver Awe
Mplib    T   T   F  
Ggplot2  T   T   F  
Piltdown F   F   T  

Mplib = Matplotlib
Com = Comprehensive; Ver = Well-verified; Awe = Awesome

Dot Charts

Example

Note that the number of characters used is Width x max height, because it has to be padded with whitespace characters. The one below, for example, is 10 high, plus one more for the label, which means 11 times 7 = 77 characters.

import piltdown.dot_chart as dc

# Data seems about right.
print("Number of Episodes of Animaniacs My Son Watched This Week\n" +
      dc.dot_chart([3, 2, 5, 0, 9, 4, 7],
                  ["M", "T", "W", "R", "F", "S", "U"]))
Number of Episodes of Animaniacs My Son Watched This Week

    *  
    *  
    * *
    * *
  * * *
  * ***
* * ***
*** ***
*** ***
MTWRFSU
Example

You can also use literals.DEFAULT_LABELS for labels and then include a key --

import piltdown.dot_chart as dc
import piltdown.literals as lit

# Now I'm just being mean...
print("Who Knocked On My Door Today?" +
      dc.dot_chart([3, 4, 1],
      lit.DEFAULT_LABELS) +
      "\nA = Jehovah's Witnesses; B = Mormons; C = Avon Lady"
     )
Who Knocked On My Door Today?
 * 
** 
** 
***
ABC

A = Jehovah's Witnesses; B = Mormons; C = Avon Lady

Dot Matrix Plot

See Waffle Charts.

Horizontal Bar Charts

Example

Basic bar chart. Bars will be scaled. The resolution is 1/8th of a character width.

import piltdown.hbar_chart as hbar
import piltdown.util as util
import piltdown.literals as lit

# Source: https://i.imgur.com/CswBFYU.jpg
print(util.bold("Which should cost less: a\ngallon of gas or a gallon of milk?\n\n") +
                hbar.hbar([43, 57], ["YES % ", "NO % "]))
𝐖𝐡𝐢𝐜𝐡 𝐬𝐡𝐨𝐮𝐥𝐝 𝐜𝐨𝐬𝐭 𝐥𝐞𝐬𝐬: 𝐚
𝐠𝐚𝐥𝐥𝐨𝐧 𝐨𝐟 𝐠𝐚𝐬 𝐨𝐫 𝐚 𝐠𝐚𝐥𝐥𝐨𝐧 𝐨𝐟 𝐦𝐢𝐥𝐤?

YES % █████████43
 NO % ████████████57
Example

If you omit the second, labels argument, lit.DEFAULT_LABELS will be used. You can also pass an explicit max_line_length argument to change the length of the longest line including label and the values printed at the end of each line. Default is currently 20.

If print_values is False then it will suppress those values.

import piltdown.hbar_chart as hbar
import piltdown.util as util

print(hbar.hbar([0.23,1.4,0.3,4], max_line_len=30))

print("-" * 20 + "\n")

print(hbar.hbar([1000,2000,3000],print_values=False))

print("-" * 20 + "\n")

print(util.bold("US Covid 19 Deaths by Month, Jul-Dec 2020") + "\n\n" +
      hbar.hbar([24863,30239,23336,23578,36596,57638],
                ["Jul","Aug","Sep","Oct","Nov","Dec"]) +
      "\nSource: https://bit.ly/3eYNVYw")

print("-" * 20 + "\n")

print(util.bold("COMPOSITION OF THE MOON % TAKEN FROM LUNAR SOIL") + "\n\n" +
      hbar.hbar([42,21,13,8,7,6,3],["O","Si","Fe","Ca","Al","Mg","X"]) +
      "\nX=Other; source: my son's t-shirt")
A█▌0.23
B████████▊1.4
C█▉0.3
D█████████████████████████4

--------------------

A█████
B██████████
C███████████████

--------------------

𝐔𝐒 𝐂𝐨𝐯𝐢𝐝 𝟏𝟗 𝐃𝐞𝐚𝐭𝐡𝐬 𝐛𝐲 𝐌𝐨𝐧𝐭𝐡, 𝐉𝐮𝐥-𝐃𝐞𝐜 𝟐𝟎𝟐𝟎

Jul█████▏24863
Aug██████▎30239
Sep████▉23336
Oct████▉23578
Nov███████▋36596
Dec████████████57638

Source: https://bit.ly/3eYNVYw
--------------------

𝐂𝐎𝐌𝐏𝐎𝐒𝐈𝐓𝐈𝐎𝐍 𝐎𝐅 𝐓𝐇𝐄 𝐌𝐎𝐎𝐍 % 𝐓𝐀𝐊𝐄𝐍 𝐅𝐑𝐎𝐌 𝐋𝐔𝐍𝐀𝐑 𝐒𝐎𝐈𝐋

 O████████████████42
Si████████21
Fe█████13
Ca███8
Al██▋7
Mg██▎6
 X█▏3

X=Other; source: my son's t-shirt

Horizontal Dot Charts

Not to be confused with a dot plot. This is the kind of thing they use on Khan Acadamy to teach small children about data. This is the horizontal version.

Example

Note Labels are automatically converted to the Unicode manthimatical "fullwidth" characters, which do not play nicely with screen readers. Beware the righteous wrath of the screen reader user.

import piltdown.hdot_chart as hdot

# Data completely made up and implausible.
print(
    "How many times I ate chocolate this week:\n\n"
    + hdot.hdot_chart(
        [3, 2, 4, 0, 2, 1, 4],
        ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
    )
)
How many times I ate chocolate this week:

Mo|⚫⚫⚫
Tu|⚫⚫
We|⚫⚫⚫⚫
Th|
Fr|⚫⚫
Sa|⚫
Su|⚫⚫⚫⚫

Scaled Up Numbers

High tech.

Example

Se literals.py for the font definitions. By all means, send me a PR with a better version, or additional fonts.

Would like to support all digits, the comma (,) the period (.) and the percent (%) sign at the least.

Use leading_pad to adjust left-padding to center your number if you want, but remember that will take up leading_pad times number of lines in the font of your alloted 280 tweet characters. So in the case below, the pad alone takes up 50 characters, and the total for the number below is therefore 85 (the default font is 3x5 chars plus an extra column blank between each number). The default padding is 4.

import piltdown.scaled_up_number as sun

# Can I get my nerd card punched now?
print(sun.scaled_up_number("42", leading_pad=10) +
     "\nMeaning of life, the Universe, and everything.")
          ░░▟ ▄▆▖
          ░▞█ ▘░▛
          ▟▄█ ░▞░
          ░░█ ▐░░
          ░░█ █▄▟

Meaning of life, the Universe, and everything.
Example

With this one, the number takes up 155 characters.

NOTE however, that on a cell phone I don't think you can count on lines of more than about 20 characters, so this would actually try to wrap, I believe. Maybe I should try it on Twitter and see.

import piltdown.scaled_up_number as sun

# Sorry.
print(sun.scaled_up_number("1,235.7%", leading_pad=0) +
     "\nYour Mom's age compared to mine, as a very precise percentage.")
 ░█░ ░░░ ▄▆▖ ▟▀▙ ▛▀▜ ░░░ ▛▀█ █░▞
 ▞█░ ░░░ ▘░▛ ░░█ ▌░░ ░░░ ░░▛ ░▐░
 ░█░ ░░░ ░▞░ ░█░ █▀▙ ░░░ ░▞░ ░▞░
 ░█░ ░░░ ▐░░ ░░█ ░░▐ ░░░ ▐░░ ░▌░
 ▗█▖ ░▜░ █▄▟ ▜▄▛ █▄▛ ░▖░ █░░ ▞░█

Your Mom's age compared to mine, as a very precise percentage.

Tables

Very small tables can be done using the util.fullwidth trick (see under Utilites below). Little two-way contingency tables might be done, for example.

Example

Note that you can use strings or floats/ints and they will converted to strings automatically.

Also note that an empty string will be interpreted as an empty cell.

At the moment that's all there is. No fancy cell/row/column borders or anything else.

import piltdown.table as tbl

# Data imaginatively cribed from the Wikipedia entry for "Contingency Table."
print("Handedness (L or R) vs. Sex (M or F) Contingency Table\n" +
    tbl.table([["",   "R", "L", "Tot"],
               ["M",   43,  9,   52],
               ["F",   44,  4,   48],
               ["Tot", 87,  13,  100]]
              )
)
Handedness (L or R) vs. Sex (M or F) Contingency Table
    R  L  Tot
M   43 9  52 
F   44 4  48 
Tot 87 13 100

Tally Charts

Another one they use to teach "data literacy" to kids, mostly, I think.

Example

Not super pretty, as the tally characters have slightly slanted lines. There might be a better way to do the onesie-twosie characters. Also, they don't seem to work in whatever font Github uses for this README.

Note that you want your labels to be all the same length. If that's not conventient, I recommend using A, B, C, etc. or something similar, and then including a key below your chart.

import piltdown.tally as tally

# Data completely made up. What kind of gluttons do you think we are?
print("How Many Chocolate Bars My Family Ate This Week\n\n" +
     tally.tally({"Mo": 5, "Tu": 11, "We": 3, "Th": 0, "Fr": 18, "Sa": 26, "Su": 17})
     )
How Many Chocolate Bars My Family Ate This Week

Mo¦ᚎ 
Tu¦ᚎ ᚎ 𝍩
We¦𝍫
Th¦
Fr¦ᚎ ᚎ ᚎ 𝍫
Sa¦ᚎ ᚎ ᚎ ᚎ ᚎ 𝍩
Su¦ᚎ ᚎ ᚎ 𝍪

Win Loss Sparklines

Tastes as good as it sounds!

Example

Note that you are basically just plotting signum of whatever numbers you give.

Also note that a zero plots as a thin line through the middle.

import piltdown.win_loss_sparkline as wl

# Actual data from my Garmin watch doodad.
print("My sleep time for the last 7 days, greater than or less than 8 hours: " +
     wl.win_loss_sparkline([2.3, -3, -0.1, 2, 0, 67]) +
     " rounded to nearest half hour." 
     )
My sleep time for the last 7 days, greater than or less than 8 hours: ▀▄▄▀-▀ rounded to nearest half hour.

Waffle Charts

Just at the moment you can select your own glyphs, or use literals.WAFFLES_COLOR (the default) or literals.WAFFLES_GRAYSCALE, or roll your own.

Note that I think these glyphs are being counted as two characters each, so that means a 10x10 waffle would cound as 210 characters on your limit (including the 10 newline characters). So... be knowing of that.

Alzo note The moiré effect of the grayscale glyphs is pretty terrible. Use the color ones if you can, or roll your own.

Alzo alzo note The layout argument is a tuple of two integers: preffered [width,height]. Zero means calculate based on the other value. Two zeros (the default) means try to get as close to a square as possible.

Waffles fill from top to bottom, first left-to-right, then right-to-left in alternating lines. I intend to write something that will let you fill from top to bottom, but writing the routine that rotates ragged two-dimensional lists is, you know, a job for a real programmer.

Also, at the moment some glyphs aren't the same size as others in some fonts, so a little testing on Twitter might be called for.

Example
import piltdown.waffle as wfl

# Completely real data.
print("Pets Owned by Aliens by %\n" + 
      wfl.waffle([16, 66, 18], ["Babel Fish", "Tribble", "Wookie"])
     )
Pets Owned by Aliens by %
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
🟥🟥🟥🟥🟥🟥🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟦🟦🟦🟦🟦🟦🟦🟦🟦🟦
🟨🟨🟨🟨🟨🟨🟨🟨🟦🟦
🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

🟥 = Babel Fish; 🟦 = Tribble; 🟨 = Wookie
Example

You can do "dot matrix plots" as well, using the literals.DOTMATRIX. Let's get real. It's basically just another kind of Waffle chart.

Observe that the routine that tries to make the result into a rectangle can only do so much if you give it oddly-lengthed data.

import piltdown.waffle as wfl
import piltdown.literals as lit

# Completely real data.
print("Proportion of different colored dots in the below self-referential plot:\n" + 
      wfl.waffle([13, 23, 18, 3], ["First color", "Second ", "Third", "Fourth"], glyphs = lit.DOTMATRIX)
     )
Proportion of different colored dots in the below self-referential plot:
🔴🔴🔴🔴🔴🔴🔴🔴
🔴🔴🔴🔴🔴🔵🔵🔵
🔵🔵🔵🔵🔵🔵🔵🔵
🔵🔵🔵🔵🔵🔵🔵🔵
🟠🟠🟠🟠🔵🔵🔵🔵
🟠🟠🟠🟠🟠🟠🟠🟠
🟡🟡🟠🟠🟠🟠🟠🟠
🟡

🔴 = First color; 🔵 = Second ; 🟠 = Third; 🟡 = Fourth

Utilities

Various little helpers which you may variously find helpful.

bold()

Fake bold using the Unicode mathematical bold characters for Latin and Greek capitals and miniscules, and Arabic digits. Yet again I warn you: screen readers might not like this.

You can include any other character you like (eg., punctuation characters) but they will simply be passed to the return string unchanged.

Example
import piltdown.util as util

print("This is a " + util.bold('very "important" thing to say!'))
This is a 𝐯𝐞𝐫𝐲 "𝐢𝐦𝐩𝐨𝐫𝐭𝐚𝐧𝐭" 𝐭𝐡𝐢𝐧𝐠 𝐭𝐨 𝐬𝐚𝐲!

fullwidth

Convert text to fake monospace using the Unicode "fullwidth" character set. Yet again I mention that this does not play well with screen readers. With this you have access to punctuation characters (unlike the monospace() function) but the tradeoff is that they are very wide, and it doesn't take many to go as wide as works on a Twitter on a cell phone.

Example
import piltdown.util as util

print(util.fullwidth("Witty example text."))
Witty example text.

monospace

Fake monospace with Unicode mathimatical monospace. The only characters, though, are Latin capitals and miniscules and Arabic digits. NOTE No punctuation! and no SPACE character! So, if you need those things, try fullwidth() which has them, but which is very wide, and for which therefore fewer characters fit in a tweet on a narrow cell phone screen.

Example
import piltdown.util as util

print(util.monospace("Foobar"))
𝙵𝚘𝚘𝚋𝚊𝚛

with_char_count

Use to wrap output. It will return a failure message if your string is longer than 280 characters.

OBVIOUS NOTE If you're going to use with_cut_lines or similar with this, put the call to that outside of the call to with_char_count, or else you'll end up counting the cut lines.

Misc Unicode thingies

See the bottom of piltdown/literals.py for some variables bound to various Unicode characters that might be useful, like Harvey balls, bullet points, and so forth.

import piltdown.util as util

print(util.with_char_count("This is short enough to be a tweet!"))

print(util.with_char_count("x" * 281))
This is short enough to be a tweet!
String too long to post on Twitter

Hacking

Project goals

(Other than the lulz)

  • Simple
  • Really simple
  • Simple enough to just write static documentation-by-examples here in the README
  • Simple enough that the examples in the documentation here can serve as a rudimentary test suite
  • (Mostly) don't try to do anything but create the charts and graphs. Just functions that return strings. Leave Twitter posting, statistical preprocessing, and mixing output with other strings (titles, general writing, etc.) to the user/other libraries.
  • Easy to use.
  • Portable with no dependencies other than basic Python 3
  • Just focused on the Twitter use case. Not a general solution to the Grand ASCII-art Data Viz Problem, which is never-ending.

And also

The README is generated from this Jupyter notebook. Don't forget to re-export it to README.md if you make changes.

TODO

  • Instead of telling people to have labels all the same length (eg. horizontal dot chart) just pad to max(len(...))
  • Better examples. Better... or at least funnier.

A list of plots I got somewhere that looked at a quick glance like they might be possible. Not to be interpreted as firm intentions. More or less in descending order of priority.

  • bullet diagram
  • stem and leaf
  • heatmap
  • boxplot
  • stacked bar
  • grouped barchart
  • pictorial unit -- basically allow custom characters for the dot chart, one per line

Credits

In the immortal words of Olin Shivers: I did it. I did it all, by myself.

Final words

This yak... is shaved.

About

Unicode data viz for Twitter posts.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published