AnyPointSystem (powered by Qiitadon) > 2019-05-01 UI > Bullet Journal (Habitat Tracker)表示 

```txt:動作環境
Ubuntu 18.04 LTs
Jupyter Notebook v5.7.8 
(Python 3.6.7 (default, Oct 22 2018, 11:32:17) )
```

### 関連

- [Qiitadon (Mastodon) > create_app()のto_fileで保存されるclient_id.txtの中身 | AnyPointSystem始めました](https://qiita.com/7of9/items/f8deafc9c301d9e01e2b)
- [AnyPointSystem (powered by Qiitadon) > 2019-04-27 システム設計](https://qiita.com/7of9/items/45aba268f6358541080c)

### 前準備

0. Mastodon.pyのセットアップ
1. アプリの登録 (Qiitadonの開発にて登録「AnyPointSystem」）
2. client_id.txtの生成 (client_idとclient_secret: 手順2の登録情報から)
3. access_token.txtの生成 (手順2の登録情報から)

### 概要

- Qiitadonをデータベースとするポイントシステム
- 今回
    - Bullet Journal表示

### Ballet Journal

- http://muyatan.hatenablog.com/entry/2017/01/31/192008
    - Habitat Tracker

を実装

### 参考

- [images_contours_and_fields example code: interpolation_methods.py](https://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html)


### 実装

```py:Qiitadon_API_190501.ipynb
import sys
from mastodon import Mastodon
import datetime
import csv
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
import japanize_matplotlib

# [ ] PEP8 checked

# 2019-05-01 10:24
# 2019-05-01 09:58
# 2019-05-01 09:55
# 2019-05-01 08:14
# 2019-04-29 08:49
# 2019-04-27 09:17

# reference
# for ballet journal display
#   https://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html

url = "https://qiitadon.com"
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# print(user_toots[0])  # トゥートを全て表示する

NUM_LOOP = 40  # for getting 40 toot each
#NUM_LOOP = 0 # for debug


def isAnyPointData(inlist):
    for elem in inlist:
        # print('***', elem)  # debug
        if 'anypointdata' in elem['name']:
            return True
    return False


def getPointTag(inlist):
    res = []
    for elem in inlist:
        if 'anypointdata' in elem['name']:
            continue
        res += [elem['name']]
    return res


max_id = None
fulllist = []

for loop in range(NUM_LOOP):
    user_dict = mastodon.account_verify_credentials()
    # 40 seems limit as of 2019-04-27
    user_toots = mastodon.account_statuses(user_dict['id'],
                                           max_id=max_id, limit=40)

    max_id = user_toots[-1]['id']
    # print(len(user_toots))
    for idx in range(len(user_toots)):
        # print(user_toots[idx])  # to print what the toot looks like
        atoot = user_toots[idx]['tags']
        atime = user_toots[idx]['created_at']

        # to avoid [TypeError: can't subtract offset-naive and offset-aware datetimes ]
        # https://stackoverflow.com/questions/796008/cant-subtract-offset-naive-and-offset-aware-datetimes
        atime = atime.replace(tzinfo=None)

        if len(atoot) == 0:
            continue
        if not isAnyPointData(atoot):
            continue
        res = getPointTag(atoot)
        fulllist += [[atime, res]]
        print('.', end='')
        # print(res)
        # print(atime)

# for debug
if NUM_LOOP == 0:
    fulllist = [
        [datetime.datetime(2019, 4, 30, 23, 8, 29, 983000), ['中国語学習']],
        [datetime.datetime(2019, 4, 30, 23, 1, 52, 859000), ['英語復習']],
        [datetime.datetime(2019, 4, 30, 13, 25, 46, 588000), ['中国語復習']],
        [datetime.datetime(2019, 4, 30, 13, 1, 31, 340000), ['ツール作成']],
        [datetime.datetime(2019, 4, 30, 12, 58, 27, 258000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 30, 9, 40, 31, 145000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 30, 9, 21, 52, 735000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 30, 9, 14, 35, 939000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 30, 9, 10, 58, 67000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 30, 2, 59, 50, 350000), ['英語復習']],
        [datetime.datetime(2019, 4, 29, 23, 32, 33, 842000), ['grenning学習', 'tdd学習']],
        [datetime.datetime(2019, 4, 29, 23, 27, 7, 216000), ['英語復習']],
        [datetime.datetime(2019, 4, 29, 13, 21, 5, 351000), ['英語学習']],
        [datetime.datetime(2019, 4, 29, 13, 5, 28, 552000), ['chinese']],
        [datetime.datetime(2019, 4, 29, 12, 30, 45, 331000), ['中国語学習']],
        [datetime.datetime(2019, 4, 29, 11, 57, 15, 318000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 29, 9, 51, 2, 24000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 29, 9, 33, 31, 33000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 29, 9, 32, 7, 586000), ['英語学習']],
        [datetime.datetime(2019, 4, 29, 9, 26, 30, 169000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 29, 9, 22, 31, 857000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 29, 1, 30, 51, 103000), ['ツール作成']],
        [datetime.datetime(2019, 4, 29, 1, 29, 41, 267000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 28, 23, 25, 35, 632000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 28, 23, 18, 31, 362000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 28, 23, 0, 21, 950000), ['英語復習']],
        [datetime.datetime(2019, 4, 28, 12, 36, 59, 80000), ['中国語復習']],
        [datetime.datetime(2019, 4, 28, 3, 34, 23, 115000), ['queendombyaurora']],
        [datetime.datetime(2019, 4, 28, 3, 30, 38, 750000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 28, 3, 29, 39, 44000), ['中国語学習']],
        [datetime.datetime(2019, 4, 28, 1, 18, 9, 823000), ['grenning学習', 'tdd学習']],
        [datetime.datetime(2019, 4, 28, 1, 17, 45, 883000), ['チョコ食べる']],
    ]

print()  # for new line

# add 1st tag
tagcsv = fulllist[0][1][0]  # 0: 1st toot, 1: tag, 0: expand from list
# print(tagcsv)
for elem in fulllist:
    # print(elem[1])
    atag = elem[1][0]
    if atag not in tagcsv:
        tagcsv += "," + str(elem[1][0])
taglist = tagcsv.split(",")

DAYS_PAST = 40
BASE_DATE = datetime.datetime(2019, 4, 1, 0, 0, 0)

def drawImage(ax, wrklist, atag):
    #print(wrklist)
    ary = np.array(wrklist)
    ary = np.reshape(ary, (1, -1))  # bar display
    #print(ary)
    #ax.imshow(ary, extent=(0, 40, 0, 1), cmap='viridis')
    ax.imshow(ary, cmap='viridis')
    ax.set_title(atag)
    
fig, ax = plt.subplots(20, 1, figsize=(12, 12),
                       subplot_kw={'xticks': [], 'yticks': []})
fig.subplots_adjust(hspace=0.9, wspace=0.05)

for idx, trgtag in enumerate(taglist):  # loop by target tag
    wrklist = [0 for i in range(DAYS_PAST)]
    # print(trgtag)
    for elem in fulllist:
        adate = elem[0]
        tags = elem[1]
        for atag in tags:
            if trgtag not in atag:
                continue
            elpsd = (int)((elem[0] - BASE_DATE).total_seconds() / 60 / 60 / 24)
            wrklist[elpsd] += 1
            # print(elpsd, adate, atag)
    # print(wrklist)
    drawImage(ax.flat[idx], wrklist, trgtag)

plt.show()
    
```

### 実行例

![Screenshot from 2019-05-01 11-00-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32870/e2670194-be6c-f548-baa9-c7416492ded6.png)


### 所感

続けたい習慣
可視化してみることで、続くだろう


## colormap付き

### 参考

- [matplotlib - カラーマップについて](http://pynote.hatenablog.com/entry/matplotlib-color)
    - nekobeanさん、情報感謝です

### 実装

colormapを簡易的に追加

```py:Qiitadon_API_190501.ipynb

import sys
from mastodon import Mastodon
import datetime
import csv
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
import japanize_matplotlib

# [ ] PEP8 checked

# 2019-05-01 11:16
# 2019-05-01 10:24
# 2019-05-01 09:58
# 2019-05-01 09:55
# 2019-05-01 08:14
# 2019-04-29 08:49
# 2019-04-27 09:17

# reference
# for ballet journal display
#   https://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html

url = "https://qiitadon.com"
cid_file = 'client_id.txt'
token_file = 'access_token.txt'

mastodon = Mastodon(
    client_id=cid_file,
    access_token=token_file,
    api_base_url=url
)

# print(user_toots[0])  # トゥートを全て表示する

#NUM_LOOP = 40  # for getting 40 toot each
NUM_LOOP = 0 # for debug


def isAnyPointData(inlist):
    for elem in inlist:
        # print('***', elem)  # debug
        if 'anypointdata' in elem['name']:
            return True
    return False


def getPointTag(inlist):
    res = []
    for elem in inlist:
        if 'anypointdata' in elem['name']:
            continue
        res += [elem['name']]
    return res


max_id = None
fulllist = []

for loop in range(NUM_LOOP):
    user_dict = mastodon.account_verify_credentials()
    # 40 seems limit as of 2019-04-27
    user_toots = mastodon.account_statuses(user_dict['id'],
                                           max_id=max_id, limit=40)

    max_id = user_toots[-1]['id']
    # print(len(user_toots))
    for idx in range(len(user_toots)):
        # print(user_toots[idx])  # to print what the toot looks like
        atoot = user_toots[idx]['tags']
        atime = user_toots[idx]['created_at']

        # to avoid [TypeError: can't subtract offset-naive and offset-aware datetimes ]
        # https://stackoverflow.com/questions/796008/cant-subtract-offset-naive-and-offset-aware-datetimes
        atime = atime.replace(tzinfo=None)

        if len(atoot) == 0:
            continue
        if not isAnyPointData(atoot):
            continue
        res = getPointTag(atoot)
        fulllist += [[atime, res]]
        print('.', end='')
        # print(res)
        # print(atime)

# for debug
if NUM_LOOP == 0:
    fulllist = [
        [datetime.datetime(2019, 4, 30, 23, 8, 29, 983000), ['中国語学習']],
        [datetime.datetime(2019, 4, 30, 23, 1, 52, 859000), ['英語復習']],
        [datetime.datetime(2019, 4, 30, 13, 25, 46, 588000), ['中国語復習']],
        [datetime.datetime(2019, 4, 30, 13, 1, 31, 340000), ['ツール作成']],
        [datetime.datetime(2019, 4, 30, 12, 58, 27, 258000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 30, 9, 40, 31, 145000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 30, 9, 21, 52, 735000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 30, 9, 14, 35, 939000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 30, 9, 10, 58, 67000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 30, 2, 59, 50, 350000), ['英語復習']],
        [datetime.datetime(2019, 4, 29, 23, 32, 33, 842000), ['grenning学習', 'tdd学習']],
        [datetime.datetime(2019, 4, 29, 23, 27, 7, 216000), ['英語復習']],
        [datetime.datetime(2019, 4, 29, 13, 21, 5, 351000), ['英語学習']],
        [datetime.datetime(2019, 4, 29, 13, 5, 28, 552000), ['chinese']],
        [datetime.datetime(2019, 4, 29, 12, 30, 45, 331000), ['中国語学習']],
        [datetime.datetime(2019, 4, 29, 11, 57, 15, 318000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 29, 9, 51, 2, 24000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 29, 9, 33, 31, 33000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 29, 9, 32, 7, 586000), ['英語学習']],
        [datetime.datetime(2019, 4, 29, 9, 26, 30, 169000), ['farbymarieueda']],
        [datetime.datetime(2019, 4, 29, 9, 22, 31, 857000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 29, 1, 30, 51, 103000), ['ツール作成']],
        [datetime.datetime(2019, 4, 29, 1, 29, 41, 267000), ['チカッとチカ千花っ']],
        [datetime.datetime(2019, 4, 28, 23, 25, 35, 632000), ['queendombyaudora']],
        [datetime.datetime(2019, 4, 28, 23, 18, 31, 362000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 28, 23, 0, 21, 950000), ['英語復習']],
        [datetime.datetime(2019, 4, 28, 12, 36, 59, 80000), ['中国語復習']],
        [datetime.datetime(2019, 4, 28, 3, 34, 23, 115000), ['queendombyaurora']],
        [datetime.datetime(2019, 4, 28, 3, 30, 38, 750000), ['animalbyaurora']],
        [datetime.datetime(2019, 4, 28, 3, 29, 39, 44000), ['中国語学習']],
        [datetime.datetime(2019, 4, 28, 1, 18, 9, 823000), ['grenning学習', 'tdd学習']],
        [datetime.datetime(2019, 4, 28, 1, 17, 45, 883000), ['チョコ食べる']],
    ]

print()  # for new line

# add 1st tag
tagcsv = fulllist[0][1][0]  # 0: 1st toot, 1: tag, 0: expand from list
# print(tagcsv)
for elem in fulllist:
    # print(elem[1])
    atag = elem[1][0]
    if atag not in tagcsv:
        tagcsv += "," + str(elem[1][0])
taglist = tagcsv.split(",")

DAYS_PAST = 40
BASE_DATE = datetime.datetime(2019, 4, 1, 0, 0, 0)
NUM_GRAPHS = 30
FIG_SIZE = figsize=(12,20)
NUM_COLORS = 5

def drawImage(ax, wrklist, atag):
    #print(wrklist)
    ary = np.array(wrklist)
    ary = np.reshape(ary, (1, -1))  # bar display
    #print(ary)
    #ax.imshow(ary, extent=(0, 40, 0, 1), cmap='viridis')
    #ax.imshow(ary, cmap='viridis')
    #ax.imshow(ary, cmap='viridis', vmin=0, vmax=NUM_COLORS)
    ax.imshow(ary, cmap='Paired', vmin=0, vmax=NUM_COLORS)
    ax.set_title(atag)
    
fig, ax = plt.subplots(NUM_GRAPHS, 1, figsize=FIG_SIZE,
                       subplot_kw={'xticks': [], 'yticks': []})
fig.subplots_adjust(hspace=0.9, wspace=0.05)

amax = 1
for idx, trgtag in enumerate(taglist):  # loop by target tag
    wrklist = [0 for i in range(DAYS_PAST)]
    # print(trgtag)
    for elem in fulllist:
        adate = elem[0]
        tags = elem[1]
        for atag in tags:
            if trgtag not in atag:
                continue
            elpsd = (int)((elem[0] - BASE_DATE).total_seconds() / 60 / 60 / 24)
            wrklist[elpsd] += 1
            # print(elpsd, adate, atag)
    # print(wrklist)
    if amax < max(wrklist):
        amax = max(wrklist)
    if idx < len(taglist) - 1:
        drawImage(ax.flat[idx], wrklist, trgtag)
    else:
        wrklist = range(NUM_COLORS)
        drawImage(ax.flat[idx], wrklist, 'colormap')
        

plt.show()
    
```

![Screenshot from 2019-05-01 11-52-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32870/0372e1ea-2f4d-9811-6300-7a4478023512.png)