1
+ import numpy as np
2
+ import pandas as pd
3
+ import matplotlib .pyplot as plt
4
+ import matplotlib .animation as animation
5
+ import streamlit as st
6
+ import streamlit .components .v1 as components
7
+ import bar_chart_race as bcr
8
+ from PIL import Image
9
+ from io import StringIO
10
+ from datetime import datetime
11
+ from collections import defaultdict
12
+ import japanize_matplotlib
13
+ import plotly .graph_objects as go
14
+
15
+ # ロゴ
16
+ logo_image = Image .open ('src/logo.png' )
17
+ st .image (logo_image )
18
+
19
+ # メイン画像
20
+ main_image = Image .open ('src/mainimg.png' )
21
+ st .image (main_image )
22
+
23
+ """
24
+ # 遊び方
25
+
26
+ 以下の手順で、誰でも簡単に**バーチャートレース(ぬるぬる動くグラフ)**を作ることができます。
27
+
28
+ 1. LINEのトーク履歴をtxtファイル形式で保存する(所要時間:1分)<方法は[コチラ]("https://appllio.com/line-talk-history-send-mail")>
29
+ 2. 保存したtxtファイルをアップロードする(所要時間:30秒)
30
+ 3. お好みでカスタマイズして完成!(所要時間:30秒)
31
+
32
+ # さっそく遊んでみる
33
+ LINEトーク履歴(txtファイル)を選択してください
34
+ """
35
+ uploaded_file = st .file_uploader ("""※期間が1年以上になると動画処理が終わらない可能性があります""" ,type = "txt" ,)
36
+ st .markdown ("**(画面左上からサイドバーを開くと自由にカスタマイズできます)**" )
37
+ """
38
+ ---
39
+ """
40
+
41
+
42
+ #####サイドバー#####
43
+ st .sidebar .markdown ("# ⚙️カスタマイズオプション" )
44
+
45
+ st .sidebar .markdown ("### 【データフレーム】" )
46
+ df_category = st .sidebar .radio ("データフレームの種類を選択してください" , ('標準' , '累積和' ,'標準<markdown>' , '累積和<markdown>' ))
47
+
48
+ st .sidebar .markdown ("### 【折れ線グラフ】" )
49
+ line_title = st .sidebar .text_input ('表示タイトル' , 'グループチャット発言回数の推移' )
50
+ line_category = st .sidebar .radio ("折れ線グラフの種類を選択してください" , ('累積和' ,'標準' ))
51
+
52
+ st .sidebar .markdown ("### 【ヒートマップ】" )
53
+ heat_title = st .sidebar .text_input ('表示タイトル' , 'グループチャット発言回数' )
54
+ heat_colorscale = st .sidebar .radio ("カラースケールを選択してください" , ('Defalut' ,'Blackbody' ,'Bluered' ,'Blues' ,'Earth' ,'Electric' ,'Greens' ,'Greys' ,'Hot' ,'Jet' ,'Picnic' ,'Portland' ,'Rainbow' ,'RdBu' ,'Reds' ,'Viridis' ,'YlGnBu' ,'YlOrRd' ))
55
+ st .sidebar .markdown ("### 【バーチャートレース】" )
56
+ bcr_title = st .sidebar .text_input ('表示タイトル' , 'グループチャット発言回数ランキング' )
57
+ n_bars = st .sidebar .slider ('ランキング上位表示人数' , min_value = 1 ,max_value = 20 ,value = 2 )
58
+ st .sidebar .write ('↪' ,n_bars , '人' )
59
+ from_date = str (st .sidebar .date_input ('表示期間(開始)' ,value = datetime (2000 ,1 ,1 )))
60
+ to_date = str (st .sidebar .date_input ('表示期間(終了)' ,value = datetime (2100 ,12 ,31 )))
61
+ steps_per_period = st .sidebar .slider ('ピリオド毎のステップ数' , min_value = 1 ,max_value = 50 ,value = 10 )
62
+ st .sidebar .write ('↪' ,steps_per_period )
63
+ period_length = st .sidebar .slider ('1ピリオドの長さ' , min_value = 100 ,max_value = 1000 ,value = 500 )
64
+ st .sidebar .write ('↪' ,period_length )
65
+ #####サイドバー#####
66
+
67
+
68
+ if uploaded_file is not None : # ファイルがアップロードされた場合
69
+
70
+ bytes_data = uploaded_file .getvalue ()
71
+ stringio = StringIO (uploaded_file .getvalue ().decode ("utf-8" ))
72
+ tmp_names = defaultdict (int ) # グループメンバーの名前を格納するリスト
73
+ stringio_list = []
74
+ # グループの人数をカウント
75
+ for i ,data in enumerate (stringio ):
76
+ data_list = list (map (str ,data .split ())) # txtファイルの各行のデータをリスト化
77
+ stringio_list .append (data_list )
78
+ if i < 2 : #2行目までのタイトルと保存日時はスキップ
79
+ continue
80
+ if len (data_list ) <= 2 : # 空白行&日付行はスキップ
81
+ continue
82
+ name = data_list [1 ]
83
+ if not data_list [0 ][0 ].isdigit () or name in ['Group' ,'You' ,'☎' ]: # システムメッセージ等を除外する
84
+ continue
85
+ tmp_names [name ] += 1 # メンバーリストに追加
86
+ names = [key for key in tmp_names .keys () if tmp_names [key ] >= 5 ] # 発言回数5回以上のメンバーだけ残す
87
+ chat_count = [] # 日付ごとの発言カウントを格納するリスト
88
+ daily_data = [] # 1日のデータを格納するリスト
89
+ for i ,data_list in enumerate (stringio_list ):
90
+
91
+ if i < 2 : #2行目までのタイトルと保存日時はスキップ
92
+ continue
93
+
94
+ if len (data_list ) < 1 : # 空白行はスキップ
95
+ continue
96
+
97
+ if len (data_list [0 ])>= 10 and data_list [0 ][4 ]== '/' and data_list [0 ][7 ]== '/' : # 日付の行
98
+ if daily_data :
99
+ if daily_data [0 ] <= to_date : # 表示期間以内
100
+ chat_count .append (daily_data ) # 日付の行が来たタイミングで先日の発言回数をchat_countリストに追加
101
+ else : # 表示期間外
102
+ break
103
+
104
+ date = data_list [0 ].replace ('/' ,'-' )[:10 ] # 2020/01/01 ---> 2020-01-01 (日付表示を変更)
105
+ if from_date <= date : # 表示期間内
106
+ daily_data = [date ]+ [0 ]* (len (names )) # その日のデータを格納するリストを用意 ['2020-01-01',0,0,0,...]
107
+ continue
108
+ else :
109
+ daily_data = None
110
+
111
+ if len (data_list ) >= 3 :
112
+ name = data_list [1 ]
113
+ if name in names and daily_data : # 発言表示の行の場合
114
+ daily_data [names .index (name )+ 1 ] += 1 # 発言者ごとの発言数をインクリメントする
115
+ if daily_data and daily_data [0 ] <= to_date :
116
+ chat_count .append (daily_data )
117
+ chat_count = np .array (chat_count ) # 発言カウントリストをnumpy配列に変換
118
+ original_df = pd .DataFrame (chat_count )
119
+ original_df .columns = ['日付' ] + names # 列インデックスに氏名を指定
120
+ original_df = original_df .set_index ('日付' ) # 行インデックスに日付を指定
121
+ original_df = original_df .astype (dict (zip (names ,['int64' ]* len (names )))) # カウントした発言数を整数(int64)型に変換
122
+ chat_count [:,1 :len (names )+ 1 ] = np .cumsum (np .array (chat_count [:,1 :len (names )+ 1 ],dtype = int ),axis = 0 ) # 日付以外の列に関して、縦方向に累積和を取る
123
+ df = pd .DataFrame (chat_count )
124
+ df .columns = ['日付' ] + names # 列インデックスに氏名を指定
125
+ df = df .set_index ('日付' ) # 行インデックスに日付を指定
126
+ df = df .astype (dict (zip (names ,['int64' ]* len (names )))) # カウントした発言数を整数(int64)型に変換
127
+
128
+ # データフレーム
129
+ st .write (f'## データフレーム({ df_category } )' )
130
+ if df_category == '標準' :
131
+ st .write (original_df )
132
+ elif df_category == '累積和' :
133
+ st .write (df )
134
+ elif df_category == '標準<markdown>' :
135
+ st .markdown (original_df .to_markdown ())
136
+ else :
137
+ st .markdown (df .to_markdown ())
138
+
139
+ # 折れ線グラフ
140
+ st .write (f'## 折れ線グラフ({ line_category } )' )
141
+ pd .options .plotting .backend = "plotly"
142
+ if line_category == "標準" :
143
+ fig_line = original_df .plot (title = line_title , template = "simple_white" ,
144
+ labels = dict (index = "日付" , value = "回数" , variable = "メンバー" ))
145
+ else :
146
+ fig_line = df .plot (title = line_title , template = "simple_white" ,
147
+ labels = dict (index = "日付" , value = "回数" , variable = "メンバー" ))
148
+ st .write (fig_line )
149
+
150
+ # ヒートマップ
151
+ st .write ('## ヒートマップ' )
152
+ if heat_colorscale == 'Defalut' :
153
+ heat_colorscale = None
154
+ fig_heat = go .Figure (data = go .Heatmap (
155
+ z = original_df ,
156
+ x = names ,
157
+ y = list (df .index ),
158
+ colorbar = dict (title = '回数' ),
159
+ colorscale = heat_colorscale ,
160
+ hoverongaps = True )
161
+ )
162
+ fig_heat .update_xaxes (title = "メンバー" )
163
+ fig_heat .update_yaxes (title = "日付" )
164
+ fig_heat .update_layout (title = heat_title )
165
+ st .write (fig_heat )
166
+
167
+ # バーチャートレース
168
+ st .write ('## バーチャートレース' )
169
+ html = bcr .bar_chart_race (df ,title = bcr_title ,n_bars = n_bars ,figsize = (4 ,3 ),steps_per_period = steps_per_period ,period_length = period_length )
170
+ components .html (html ._repr_html_ (),width = 10000 ,height = 7500 )
171
+
172
+ # 注意事項
173
+ st .markdown ("※本サービスは、アップロードされたLINEトーク履歴(個人情報を含む)を使用して処理を行います。アップロードされた情報は保存されることなく処理が終了した時点で破棄されますが、心配な方は利用を控えてください。" )
0 commit comments