mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-06 13:01:50 +02:00
Added modified files and bb js/css
This commit is contained in:
parent
400149bad1
commit
918d4a3c3c
6 changed files with 292 additions and 0 deletions
|
@ -144,6 +144,7 @@ class Project(db.Model):
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
"balance": self.balance[member.id],
|
"balance": self.balance[member.id],
|
||||||
|
"monthly_exp": self.get_monthly_expenditure(member.id),
|
||||||
}
|
}
|
||||||
for member in self.active_members
|
for member in self.active_members
|
||||||
]
|
]
|
||||||
|
@ -164,6 +165,32 @@ class Project(db.Model):
|
||||||
def uses_weights(self):
|
def uses_weights(self):
|
||||||
return len([i for i in self.members if i.weight != 1]) > 0
|
return len([i for i in self.members if i.weight != 1]) > 0
|
||||||
|
|
||||||
|
def get_monthly_expenditure(self, member_id):
|
||||||
|
"""
|
||||||
|
Computes monthly expenses for member_id
|
||||||
|
|
||||||
|
:return: a list of tuples of the form (date, expenditure)
|
||||||
|
"""
|
||||||
|
query_result = {}
|
||||||
|
member_monthly = defaultdict(lambda: defaultdict(float))
|
||||||
|
member_monthly_exp = []
|
||||||
|
query_result[member_id] = self.get_member_bills(member_id).all()
|
||||||
|
for bill in query_result[member_id]:
|
||||||
|
member_monthly[bill.date.year][bill.date.month] += bill.converted_amount
|
||||||
|
for year in member_monthly:
|
||||||
|
for month in member_monthly[year]:
|
||||||
|
amount = member_monthly[year][month]
|
||||||
|
str_month = ""
|
||||||
|
# Datetime requires month as a zero padded decimal number (ie, 01, 02.. 11, 12)
|
||||||
|
if month > 9:
|
||||||
|
str_month = str(month)
|
||||||
|
else:
|
||||||
|
str_month = "0" + str(month)
|
||||||
|
# Convert date into datetime object
|
||||||
|
date = datetime.strptime(str(year) + " " + str_month, "%Y %m")
|
||||||
|
member_monthly_exp.append((date, amount))
|
||||||
|
return member_monthly_exp
|
||||||
|
|
||||||
def get_transactions_to_settle_bill(self, pretty_output=False):
|
def get_transactions_to_settle_bill(self, pretty_output=False):
|
||||||
"""Return a list of transactions that could be made to settle the bill"""
|
"""Return a list of transactions that could be made to settle the bill"""
|
||||||
|
|
||||||
|
|
9
ihatemoney/static/css/billboard.min.css
vendored
Normal file
9
ihatemoney/static/css/billboard.min.css
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*!
|
||||||
|
* Copyright (c) 2017 ~ present NAVER Corp.
|
||||||
|
* billboard.js project is licensed under the MIT license
|
||||||
|
*
|
||||||
|
* billboard.js, JavaScript chart library
|
||||||
|
* https://naver.github.io/billboard.js/
|
||||||
|
*
|
||||||
|
* @version 3.0.2
|
||||||
|
*/.bb svg{font:10px sans-serif;-webkit-tap-highlight-color:rgba(0,0,0,0)}.bb line,.bb path{fill:none;stroke:#000}.bb .bb-button,.bb text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.bb-bars path,.bb-event-rect,.bb-legend-item-tile,.bb-xgrid-focus,.bb-ygrid,.bb-ygrid-focus{shape-rendering:crispEdges}.bb-chart-arc .bb-gauge-value{fill:#000}.bb-chart-arc path{stroke:#fff}.bb-chart-arc rect{stroke:#fff;stroke-width:1}.bb-chart-arc text{fill:#fff;font-size:13px}.bb-axis{shape-rendering:crispEdges}.bb-grid line{stroke:#aaa}.bb-grid text{fill:#aaa}.bb-xgrid,.bb-ygrid{stroke-dasharray:3 3}.bb-text.bb-empty{fill:grey;font-size:2em}.bb-line{stroke-width:1px}.bb-circle._expanded_{stroke-width:1px;stroke:#fff}.bb-selected-circle{fill:#fff;stroke-width:2px}.bb-bar{stroke-width:0}.bb-bar._expanded_{fill-opacity:.75}.bb-circles.bb-focused,.bb-target.bb-focused{opacity:1}.bb-circles.bb-focused path.bb-line,.bb-circles.bb-focused path.bb-step,.bb-target.bb-focused path.bb-line,.bb-target.bb-focused path.bb-step{stroke-width:2px}.bb-circles.bb-defocused,.bb-target.bb-defocused{opacity:.3!important}.bb-circles.bb-defocused .text-overlapping,.bb-target.bb-defocused .text-overlapping{opacity:.05!important}.bb-region{fill:#4682b4;fill-opacity:.1}.bb-brush .extent,.bb-zoom-brush{fill-opacity:.1}.bb-legend-item{font-size:12px;user-select:none}.bb-legend-item-hidden{opacity:.15}.bb-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.bb-title{font:14px sans-serif}.bb-tooltip-container{z-index:10;user-select:none}.bb-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;opacity:.9;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777}.bb-tooltip tr{border:1px solid #ccc}.bb-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.bb-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.bb-tooltip td>span,.bb-tooltip td>svg{display:inline-block;width:10px;height:10px;margin-right:6px}.bb-tooltip.value{text-align:right}.bb-area{stroke-width:0;opacity:.2}.bb-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.bb-chart-arcs-gauge-title{dominant-baseline:middle;font-size:2.7em}.bb-chart-arcs .bb-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.bb-chart-arcs .bb-chart-arcs-gauge-unit{fill:#000;font-size:16px}.bb-chart-arcs .bb-chart-arcs-gauge-max,.bb-chart-arcs .bb-chart-arcs-gauge-min{fill:#777}.bb-chart-radars .bb-levels polygon{fill:none;stroke:#848282;stroke-width:.5px}.bb-chart-radars .bb-levels text{fill:#848282}.bb-chart-radars .bb-axis line{stroke:#848282;stroke-width:.5px}.bb-chart-radars .bb-axis text{font-size:1.15em;cursor:default}.bb-chart-radars .bb-shapes polygon{fill-opacity:.2;stroke-width:1px}.bb-button{position:absolute;top:10px;right:10px}.bb-button .bb-zoom-reset{font-size:11px;border:1px solid #ccc;background-color:#fff;padding:5px;border-radius:5px;cursor:pointer}
|
10
ihatemoney/static/js/billboard.min.js
vendored
Normal file
10
ihatemoney/static/js/billboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,11 @@
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}">
|
<link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}">
|
||||||
|
<!-- Load d3.js -->
|
||||||
|
<script src="https://d3js.org/d3.v6.min.js" charset="utf-8"></script>
|
||||||
|
<!-- Load billboard.js with base style -->
|
||||||
|
<link rel="stylesheet" href="{{url_for("static", filename='css/billboard.min.css')}}">
|
||||||
|
<script src="{{ url_for("static", filename='js/billboard.min.js')}}"></script>
|
||||||
{% block css %}{% endblock %}
|
{% block css %}{% endblock %}
|
||||||
<script src="{{ url_for("static", filename="js/jquery-3.1.1.min.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/jquery-3.1.1.min.js") }}"></script>
|
||||||
<script src="{{ url_for("static", filename="js/ihatemoney.js") }}"></script>
|
<script src="{{ url_for("static", filename="js/ihatemoney.js") }}"></script>
|
||||||
|
|
|
@ -33,6 +33,243 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<h2> {{ _("Visualization") }}</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Required for some style in charts, feel free to move from here -->
|
||||||
|
<style>
|
||||||
|
#balanceBar .bb-ygrid-line line {stroke: red;}
|
||||||
|
|
||||||
|
#balanceBar .fill_green { fill: green; }
|
||||||
|
#balanceBar .fill_red { fill: red; }
|
||||||
|
</style>
|
||||||
|
<br>
|
||||||
|
<div id="balanceBar"></div>
|
||||||
|
<br>
|
||||||
|
<div id="memberPerMonth"></div>
|
||||||
|
<br>
|
||||||
|
<div id="combinedPerMonth"></div>
|
||||||
|
<br>
|
||||||
|
<div id="expPie"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var members = [];
|
||||||
|
var total_per_member = ['CurrentBalance'];
|
||||||
|
var total_exp_per_member = {};
|
||||||
|
var balance_min_value = Number.POSITIVE_INFINITY;
|
||||||
|
var balance_max_value = Number.NEGATIVE_INFINITY;
|
||||||
|
{% for stat in members_stats %}
|
||||||
|
var member_name = "{{ stat.member.name }}"
|
||||||
|
members.push(member_name)
|
||||||
|
var val = {{ stat.balance }};
|
||||||
|
total_per_member.push(val);
|
||||||
|
total_exp_per_member[member_name] = 0;
|
||||||
|
if (val > balance_max_value) {
|
||||||
|
balance_max_value = val;
|
||||||
|
}
|
||||||
|
if (val < balance_min_value) {
|
||||||
|
balance_min_value = val;
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
//TODO: Remove this, its for debugging.
|
||||||
|
members.forEach(function(entry) {
|
||||||
|
console.log(entry);
|
||||||
|
});
|
||||||
|
//TODO: Remove this, its for debugging.
|
||||||
|
total_per_member.forEach(function(entry) {
|
||||||
|
console.log(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start of current balance per month chart
|
||||||
|
var balance_bar = bb.generate({
|
||||||
|
title: {
|
||||||
|
text: "Current Balance per Member"
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
total_per_member
|
||||||
|
],
|
||||||
|
type: "bar",
|
||||||
|
colors: {
|
||||||
|
CurrentBalance: "#999C9F",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
x: {
|
||||||
|
type: "category",
|
||||||
|
categories: members
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
label: "Expenditure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
y: {
|
||||||
|
lines: [
|
||||||
|
{
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
regions: [
|
||||||
|
{
|
||||||
|
axis:"y",
|
||||||
|
start: 0,
|
||||||
|
end: balance_max_value,
|
||||||
|
class: "fill_green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
axis:"y",
|
||||||
|
start: balance_min_value,
|
||||||
|
end: 0,
|
||||||
|
class: "fill_red"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bar: {
|
||||||
|
width: {
|
||||||
|
ratio: 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bindto: "#balanceBar"
|
||||||
|
});
|
||||||
|
// End of current balance per month chart
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Start of chart for total expenditure per month
|
||||||
|
var months =[];
|
||||||
|
var exp_per_month = [];
|
||||||
|
{% for month in months %}
|
||||||
|
var month_year = "{{ _(month.strftime("%B")) }} {{ month.year }}";
|
||||||
|
months.push(month_year);
|
||||||
|
exp_per_month.push({{ monthly_stats[month.year][month.month] }});
|
||||||
|
{% endfor %}
|
||||||
|
months.reverse();
|
||||||
|
exp_per_month.reverse()
|
||||||
|
exp_per_month.splice(0,0,"Combined Monthly Expenditure");
|
||||||
|
//TODO: Remove this, its for debugging.
|
||||||
|
months.forEach(function(entry) {
|
||||||
|
console.log(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
var exp_per_month_chart = bb.generate({
|
||||||
|
title: {
|
||||||
|
text: "Combined Monthly Expenditure"
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
columns: [
|
||||||
|
exp_per_month
|
||||||
|
],
|
||||||
|
type: "line",
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
x: {
|
||||||
|
type: "category",
|
||||||
|
categories: months
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
label: "Expenditure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bindto: "#combinedPerMonth"
|
||||||
|
});
|
||||||
|
// End of total expenditure per month chart
|
||||||
|
|
||||||
|
// Start of expenditure per member per month chart
|
||||||
|
var members_exp_by_month = [];
|
||||||
|
{% for stat in members_stats|sort(attribute='member.name') %}
|
||||||
|
var month_dict = [];
|
||||||
|
var month_amount = {};
|
||||||
|
{% for data in stat.monthly_exp %}
|
||||||
|
// data is a tuple with a datetime object (Mon Year) at index 0 and amount at idx 1.
|
||||||
|
month_dict.push({
|
||||||
|
date: "{{ _(data[0].strftime("%B")) }} {{ data[0].year }}",
|
||||||
|
amount: {{ data[1] }}
|
||||||
|
});
|
||||||
|
var date = "{{ _(data[0].strftime("%B")) }} {{ data[0].year }}";
|
||||||
|
month_amount[date] = {{ data[1] }};
|
||||||
|
total_exp_per_member["{{ stat.member.name }}"] += {{ data[1] }};
|
||||||
|
{% endfor %}
|
||||||
|
month_dict.reverse();
|
||||||
|
|
||||||
|
members_exp_by_month.push({
|
||||||
|
name: "{{stat.member.name}}",
|
||||||
|
exp_by_month: month_dict,
|
||||||
|
member_months: month_amount,
|
||||||
|
});
|
||||||
|
{% endfor %}
|
||||||
|
var member_exp_dict = {};
|
||||||
|
|
||||||
|
members_exp_by_month.forEach(function(member) {
|
||||||
|
// inserting the name of the member as the first element of the array, as per bb docs
|
||||||
|
member_exp_dict[member.name] = [member.name];
|
||||||
|
});
|
||||||
|
var chart_columns = [];
|
||||||
|
months.forEach(function(month) {
|
||||||
|
members_exp_by_month.forEach(function(member) {
|
||||||
|
// If current member had expenditure on current month
|
||||||
|
if (month in member.member_months) {
|
||||||
|
member_exp_dict[member.name].push(member.member_months[month]);
|
||||||
|
} else {
|
||||||
|
member_exp_dict[member.name].push(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(member_exp_dict);
|
||||||
|
// Pushing each individual member's expenditure per month array into the chart array
|
||||||
|
// chart array is what is given to bb.generate
|
||||||
|
members_exp_by_month.forEach(function(member) {
|
||||||
|
chart_columns.push(member_exp_dict[member.name]);
|
||||||
|
});
|
||||||
|
var members_exp_month_chart = bb.generate({
|
||||||
|
title: {
|
||||||
|
text: "Monthly Expenditure Per Member"
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
columns: chart_columns,
|
||||||
|
type: "line"
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
x: {
|
||||||
|
type: "category",
|
||||||
|
categories: months,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
label: "Expenditure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bindto: "#memberPerMonth"
|
||||||
|
});
|
||||||
|
|
||||||
|
//end of expenditure per month per member
|
||||||
|
|
||||||
|
// Start of Expenditure Pie Chart
|
||||||
|
pie_cols = [];
|
||||||
|
members_exp_by_month.forEach(function(member) {
|
||||||
|
var particpant = member.name;
|
||||||
|
var spent = total_exp_per_member[member.name];
|
||||||
|
var exp_arr = [particpant, spent];
|
||||||
|
pie_cols.push(exp_arr);
|
||||||
|
})
|
||||||
|
console.log(pie_cols);
|
||||||
|
var pie = bb.generate({
|
||||||
|
data: {
|
||||||
|
columns: pie_cols,
|
||||||
|
type: "pie",
|
||||||
|
},
|
||||||
|
bindto: "#expPie"
|
||||||
|
});
|
||||||
|
|
||||||
|
// End of Expenditure Pie Chart
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -602,6 +602,9 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
},
|
},
|
||||||
"paid": 25.0,
|
"paid": 25.0,
|
||||||
"spent": 12.5,
|
"spent": 12.5,
|
||||||
|
"monthly_exp": [
|
||||||
|
["2011-08-01T00:00:00", 25.0]
|
||||||
|
], # Day returned will always be 01, and h/m/s always 00
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"balance": -12.5,
|
"balance": -12.5,
|
||||||
|
@ -613,6 +616,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
},
|
},
|
||||||
"paid": 0,
|
"paid": 0,
|
||||||
"spent": 12.5,
|
"spent": 12.5,
|
||||||
|
"monthly_exp": [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
json.loads(req.data.decode("utf-8")),
|
json.loads(req.data.decode("utf-8")),
|
||||||
|
|
Loading…
Reference in a new issue