better-git-of-theseus 0.4.5__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/PKG-INFO +5 -2
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/README.md +3 -1
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/PKG-INFO +5 -2
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/requires.txt +1 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/cmd.py +1 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/plotly_plots.py +58 -130
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/setup.py +3 -2
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/LICENSE +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/SOURCES.txt +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/dependency_links.txt +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/entry_points.txt +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/top_level.txt +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/__init__.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/analyze.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/app.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/line_plot.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/stack_plot.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/survival_plot.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/git_of_theseus/utils.py +0 -0
- {better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better-git-of-theseus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Plot stats on Git repositories with interactive Plotly charts
|
|
5
5
|
Home-page: https://github.com/onewesong/better-git-of-theseus
|
|
6
6
|
Author: Erik Bernhardsson
|
|
@@ -16,6 +16,7 @@ Requires-Dist: plotly
|
|
|
16
16
|
Requires-Dist: streamlit
|
|
17
17
|
Requires-Dist: python-dateutil
|
|
18
18
|
Requires-Dist: scipy
|
|
19
|
+
Requires-Dist: matplotlib
|
|
19
20
|
Dynamic: author
|
|
20
21
|
Dynamic: author-email
|
|
21
22
|
Dynamic: description
|
|
@@ -34,6 +35,7 @@ Dynamic: summary
|
|
|
34
35
|
[](https://pypi.org/project/better-git-of-theseus/)
|
|
35
36
|
[](https://github.com/onewesong/better-git-of-theseus/blob/master/LICENSE)
|
|
36
37
|
[](https://deepwiki.com/onewesong/better-git-of-theseus)
|
|
38
|
+
<a href="https://llmapis.com?source=https%3A%2F%2Fgithub.com%2Fonewesong%2Fbetter-git-of-theseus" target="_blank"><img src="https://llmapis.com/api/badge/onewesong/better-git-of-theseus" alt="LLMAPIS" width="20" /></a>
|
|
37
39
|
|
|
38
40
|
[中文版](README_zh.md)
|
|
39
41
|
|
|
@@ -41,7 +43,8 @@ Dynamic: summary
|
|
|
41
43
|
|
|
42
44
|
**Better Git of Theseus** is a modern refactor of the original [git-of-theseus](https://github.com/erikbern/git-of-theseus). It provides a fully interactive Web Dashboard powered by **Streamlit** and **Plotly**, making it easier than ever to visualize how your code evolves over time.
|
|
43
45
|
|
|
44
|
-

|
|
47
|
+

|
|
45
48
|
|
|
46
49
|
## Key Enhancements
|
|
47
50
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://pypi.org/project/better-git-of-theseus/)
|
|
8
8
|
[](https://github.com/onewesong/better-git-of-theseus/blob/master/LICENSE)
|
|
9
9
|
[](https://deepwiki.com/onewesong/better-git-of-theseus)
|
|
10
|
+
<a href="https://llmapis.com?source=https%3A%2F%2Fgithub.com%2Fonewesong%2Fbetter-git-of-theseus" target="_blank"><img src="https://llmapis.com/api/badge/onewesong/better-git-of-theseus" alt="LLMAPIS" width="20" /></a>
|
|
10
11
|
|
|
11
12
|
[中文版](README_zh.md)
|
|
12
13
|
|
|
@@ -14,7 +15,8 @@
|
|
|
14
15
|
|
|
15
16
|
**Better Git of Theseus** is a modern refactor of the original [git-of-theseus](https://github.com/erikbern/git-of-theseus). It provides a fully interactive Web Dashboard powered by **Streamlit** and **Plotly**, making it easier than ever to visualize how your code evolves over time.
|
|
16
17
|
|
|
17
|
-

|
|
19
|
+

|
|
18
20
|
|
|
19
21
|
## Key Enhancements
|
|
20
22
|
|
{better_git_of_theseus-0.4.5 → better_git_of_theseus-0.5.1}/better_git_of_theseus.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better-git-of-theseus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Plot stats on Git repositories with interactive Plotly charts
|
|
5
5
|
Home-page: https://github.com/onewesong/better-git-of-theseus
|
|
6
6
|
Author: Erik Bernhardsson
|
|
@@ -16,6 +16,7 @@ Requires-Dist: plotly
|
|
|
16
16
|
Requires-Dist: streamlit
|
|
17
17
|
Requires-Dist: python-dateutil
|
|
18
18
|
Requires-Dist: scipy
|
|
19
|
+
Requires-Dist: matplotlib
|
|
19
20
|
Dynamic: author
|
|
20
21
|
Dynamic: author-email
|
|
21
22
|
Dynamic: description
|
|
@@ -34,6 +35,7 @@ Dynamic: summary
|
|
|
34
35
|
[](https://pypi.org/project/better-git-of-theseus/)
|
|
35
36
|
[](https://github.com/onewesong/better-git-of-theseus/blob/master/LICENSE)
|
|
36
37
|
[](https://deepwiki.com/onewesong/better-git-of-theseus)
|
|
38
|
+
<a href="https://llmapis.com?source=https%3A%2F%2Fgithub.com%2Fonewesong%2Fbetter-git-of-theseus" target="_blank"><img src="https://llmapis.com/api/badge/onewesong/better-git-of-theseus" alt="LLMAPIS" width="20" /></a>
|
|
37
39
|
|
|
38
40
|
[中文版](README_zh.md)
|
|
39
41
|
|
|
@@ -41,7 +43,8 @@ Dynamic: summary
|
|
|
41
43
|
|
|
42
44
|
**Better Git of Theseus** is a modern refactor of the original [git-of-theseus](https://github.com/erikbern/git-of-theseus). It provides a fully interactive Web Dashboard powered by **Streamlit** and **Plotly**, making it easier than ever to visualize how your code evolves over time.
|
|
43
45
|
|
|
44
|
-

|
|
47
|
+

|
|
45
48
|
|
|
46
49
|
## Key Enhancements
|
|
47
50
|
|
|
@@ -7,12 +7,17 @@ import math
|
|
|
7
7
|
import os
|
|
8
8
|
from .utils import generate_n_colors
|
|
9
9
|
|
|
10
|
+
# Harmonious, professional color palette (Modern & Muted)
|
|
11
|
+
# Inspired by Tableau 20 and modern UI systems
|
|
12
|
+
PREMIUM_PALETTE = [
|
|
13
|
+
"#4E79A7", "#A0CBE8", "#F28E2B", "#FFBE7D", "#59A14F",
|
|
14
|
+
"#8CD17D", "#B6992D", "#F1CE63", "#499894", "#86BCB6",
|
|
15
|
+
"#E15759", "#FF9D9A", "#79706E", "#BAB0AC", "#D37295",
|
|
16
|
+
"#FABFD2", "#B07AA1", "#D4A1D2", "#9D7660", "#D7B5A6"
|
|
17
|
+
]
|
|
18
|
+
|
|
10
19
|
def _process_stack_line_data(data, max_n=20, normalize=False):
|
|
11
|
-
# Handle dict or file path
|
|
12
|
-
# If it's a file path, load it? But app.py passes dict now.
|
|
13
|
-
# Let's assume dict for now as per app.py refactor.
|
|
14
20
|
if not isinstance(data, dict):
|
|
15
|
-
# Fallback if needed, though app.py sends dict
|
|
16
21
|
import json
|
|
17
22
|
data = json.load(open(data))
|
|
18
23
|
|
|
@@ -20,34 +25,16 @@ def _process_stack_line_data(data, max_n=20, normalize=False):
|
|
|
20
25
|
labels = data["labels"]
|
|
21
26
|
ts = [dateutil.parser.parse(t) for t in data["ts"]]
|
|
22
27
|
|
|
23
|
-
# Sort and filter top N
|
|
24
28
|
if y.shape[0] > max_n:
|
|
25
|
-
# Sort by max value in the series
|
|
26
29
|
js = sorted(range(len(labels)), key=lambda j: max(y[j]), reverse=True)
|
|
27
|
-
|
|
28
|
-
# Calculate other sum
|
|
29
30
|
other_indices = js[max_n:]
|
|
30
31
|
if other_indices:
|
|
31
32
|
other_sum = np.sum([y[j] for j in other_indices], axis=0)
|
|
32
|
-
|
|
33
|
-
# Top N indices
|
|
34
33
|
top_js = sorted(js[:max_n], key=lambda j: labels[j])
|
|
35
|
-
|
|
36
34
|
y = np.array([y[j] for j in top_js] + [other_sum])
|
|
37
35
|
labels = [labels[j] for j in top_js] + ["other"]
|
|
38
|
-
else:
|
|
39
|
-
# Should hopefully not happen if shape[0] > max_n
|
|
40
|
-
pass
|
|
41
|
-
else:
|
|
42
|
-
# Sort alphabetically for consistency
|
|
43
|
-
js = range(len(labels))
|
|
44
|
-
# strictly speaking existing code didn't sort if <= max_n?
|
|
45
|
-
# "labels = data['labels']" in existing code.
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
y_sums = np.sum(y, axis=0)
|
|
49
36
|
|
|
50
|
-
|
|
37
|
+
y_sums = np.sum(y, axis=0)
|
|
51
38
|
y_sums[y_sums == 0] = 1.0
|
|
52
39
|
|
|
53
40
|
if normalize:
|
|
@@ -57,43 +44,39 @@ def _process_stack_line_data(data, max_n=20, normalize=False):
|
|
|
57
44
|
|
|
58
45
|
def plotly_stack_plot(data, max_n=20, normalize=False, title=None):
|
|
59
46
|
ts, y, labels = _process_stack_line_data(data, max_n, normalize)
|
|
60
|
-
|
|
61
47
|
fig = go.Figure()
|
|
62
48
|
|
|
63
|
-
# Use a nice color palette
|
|
64
|
-
colors = px.colors.qualitative.Plotly
|
|
65
|
-
if len(labels) > len(colors):
|
|
66
|
-
colors = px.colors.qualitative.Dark24 # More colors if needed
|
|
67
|
-
|
|
68
49
|
for i, label in enumerate(labels):
|
|
69
|
-
color =
|
|
50
|
+
color = PREMIUM_PALETTE[i % len(PREMIUM_PALETTE)]
|
|
70
51
|
fig.add_trace(go.Scatter(
|
|
71
52
|
x=ts,
|
|
72
53
|
y=y[i],
|
|
73
54
|
mode='lines',
|
|
74
55
|
name=label,
|
|
75
|
-
stackgroup='one',
|
|
76
|
-
line=dict(width=0.5, color=
|
|
77
|
-
fillcolor=color
|
|
56
|
+
stackgroup='one',
|
|
57
|
+
line=dict(width=0.5, color='rgba(255,255,255,0.3)'),
|
|
58
|
+
fillcolor=color,
|
|
59
|
+
hoverinfo='x+y+name'
|
|
78
60
|
))
|
|
79
61
|
|
|
80
62
|
fig.update_layout(
|
|
81
|
-
title=dict(text=title, x=0.5) if title else None,
|
|
63
|
+
title=dict(text=title, x=0.5, font=dict(size=20)) if title else None,
|
|
82
64
|
yaxis=dict(
|
|
83
|
-
title="Share of
|
|
84
|
-
range=[0, 100] if normalize else None
|
|
65
|
+
title="Share of LoC (%)" if normalize else "Lines of Code",
|
|
66
|
+
range=[0, 100.1] if normalize else None,
|
|
67
|
+
gridcolor='rgba(128,128,128,0.2)'
|
|
85
68
|
),
|
|
86
|
-
xaxis=dict(title="Date"),
|
|
69
|
+
xaxis=dict(title="Date", gridcolor='rgba(128,128,128,0.2)'),
|
|
87
70
|
hovermode="x unified",
|
|
88
|
-
margin=dict(l=20, r=20, t=
|
|
71
|
+
margin=dict(l=20, r=20, t=60, b=100),
|
|
72
|
+
legend=dict(orientation="h", yanchor="top", y=-0.15, xanchor="center", x=0.5),
|
|
73
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
|
74
|
+
paper_bgcolor='rgba(0,0,0,0)'
|
|
89
75
|
)
|
|
90
|
-
|
|
91
|
-
|
|
92
76
|
return fig
|
|
93
77
|
|
|
94
78
|
def plotly_line_plot(data, max_n=20, normalize=False, title=None):
|
|
95
79
|
ts, y, labels = _process_stack_line_data(data, max_n, normalize)
|
|
96
|
-
|
|
97
80
|
fig = go.Figure()
|
|
98
81
|
|
|
99
82
|
for i, label in enumerate(labels):
|
|
@@ -102,40 +85,30 @@ def plotly_line_plot(data, max_n=20, normalize=False, title=None):
|
|
|
102
85
|
y=y[i],
|
|
103
86
|
mode='lines',
|
|
104
87
|
name=label,
|
|
105
|
-
line=dict(width=2)
|
|
88
|
+
line=dict(width=2.5, color=PREMIUM_PALETTE[i % len(PREMIUM_PALETTE)])
|
|
106
89
|
))
|
|
107
90
|
|
|
108
91
|
fig.update_layout(
|
|
109
|
-
title=dict(text=title, x=0.5) if title else None,
|
|
92
|
+
title=dict(text=title, x=0.5, font=dict(size=20)) if title else None,
|
|
110
93
|
yaxis=dict(
|
|
111
|
-
title="Share of
|
|
112
|
-
range=[0, 100] if normalize else None
|
|
94
|
+
title="Share of LoC (%)" if normalize else "Lines of Code",
|
|
95
|
+
range=[0, 100.1] if normalize else None,
|
|
96
|
+
gridcolor='rgba(128,128,128,0.2)'
|
|
113
97
|
),
|
|
114
|
-
xaxis=dict(title="Date"),
|
|
98
|
+
xaxis=dict(title="Date", gridcolor='rgba(128,128,128,0.2)'),
|
|
115
99
|
hovermode="x unified",
|
|
116
|
-
margin=dict(l=20, r=20, t=
|
|
100
|
+
margin=dict(l=20, r=20, t=60, b=100),
|
|
101
|
+
legend=dict(orientation="h", yanchor="top", y=-0.15, xanchor="center", x=0.5),
|
|
102
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
|
103
|
+
paper_bgcolor='rgba(0,0,0,0)'
|
|
117
104
|
)
|
|
118
|
-
|
|
119
|
-
|
|
120
105
|
return fig
|
|
121
106
|
|
|
122
107
|
def plotly_survival_plot(commit_history, exp_fit=False, years=5, title=None):
|
|
123
|
-
# Logic copied from survival_plot.py
|
|
124
|
-
# commit_history is {sha: [[ts, count], ...]}
|
|
125
|
-
|
|
126
108
|
deltas = collections.defaultdict(lambda: np.zeros(2))
|
|
127
109
|
total_n = 0
|
|
128
110
|
YEAR = 365.25 * 24 * 60 * 60
|
|
129
111
|
|
|
130
|
-
# Process history
|
|
131
|
-
# Input might be a list of histories if we support multiple inputs,
|
|
132
|
-
# but based on app.py we pass a single result["survival"] dict.
|
|
133
|
-
# However, existing survival_plot took a LIST of filenames.
|
|
134
|
-
# Let's support the single dict passed from app.py.
|
|
135
|
-
|
|
136
|
-
# The logic in survival_plot.py iterates over input_fns, loads them, and computes `all_deltas`.
|
|
137
|
-
# Here we assume `commit_history` IS the content of one such file (the dict).
|
|
138
|
-
|
|
139
112
|
for commit, history in commit_history.items():
|
|
140
113
|
t0, orig_count = history[0]
|
|
141
114
|
total_n += orig_count
|
|
@@ -145,132 +118,87 @@ def plotly_survival_plot(commit_history, exp_fit=False, years=5, title=None):
|
|
|
145
118
|
last_count = count
|
|
146
119
|
deltas[history[-1][0] - t0] += (-last_count, -orig_count)
|
|
147
120
|
|
|
148
|
-
# Calculate curve
|
|
149
121
|
P = 1.0
|
|
150
|
-
xs = []
|
|
151
|
-
ys = []
|
|
152
|
-
|
|
153
|
-
# Sort deltas by time
|
|
122
|
+
xs, ys = [], []
|
|
154
123
|
sorted_times = sorted(deltas.keys())
|
|
155
124
|
|
|
156
|
-
total_k = total_n # unused?
|
|
157
|
-
|
|
158
125
|
for t in sorted_times:
|
|
159
126
|
delta_k, delta_n = deltas[t]
|
|
160
127
|
xs.append(t / YEAR)
|
|
161
128
|
ys.append(100.0 * P)
|
|
162
|
-
|
|
163
129
|
if total_n > 0:
|
|
164
130
|
P *= 1 + delta_k / total_n
|
|
165
|
-
|
|
166
|
-
# total_k += delta_k
|
|
167
131
|
total_n += delta_n
|
|
168
|
-
|
|
169
|
-
if P < 0.05:
|
|
170
|
-
break
|
|
132
|
+
if P < 0.05: break
|
|
171
133
|
|
|
172
134
|
fig = go.Figure()
|
|
173
|
-
|
|
174
|
-
# Main survival curve
|
|
175
135
|
fig.add_trace(go.Scatter(
|
|
176
136
|
x=xs, y=ys,
|
|
177
137
|
mode='lines',
|
|
178
138
|
name='Survival Rate',
|
|
179
|
-
line=dict(color=
|
|
139
|
+
line=dict(color=PREMIUM_PALETTE[0], width=3)
|
|
180
140
|
))
|
|
181
141
|
|
|
182
|
-
# Exponential fit
|
|
183
142
|
if exp_fit:
|
|
184
143
|
try:
|
|
185
144
|
import scipy.optimize
|
|
186
|
-
|
|
187
|
-
# Define loss function for fit
|
|
188
145
|
def fit(k):
|
|
189
|
-
loss = 0.0
|
|
190
|
-
|
|
191
|
-
# Need to iterate again or reuse data?
|
|
192
|
-
# The original code re-iterates.
|
|
193
|
-
|
|
194
|
-
# Simplified for single dataset:
|
|
195
|
-
curr_total_n = 0
|
|
196
|
-
for _, history in commit_history.items():
|
|
197
|
-
curr_total_n += history[0][1]
|
|
198
|
-
|
|
199
|
-
P_fit = 1.0
|
|
200
|
-
curr_total_n_fit = curr_total_n
|
|
201
|
-
|
|
146
|
+
loss, curr_total_n = 0.0, sum(h[0][1] for h in commit_history.values())
|
|
147
|
+
P_fit, curr_total_n_fit = 1.0, curr_total_n
|
|
202
148
|
for t in sorted_times:
|
|
203
149
|
delta_k, delta_n = deltas[t]
|
|
204
150
|
pred = curr_total_n_fit * math.exp(-k * t / YEAR)
|
|
205
151
|
loss += (curr_total_n_fit * P_fit - pred) ** 2
|
|
206
|
-
if curr_total_n_fit > 0:
|
|
207
|
-
P_fit *= 1 + delta_k / curr_total_n_fit
|
|
152
|
+
if curr_total_n_fit > 0: P_fit *= 1 + delta_k / curr_total_n_fit
|
|
208
153
|
curr_total_n_fit += delta_n
|
|
209
154
|
return loss
|
|
210
|
-
|
|
211
155
|
k_opt = scipy.optimize.fmin(fit, 0.5, maxiter=50, disp=False)[0]
|
|
212
|
-
|
|
213
156
|
ts_fit = np.linspace(0, years, 100)
|
|
214
157
|
ys_fit = [100.0 * math.exp(-k_opt * t) for t in ts_fit]
|
|
215
|
-
|
|
216
158
|
half_life = math.log(2) / k_opt
|
|
217
|
-
|
|
218
159
|
fig.add_trace(go.Scatter(
|
|
219
160
|
x=ts_fit, y=ys_fit,
|
|
220
161
|
mode='lines',
|
|
221
162
|
name=f"Exp. Fit (Half-life: {half_life:.2f} yrs)",
|
|
222
|
-
line=dict(color=
|
|
163
|
+
line=dict(color=PREMIUM_PALETTE[10], dash='dash', width=2)
|
|
223
164
|
))
|
|
224
|
-
|
|
225
|
-
except ImportError:
|
|
226
|
-
pass # Or warn user
|
|
165
|
+
except ImportError: pass
|
|
227
166
|
|
|
228
167
|
fig.update_layout(
|
|
229
168
|
title=dict(text=title, x=0.5) if title else None,
|
|
230
|
-
yaxis=dict(
|
|
231
|
-
|
|
232
|
-
range=[0, 100]
|
|
233
|
-
),
|
|
234
|
-
xaxis=dict(
|
|
235
|
-
title="Years",
|
|
236
|
-
range=[0, years]
|
|
237
|
-
),
|
|
169
|
+
yaxis=dict(title="Lines still present (%)", range=[0, 105], gridcolor='rgba(128,128,128,0.2)'),
|
|
170
|
+
xaxis=dict(title="Years", range=[0, years], gridcolor='rgba(128,128,128,0.2)'),
|
|
238
171
|
hovermode="x unified",
|
|
239
|
-
margin=dict(l=20, r=20, t=50, b=
|
|
172
|
+
margin=dict(l=20, r=20, t=50, b=100),
|
|
173
|
+
legend=dict(orientation="h", yanchor="top", y=-0.15, xanchor="center", x=0.5),
|
|
174
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
|
175
|
+
paper_bgcolor='rgba(0,0,0,0)'
|
|
240
176
|
)
|
|
241
|
-
|
|
242
|
-
|
|
243
177
|
return fig
|
|
244
178
|
|
|
245
179
|
def plotly_bar_plot(data, max_n=20, title=None):
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
# Get latest data point (current state)
|
|
180
|
+
_, y, labels = _process_stack_line_data(data, max_n, normalize=False)
|
|
249
181
|
latest_values = [row[-1] for row in y]
|
|
250
|
-
|
|
251
|
-
# Sort by value for better bar chart presentation
|
|
252
|
-
# (Though _process_stack_line_data already does some sorting, we want descending order)
|
|
253
182
|
indices = sorted(range(len(labels)), key=lambda i: latest_values[i], reverse=True)
|
|
254
|
-
|
|
255
183
|
sorted_labels = [labels[i] for i in indices]
|
|
256
184
|
sorted_values = [latest_values[i] for i in indices]
|
|
257
185
|
|
|
258
|
-
# Generate colors
|
|
259
|
-
colors = px.colors.qualitative.Plotly
|
|
260
|
-
if len(sorted_labels) > len(colors):
|
|
261
|
-
colors = px.colors.qualitative.Dark24
|
|
262
|
-
|
|
263
186
|
fig = go.Figure(go.Bar(
|
|
264
187
|
x=sorted_labels,
|
|
265
188
|
y=sorted_values,
|
|
266
|
-
|
|
189
|
+
marker=dict(
|
|
190
|
+
color=sorted_values,
|
|
191
|
+
colorscale=[[i/(len(PREMIUM_PALETTE)-1), c] for i, c in enumerate(PREMIUM_PALETTE)],
|
|
192
|
+
showscale=False
|
|
193
|
+
)
|
|
267
194
|
))
|
|
268
195
|
|
|
269
196
|
fig.update_layout(
|
|
270
|
-
title=dict(text=f"{title} (
|
|
271
|
-
yaxis=dict(title="Lines of Code"),
|
|
197
|
+
title=dict(text=f"{title} (Latest)" if title else "Latest Distribution", x=0.5),
|
|
198
|
+
yaxis=dict(title="Lines of Code", gridcolor='rgba(128,128,128,0.2)'),
|
|
272
199
|
xaxis=dict(title=""),
|
|
273
200
|
margin=dict(l=20, r=20, t=50, b=100),
|
|
201
|
+
plot_bgcolor='rgba(0,0,0,0)',
|
|
202
|
+
paper_bgcolor='rgba(0,0,0,0)'
|
|
274
203
|
)
|
|
275
|
-
|
|
276
204
|
return fig
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="better-git-of-theseus",
|
|
8
|
-
version="0.
|
|
8
|
+
version="0.5.1",
|
|
9
9
|
description="Plot stats on Git repositories with interactive Plotly charts",
|
|
10
10
|
long_description=long_description,
|
|
11
11
|
long_description_content_type="text/markdown",
|
|
@@ -23,7 +23,8 @@ setup(
|
|
|
23
23
|
"plotly",
|
|
24
24
|
"streamlit",
|
|
25
25
|
"python-dateutil",
|
|
26
|
-
"scipy"
|
|
26
|
+
"scipy",
|
|
27
|
+
"matplotlib"
|
|
27
28
|
],
|
|
28
29
|
entry_points={
|
|
29
30
|
"console_scripts": [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|