aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjwansek <eddie.atten.ea29@gmail.com>2023-05-07 21:03:25 +0100
committerjwansek <eddie.atten.ea29@gmail.com>2023-05-07 21:03:25 +0100
commit1eea185d337eadd7a5309a707d8f0c5cd0d85d9c (patch)
tree0552df0f460e10f709bb342ab41822912ace6a9c
parentf77e28a8b4937b8119761d27dde323b5b982e00a (diff)
downloadUKGenderPayGap-1eea185d337eadd7a5309a707d8f0c5cd0d85d9c.tar.gz
UKGenderPayGap-1eea185d337eadd7a5309a707d8f0c5cd0d85d9c.zip
Added some charts
-rw-r--r--app.py50
-rw-r--r--charts.json32
-rw-r--r--database.py37
-rw-r--r--parser.py2
-rw-r--r--static/scripts.js145
-rw-r--r--static/style.css44
-rw-r--r--templates/plot.html.j28
-rw-r--r--templates/template.html.j234
8 files changed, 338 insertions, 14 deletions
diff --git a/app.py b/app.py
index 2d6d8ec..a88f2c4 100644
--- a/app.py
+++ b/app.py
@@ -15,19 +15,33 @@ else:
@app.route("/")
def serve_index():
- with open("charts.json", "r") as f:
- charts = json.load(f)
-
return flask.render_template(
"index.html.j2",
title = "UK Gender Pay Gap",
- charts = charts["index"]
+ charts = get_charts()["index"]
)
+def get_charts():
+ with open("charts.json", "r") as f:
+ return json.load(f)
+
+@app.route("/api/charts.json")
+def serve_charts():
+ return flask.jsonify(get_charts())
+
+
@app.route("/search_click", methods = ["POST"])
def search_redirect():
return flask.redirect("/search?s=%s" % urllib.parse.quote_plus(dict(flask.request.form)["search"]))
+@app.route("/api/years")
+def api_get_years():
+ pay_type = flask.request.args.get("Pay Type")
+ if pay_type is None or pay_type.lower() not in {'hourly', 'bonuses'}:
+ return flask.abort(400, "The key `pay type` must be equal to 'hourly' or 'bonuses'")
+ with database.PayGapDatabase(host = host) as db:
+ return flask.jsonify(db.get_pay_by_year(pay_type))
+
@app.route("/search")
def search():
with database.PayGapDatabase(host = host) as db:
@@ -42,5 +56,33 @@ def search():
companies = companies
)
+def get_chart_elem(url):
+ for i in get_charts()["index"]:
+ if i["url"] == url:
+ return i
+
+
+@app.route("/plot/<name>")
+def serve_large_plot(name):
+ with database.PayGapDatabase(host = host) as db:
+ elem = get_chart_elem(flask.request.full_path)
+ filters = elem["filters"]
+ for k, v in filters.items():
+ if v == "<SICType>":
+ filters[k] = {"options": db.get_sic_sections()}
+
+ current_filters = dict(flask.request.args)
+ print(filters)
+ print(current_filters)
+ return flask.render_template(
+ "plot.html.j2",
+ title = elem["title"],
+ elem = elem,
+ alt = "Lorem ipsum.",
+ filters = filters,
+ current_filters = current_filters,
+ len = len
+ )
+
if __name__ == "__main__":
app.run("0.0.0.0", port = 5005, debug = True) \ No newline at end of file
diff --git a/charts.json b/charts.json
index 45a5111..2bc3ef9 100644
--- a/charts.json
+++ b/charts.json
@@ -1,12 +1,36 @@
{
"index": [
{
- "title": "Median Hourly Pay Difference",
- "url": "/plot/medianhourly"
+ "title": "Median Hourly Pay Difference by Year",
+ "url": "/plot/years?Pay+Type=Hourly",
+ "filters": {
+ "Pay Type": {
+ "options": [
+ "Hourly",
+ "Bonuses"
+ ],
+ "default": "Hourly"
+ },
+ "SIC Type": "<SICType>",
+ "Employer Type": "<CompanyType>",
+ "Employer Size": "<CompanySize>"
+ }
},
{
- "title": "Median Bonus Pay Difference",
- "url": "/plot/medianbonus"
+ "title": "Median Bonus Pay Difference by Year",
+ "url": "/plot/years?Pay+Type=Bonuses",
+ "filters": {
+ "Pay Type": {
+ "options": [
+ "Hourly",
+ "Bonuses"
+ ],
+ "default": "Hourly"
+ },
+ "SIC Type": "<SICType>",
+ "Employer Type": "<CompanyType>",
+ "Employer Size": "<CompanySize>"
+ }
},
{
"title": "Pay Difference by SIC Section REallt LONNNNNNNNNNNNNNNNNNNNNNNGGGGGGGGGGGGG",
diff --git a/database.py b/database.py
index e368d02..cfb1eb0 100644
--- a/database.py
+++ b/database.py
@@ -219,3 +219,40 @@ class PayGapDatabase:
))
return [(i[0].title(), i[1]) for i in cursor.fetchall()]
+
+ def get_company_types(self):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("SELECT DISTINCT type_ FROM employer WHERE type_ IS NOT NULL;")
+ return [i[0] for i in cursor.fetchall()]
+
+ def get_company_sizes(self):
+ return [
+ "Not Provided",
+ "Less than 250",
+ "250 to 499",
+ "500 to 999",
+ "1000 to 4999",
+ "5000 to 19,999",
+ "20,000 or more"
+ ]
+
+ def get_sic_sections(self):
+ with self.__connection.cursor() as cursor:
+ cursor.execute("SELECT sic_section_name FROM sic_sections")
+ return [i[0] for i in cursor.fetchall()]
+
+ def _source_name_to_year(self, source):
+ return os.path.splitext(source)[0].split("-")[-1].strip().replace("to", "-")
+
+ def get_pay_by_year(self, pay_type):
+ sql = "SELECT source, -AVG("
+ if pay_type.lower() == "hourly":
+ sql += "DiffMedianHourlyPercent"
+ elif pay_type.lower() == "bonuses":
+ sql += "DiffMedianBonusPercent"
+ sql += ") FROM pay GROUP BY source ORDER BY source;"
+
+ with self.__connection.cursor() as cursor:
+ cursor.execute(sql)
+ return [(self._source_name_to_year(i[0]), float(i[1])) for i in cursor.fetchall()]
+
diff --git a/parser.py b/parser.py
index 2918d73..df8d9aa 100644
--- a/parser.py
+++ b/parser.py
@@ -78,4 +78,4 @@ if __name__ == "__main__":
host = "db"
with database.PayGapDatabase(host = host) as db:
- parse_csv(db, sys.argv[1]) \ No newline at end of file
+ print(db.get_sic_sections()) \ No newline at end of file
diff --git a/static/scripts.js b/static/scripts.js
index dc43112..6fec221 100644
--- a/static/scripts.js
+++ b/static/scripts.js
@@ -1,3 +1,144 @@
+function collapseTogglePress(elem) {
+ console.log("elem");
+}
+
+const PLOT_FUNC_MAPPINGS = {
+ "years": draw_plot_years,
+}
+
$(document).ready(function() {
- console.log("ready!")
-}); \ No newline at end of file
+ console.log("ready!");
+
+ fetch("/api/charts.json").then((resp) => {
+ resp.json().then((body) => {
+ const CHARTS = body;
+
+ var minicharts = document.getElementsByClassName("minichart");
+ for (var i = 0; i < minicharts.length; i++) {
+ var theId = minicharts.item(i).id;
+ var u = new URL(window.location.origin + theId);
+ var theIdSplit = u.pathname.split("/");
+
+ CHARTS["index"].forEach(element => {
+ if (theId === "/minichart" + element.url) {
+ filters = element["filters"];
+ }
+ });
+
+ PLOT_FUNC_MAPPINGS[theIdSplit[theIdSplit.length - 1]](theId, filters);
+ }
+
+ var charts = document.getElementsByClassName("chart");
+ for (var i = 0; i < charts.length; i++) {
+ var theId = charts.item(i).id;
+ var u = new URL(window.location.origin + theId);
+ var theIdSplit = u.pathname.split("/");
+
+ CHARTS["index"].forEach(element => {
+ if (theId === "/chart" + element.url) {
+ filters = element["filters"];
+ }
+ });
+
+ PLOT_FUNC_MAPPINGS[theIdSplit[theIdSplit.length - 1]](theId, filters);
+ }
+ })
+ })
+});
+
+function form_api_url(containerName, filters) {
+ var name = containerName.split("/")[containerName.split("/").length - 1];
+ var url = new URL(window.location.origin + "/api/" + name);
+ for (const [filterName, value] of Object.entries(filters)) {
+
+ if (typeof value === 'object' && value !== null) {
+ if ("default" in value) {
+ // console.log(filterName, value["default"]);
+ url.searchParams.append(filterName, value["default"]);
+ }
+ }
+ }
+ return url.toString();
+}
+
+function draw_plot_years(containerName, filters) {
+ fetch(form_api_url(containerName, filters)).then(resp => {
+ resp.json().then((data) => {
+ if (containerName.substring(1, 6) === "chart") {
+ var yAxisTitle = true;
+ var xAxisLabels = true;
+ var showLegend = true;
+ } else {
+ var yAxisTitle = false;
+ var xAxisLabels = false;
+ var showLegend = false;
+ }
+
+ Highcharts.chart(containerName, {
+ chart: {
+ zoomType: 'x',
+ type: 'area',
+ },
+
+ title: {
+ text: null
+ },
+
+ yAxis: {
+ title: {
+ enabled: yAxisTitle,
+ text: 'Percentage Pay Difference'
+ },
+ labels: {
+ format: '{value}%'
+ },
+ // tickPositioner: function () {
+ // // var maxDeviation = Math.ceil(Math.max(Math.abs(this.dataMax), Math.abs(this.dataMin)));
+ // // var halfMaxDeviation = Math.ceil(maxDeviation / 2);
+
+ // // return [-maxDeviation, -halfMaxDeviation, 0, halfMaxDeviation, maxDeviation];
+ // return Array.from({length: -Math.floor(this.dataMin) + 2}, (x, i) => i + Math.floor(this.dataMin));
+ // },
+ },
+
+ xAxis: {
+ type: 'category',
+ labels: {
+ enabled: xAxisLabels
+ },
+ title: {
+ text: "Year Groups",
+ enabled: yAxisTitle,
+ }
+ },
+
+ plotOptions: {
+ series: {
+ fillColor: {
+ linearGradient: [0, 0, 0, 300],
+ stops: [
+ [1, "rgba(0, 255, 0, 0.3)"]
+ ]
+ },
+ negativeFillColor: {
+ linearGradient: [0, 0, 0, 300],
+ stops: [
+ [1, "rgba(255, 0, 0, 0.3)"]
+ ]
+ }
+ }
+ },
+
+ series: [{
+ data: data,
+ lineWidth: 4,
+ showInLegend: showLegend,
+ name: "Pay Gap",
+ color: 'Green',
+ threshold: 0,
+ negativeColor: 'Red',
+ }]
+ })
+ })
+ })
+} \ No newline at end of file
diff --git a/static/style.css b/static/style.css
index 4e89888..28d53c7 100644
--- a/static/style.css
+++ b/static/style.css
@@ -49,6 +49,31 @@ aside form input[type="submit"] {
border: 2px solid black;
}
+#filterform h5 {
+ margin-top: 10px;
+ margin-bottom: 5px;
+}
+
+label {
+ font-size: x-small;
+}
+
+.collapsable {
+ display: none;
+}
+
+.collapsetoggle {
+ color: black;
+ font-weight: bold;
+ padding-top: 1px;
+ text-decoration: none;
+}
+
+.collapsetoggle#hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
#main_content {
padding-left: 2.5%;
padding-right: 2.5;
@@ -62,23 +87,38 @@ aside form input[type="submit"] {
list-style-type: none;
width: 25%;
display: inline-flex;
- background-color: pink;
+ /* background-color: pink; */
min-height: 250px;
margin-bottom: 7px;
overflow: hidden;
flex-direction: column;
justify-content: space-between;
+ /* border-color: black;
+ border-width: 2px;
+ border-radius: 1;
+ border-style: ridge; */
}
.chart_container {
display: flex;
flex-direction: row-reverse;
}
+
.minichart {
min-height: 220px;
width: 100%;
margin: 0.5rem;
- background-color: red;
+ /* background-color: red; */
+}
+
+#singlechart {
+ /* background-color: pink; */
+ width: 70%;
+ min-height: 70%;
+}
+
+.chart {
+
}
.bottom_text {
diff --git a/templates/plot.html.j2 b/templates/plot.html.j2
new file mode 100644
index 0000000..91a3a36
--- /dev/null
+++ b/templates/plot.html.j2
@@ -0,0 +1,8 @@
+{% extends "template.html.j2" %}
+{% block content %}
+ <div id="singlechart">
+ <div class="chart" id="/chart{{ elem['url'] }}">
+ </div>
+ <p id=chart_alt>{{ alt }}</p>
+ </div>
+{% endblock %} \ No newline at end of file
diff --git a/templates/template.html.j2 b/templates/template.html.j2
index 4c08853..72cf520 100644
--- a/templates/template.html.j2
+++ b/templates/template.html.j2
@@ -34,7 +34,39 @@
<input type="search" id="search_entry" name="search" required>
<input type="submit" value="Search" id="search_btn">
</form>
- <h4>Filter...</h4>
+ {% if filters is defined %}
+ <h4>Filters</h4>
+ <form id="filterform">
+ {% for filter_name, filter_content in filters.items() %}
+ <h5>{{ filter_name }}</h5>
+ {% if len(filter_content["options"]) > 5 %}
+ <button class="collapsetoggle" id="collapsetoggle/{{ filter_name }}" onclick="collapseTogglePress('collapsable/{{ filter_name }}')">
+ {{ "Un-hide %d hidden filters" % len(filter_content["options"]) }}
+ </button>
+ <div class="collapsable" id="collapsable/{{ filter_name }}">
+ {% endif %}
+ {% for option in filter_content["options"] %}
+ {% if filter_name in current_filters.keys() %}
+ {% if current_filters[filter_name] == option %}
+ <input type="radio" id="{{ option }}" name="{{ filter_name }}" value = "{{ option }}" checked="checked">
+ {% else %}
+ <input type="radio" id="{{ option }}" name="{{ filter_name }}" value = "{{ option }}">
+ {% endif %}
+ {% else %}
+ <input type="radio" id="{{ option }}" name="{{ filter_name }}" value = "{{ option }}">
+ {% endif %}
+ <label for="{{ option }}">{{ option }}</label><br>
+ {% endfor %}
+ {% if len(filter_content["default"]) < 2 %}
+ <input type="radio" id="No filter" name="{{ filter_name }}" value = "No filter">
+ <label for="No filter">No filter</label><br>
+ {% endif %}
+ {% if len(filter_content["options"]) > 5 %}
+ </div>
+ {% endif %}
+ {% endfor %}
+ </form>
+ {% endif %}
</aside>
<div id="main_content">