From 17df2cd460fd51344ef5dfe884e4a3f01533d267 Mon Sep 17 00:00:00 2001 From: Marie Birner Date: Sun, 1 Oct 2023 13:40:36 +0200 Subject: [PATCH 1/6] [TASK] not working chart --- templates/stat.html.tera | 93 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/templates/stat.html.tera b/templates/stat.html.tera index ab44157..9f6a417 100644 --- a/templates/stat.html.tera +++ b/templates/stat.html.tera @@ -34,5 +34,98 @@ {% endfor %} +
+ + {% endblock content%} From 020afbb4195c58d7efa2aeae155dd8941d05a55d Mon Sep 17 00:00:00 2001 From: Marie Birner Date: Sun, 1 Oct 2023 13:42:40 +0200 Subject: [PATCH 2/6] [TASK] add csv --- .gitignore | 2 +- frontend/static/csv/stats.csv | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 frontend/static/csv/stats.csv diff --git a/.gitignore b/.gitignore index 911a738..d17d371 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ db.sqlite .history/ Rocket.toml frontend/node_modules/* -static/ +/static/ diff --git a/frontend/static/csv/stats.csv b/frontend/static/csv/stats.csv new file mode 100644 index 0000000..8fafae2 --- /dev/null +++ b/frontend/static/csv/stats.csv @@ -0,0 +1,15 @@ +date,km +2023-01-01,5 +2023-02-01,24 +2023-03-01,56 +2023-04-01,122 +2023-04-13,232 +2023-05-01,456 +2023-06-01,567 +2023-07-01,765 +2023-08-01,801 +2023-09-01,903 +2023-10-01,1002 +2023-11-01,1100 +2023-12-01,1203 +2023-12-30,1340 \ No newline at end of file From fa66efa3615e33cd3b0a09774dafa9f89a1417eb Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 1 Oct 2023 14:01:34 +0200 Subject: [PATCH 3/6] maybe a bit better? --- templates/stat.html.tera | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/templates/stat.html.tera b/templates/stat.html.tera index 9f6a417..cc83ce4 100644 --- a/templates/stat.html.tera +++ b/templates/stat.html.tera @@ -91,14 +91,18 @@ const y = d3.scaleLinear() .attr("text-anchor", "start") .text("↑ Daily km ($)")); -d3.csv("../public/csv/stats.csv", function(d) { - return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.km } +d3.csv("../public/csv/stats.csv").then(function(data) { + data.forEach(function(d) { + d.date = d3.timeParse("%Y-%m-%d")(d.date); + d.km = +d.km; + console.log(d); + }); }, function(error, data) { if (error) throw error; x.domain(d3.extent(data, function(d) { return d.date; })); - y.domain(d3.extent(data, function(d) { return d.km; })); + y.domain(d3.extent(data, function(d) { return d.km; })); g.append("g") .attr("transform", "translate(0," + height + ")") From 22642e3867700681a83bb52478271ce4ef291d6c Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 1 Oct 2023 14:19:23 +0200 Subject: [PATCH 4/6] wurking --- templates/stat.html.tera | 133 +++++++++++++++------------------------ 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/templates/stat.html.tera b/templates/stat.html.tera index cc83ce4..820eeae 100644 --- a/templates/stat.html.tera +++ b/templates/stat.html.tera @@ -42,94 +42,59 @@ import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; -// Declare the chart dimensions and margins. - const width = 928; - const height = 500; - const marginTop = 20; - const marginRight = 30; - const marginBottom = 30; - const marginLeft = 40; +const data = [ + { date: '2023-01-01', km: 5 }, + { date: '2023-02-01', km: 24 }, + { date: '2023-08-30', km: 1340 }, +]; - // Declare the x (horizontal position) scale. -const parseTime = d3.timeParse("%d-%b-%y"); +const margin = { top: 20, right: 20, bottom: 50, left: 50 }, + width = 960 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; -const x = d3.scaleTime() - .rangeRound([0, width]); +const parseTime = d3.timeParse('%Y-%m-%d'); -const y = d3.scaleLinear() - .rangeRound([height, 0]); - - // Declare the line generator. - const line = d3.line() - .x(d => x(d.date)) - .y(d => y(d.km)); - - // Create the SVG container. - const svg = d3.create("svg") - .attr("width", width) - .attr("height", height) - .attr("viewBox", [0, 0, width, height]) - .attr("style", "max-width: 100%; height: auto; height: intrinsic;"); - - // Add the x-axis. - svg.append("g") - .attr("transform", `translate(0,${height - marginBottom})`) - .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0)); - - // Add the y-axis, remove the domain line, add grid lines and a label. - svg.append("g") - .attr("transform", `translate(${marginLeft},0)`) - .call(d3.axisLeft(y).ticks(height / 40)) - .call(g => g.select(".domain").remove()) - .call(g => g.selectAll(".tick line").clone() - .attr("x2", width - marginLeft - marginRight) - .attr("stroke-opacity", 0.1)) - .call(g => g.append("text") - .attr("x", -marginLeft) - .attr("y", 10) - .attr("fill", "currentColor") - .attr("text-anchor", "start") - .text("↑ Daily km ($)")); - -d3.csv("../public/csv/stats.csv").then(function(data) { - data.forEach(function(d) { - d.date = d3.timeParse("%Y-%m-%d")(d.date); - d.km = +d.km; - console.log(d); - }); - -}, function(error, data) { - if (error) throw error; - - x.domain(d3.extent(data, function(d) { return d.date; })); - y.domain(d3.extent(data, function(d) { return d.km; })); - - g.append("g") - .attr("transform", "translate(0," + height + ")") - .call(d3.axisBottom(x)) - .select(".domain") - .remove(); - - g.append("g") - .call(d3.axisLeft(y)) - .append("text") - .attr("fill", "#000") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", "0.71em") - .attr("text-anchor", "end") - .text("Price ($)"); - - g.append("path") - .datum(data) - .attr("fill", "none") - .attr("stroke", "steelblue") - .attr("stroke-linejoin", "round") - .attr("stroke-linecap", "round") - .attr("stroke-width", 1.5) - .attr("d", line); +data.forEach(d => { + d.date = parseTime(d.date); + d.km = +d.km; }); -container.append(svg.node()); +const x = d3.scaleTime() + .domain(d3.extent(data, d => d.date)) + .range([0, width]); + +const y = d3.scaleLinear() + .domain([0, d3.max(data, d => d.km)]) + .range([height, 0]); + +const line = d3.line() + .x(d => x(d.date)) + .y(d => y(d.km)); + +const svg = d3.select('#container') + .append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + +svg.append('path') + .data([data]) + .attr('class', 'line') + .attr('d', line); + +svg.append('g') + .attr('transform', `translate(0,${height})`) + .call(d3.axisBottom(x)); + +svg.append('g') + .call(d3.axisLeft(y)); + {% endblock content%} From b797b8011d258a21f1349f3317cdd6fd5574e6a2 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 1 Oct 2023 14:35:04 +0200 Subject: [PATCH 5/6] fake personal history --- src/model/stat.rs | 24 ++++++++++++++++++++++++ src/tera/stat.rs | 8 ++++++-- templates/stat.html.tera | 7 ++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/model/stat.rs b/src/model/stat.rs index 4f6ced9..77922f9 100644 --- a/src/model/stat.rs +++ b/src/model/stat.rs @@ -1,3 +1,4 @@ +use crate::model::user::User; use serde::Serialize; use sqlx::{FromRow, Row, SqlitePool}; @@ -41,3 +42,26 @@ ORDER BY rowed_km DESC; .collect() } } + +#[derive(Debug, Serialize)] +pub struct PersonalStat { + date: String, + km: i32, +} + +pub async fn get_personal(db: &SqlitePool, user: &User) -> Vec { + vec![ + PersonalStat { + date: String::from("2023-01-01"), + km: 5, + }, + PersonalStat { + date: String::from("2023-02-01"), + km: 24, + }, + PersonalStat { + date: String::from("2023-08-30"), + km: 1340, + }, + ] +} diff --git a/src/tera/stat.rs b/src/tera/stat.rs index dc611ef..1e39aa9 100644 --- a/src/tera/stat.rs +++ b/src/tera/stat.rs @@ -2,13 +2,17 @@ use rocket::{get, routes, Route, State}; use rocket_dyn_templates::{context, Template}; use sqlx::SqlitePool; -use crate::model::{stat::Stat, user::User}; +use crate::model::{ + stat::{self, Stat}, + user::User, +}; #[get("/")] async fn index(db: &State, user: User) -> Template { let stat = Stat::get_rowed_km(db).await; + let personal = stat::get_personal(db, &user).await; - Template::render("stat", context!(loggedin_user: &user, stat)) + Template::render("stat", context!(loggedin_user: &user, stat, personal)) } pub fn routes() -> Vec { diff --git a/templates/stat.html.tera b/templates/stat.html.tera index 820eeae..5d274fd 100644 --- a/templates/stat.html.tera +++ b/templates/stat.html.tera @@ -43,10 +43,11 @@ import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; const data = [ - { date: '2023-01-01', km: 5 }, - { date: '2023-02-01', km: 24 }, - { date: '2023-08-30', km: 1340 }, + {%- for p in personal %} + { date: '{{p.date}}', km: {{p.km}} }, + {%- endfor %} ]; +console.log(data); const margin = { top: 20, right: 20, bottom: 50, left: 50 }, width = 960 - margin.left - margin.right, From dd00c6d4fde8af1a5ca646f5f52cc550f7815e15 Mon Sep 17 00:00:00 2001 From: Marie Birner Date: Sun, 1 Oct 2023 15:39:30 +0200 Subject: [PATCH 6/6] [TASK] move code to js / css --- frontend/logbook.ts | 108 ++++++++++++++++++++------- frontend/package.json | 6 +- frontend/scss/app.scss | 1 + frontend/scss/components/_chart.scss | 3 + templates/stat.html.tera | 60 +-------------- 5 files changed, 92 insertions(+), 86 deletions(-) create mode 100644 frontend/scss/components/_chart.scss diff --git a/frontend/logbook.ts b/frontend/logbook.ts index 26c5a18..7a100dd 100644 --- a/frontend/logbook.ts +++ b/frontend/logbook.ts @@ -1,33 +1,85 @@ -/*document.addEventListener('DOMContentLoaded', function() { - setDistance('.set-distance-js'); +import * as d3 from 'd3'; + +const margin = { top: 20, right: 20, bottom: 50, left: 50 }, +width = 960 - margin.left - margin.right, +height = 500 - margin.top - margin.bottom; + +const parseTime = d3.timeParse('%Y-%m-%d'); + +data.forEach((d: any) => { + d.date = parseTime(d.date); + d.km = +d.km; }); -function setDistance(selector: string) { - const fields = document.querySelectorAll(selector); - if(fields) { - Array.prototype.forEach.call(fields, (field: HTMLInputElement) => { - if(field.dataset.relation){ - const relatedField = document.getElementById(field.dataset.relation); +const x = d3.scaleTime() +.domain(d3.extent(data, d => d.date)) +.range([0, width]); - if(relatedField) { - field.addEventListener('input', (e) => { - e.preventDefault(); - - const dataList = document.getElementById('destinations'); - if(dataList) { - var option = Array.prototype.find.call(dataList.options, function(option) { - return option.value === field.value; - }); - - // Get distance - const distance = option.getAttribute('distance'); - if(distance) relatedField.value = distance; - } - }); - } - } - }); +const y = d3.scaleLinear() +.domain([0, d3.max(data, d => d.km)]) +.range([height, 0]); + +const line = d3.line() +.x(d => x(d.date)) +.y(d => y(d.km)); + +const svg = d3.select('#container') +.append('svg') + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .call(responsivefy) +.append('g') + .attr('transform', `translate(${margin.left},${margin.top})`); + +svg.append('path') +.data([data]) +.attr('class', 'line') +.attr('d', line); + +svg.append('g') +.attr('transform', `translate(0,${height})`) +.call(d3.axisBottom(x)); + +svg.append('g') +.call(d3.axisLeft(y)); + + +function responsivefy(svg: any) { + // container will be the DOM element + // that the svg is appended to + // we then measure the container + // and find its aspect ratio + const container = d3.select(svg.node().parentNode), + width = parseInt(svg.style('width'), 10), + height = parseInt(svg.style('height'), 10), + aspect = width / height; + + // set viewBox attribute to the initial size + // control scaling with preserveAspectRatio + // resize svg on inital page load + svg.attr('viewBox', `0 0 ${width} ${height}`) + .attr('preserveAspectRatio', 'xMinYMid') + .call(resize); + + // add a listener so the chart will be resized + // when the window resizes + // multiple listeners for the same event type + // requires a namespace, i.e., 'click.foo' + // api docs: https://goo.gl/F3ZCFr + d3.select(window).on( + 'resize.' + container.attr('id'), + resize + ); + + // this is the code that resizes the chart + // it will be called on load + // and in response to window resizes + // gets the width of the container + // and resizes the svg to fill it + // while maintaining a consistent aspect ratio + function resize() { + const w = parseInt(container.style('width')); + svg.attr('width', w); + svg.attr('height', Math.round(w / aspect)); } } -*/ -export {} diff --git a/frontend/package.json b/frontend/package.json index 51e9ecb..9e1677c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,15 +9,17 @@ "preview": "vite preview" }, "devDependencies": { + "@types/d3": "^7.4.1", "autoprefixer": "^10.4.14", "postcss": "^8.4.21", "sass": "^1.60.0", "tailwindcss": "^3.3.1", - "typescript": "^4.9.3", + "typescript": "^4.9.5", "vite": "^4.2.0", "vite-plugin-static-copy": "^0.13.1" }, "dependencies": { - "choices.js": "^10.2.0" + "choices.js": "^10.2.0", + "d3": "^7.8.5" } } diff --git a/frontend/scss/app.scss b/frontend/scss/app.scss index 12ad22b..46606c2 100644 --- a/frontend/scss/app.scss +++ b/frontend/scss/app.scss @@ -9,3 +9,4 @@ @import 'components/input'; @import 'components/alert'; @import 'components/status'; +@import 'components/chart'; diff --git a/frontend/scss/components/_chart.scss b/frontend/scss/components/_chart.scss new file mode 100644 index 0000000..fc2534d --- /dev/null +++ b/frontend/scss/components/_chart.scss @@ -0,0 +1,3 @@ +.line { + @apply fill-none stroke-2 stroke-primary-600; +} diff --git a/templates/stat.html.tera b/templates/stat.html.tera index 5d274fd..780ba6b 100644 --- a/templates/stat.html.tera +++ b/templates/stat.html.tera @@ -37,65 +37,13 @@
- - + + {% endblock content%}