style improve optical balance of the progress charts

- Wider aspect (640x260) and fixed side gutters, so stacked charts share one left edge and sparse two-point series no longer look empty. - Faint horizontal gridlines anchor the lines and give mid-value reference. - Bind each heading, legend, and chart into one group with a clear gap between blocks, and anchor the kg/reps titles just above their axes. - Differentiate the series so weight leads: solid oxblood weight line, dashed brass reps line, with matching legend swatches.

Commit
d410e7200f094189e1c34fe37a859d55a4173061
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/presenter/svg_chart.ml
index ac8364c5..72802805 100644..100644
@@ -16,13 +16,16 @@
16 16 let sattr = string_attr
17 17 let fattr = float_attr
18 18
19 Removed: (* Fixed drawing surface. The viewBox scales to the container width via CSS. *)
19 Added: (* Fixed drawing surface. The viewBox scales to the container width via CSS. A
20 Added: wider-than-tall aspect keeps sparse two-point series from looking empty. The
21 Added: side gutters are fixed, so every chart's plot area starts at the same x and
22 Added: the stacked charts share one clean left edge. *)
20 23 let width = 640.
21 Removed: let height = 320.
22 Removed: let pad_left = 48.
23 Removed: let pad_right = 48.
24 Removed: let pad_top = 24.
25 Removed: let pad_bottom = 36.
24 Added: let height = 260.
25 Added: let pad_left = 52.
26 Added: let pad_right = 52.
27 Added: let pad_top = 22.
28 Added: let pad_bottom = 34.
26 29 let plot_w = width -. pad_left -. pad_right
27 30 let plot_h = height -. pad_top -. pad_bottom
28 31
@@ -72,6 +75,20 @@
72 75 ]
73 76 []
74 77
78 Added: (* A faint full-width horizontal rule at fraction [f] of the plot height, to
79 Added: anchor the lines without competing with them. *)
80 Added: let gridline f =
81 Added: let y = pad_top +. (plot_h *. f) in
82 Added: line
83 Added: [
84 Added: sattr "class" "chart-grid";
85 Added: fattr "x1" pad_left;
86 Added: fattr "y1" y;
87 Added: fattr "x2" (pad_left +. plot_w);
88 Added: fattr "y2" y;
89 Added: ]
90 Added: []
91 Added:
75 92 let axis_label ~x ~y ~anchor ~css_class s =
76 93 text
77 94 [
@@ -127,16 +144,20 @@
127 144 | [] -> ""
128 145 in
129 146 [
147 Added: (* Faint gridlines first, so axes and series sit on top. *)
148 Added: gridline 0.25;
149 Added: gridline 0.5;
150 Added: gridline 0.75;
130 151 (* Axes: left (weight), right (reps), and a baseline. *)
131 152 axis_line ~x1:pad_left ~y1:pad_top ~x2:pad_left ~y2:(pad_top +. plot_h);
132 153 axis_line ~x1:(pad_left +. plot_w) ~y1:pad_top ~x2:(pad_left +. plot_w)
133 154 ~y2:(pad_top +. plot_h);
134 155 axis_line ~x1:pad_left ~y1:(pad_top +. plot_h) ~x2:(pad_left +. plot_w)
135 156 ~y2:(pad_top +. plot_h);
136 Removed: (* Axis titles. *)
137 Removed: axis_label ~x:pad_left ~y:(pad_top -. 10.) ~anchor:"middle"
157 Added: (* Axis titles, anchored just above their axis. *)
158 Added: axis_label ~x:pad_left ~y:(pad_top -. 8.) ~anchor:"middle"
138 159 ~css_class:"chart-axis-title weight" "kg";
139 Removed: axis_label ~x:(pad_left +. plot_w) ~y:(pad_top -. 10.) ~anchor:"middle"
160 Added: axis_label ~x:(pad_left +. plot_w) ~y:(pad_top -. 8.) ~anchor:"middle"
140 161 ~css_class:"chart-axis-title reps" "reps";
141 162 (* Left axis bounds (weight, kg). *)
142 163 axis_label ~x:(pad_left -. 6.) ~y:(pad_top +. 4.) ~anchor:"end"
lib/web/assets/hito.css
index 40986b31..ccccd836 100644..100644
@@ -763,50 +763,54 @@
763 763 /* The action bar under the logbook heading. */
764 764 .logbook-actions-bar { margin: 0.5rem 0 1rem; }
765 765
766 Removed: /* The progress page: a no-JS exercise search and a dual-axis line chart. */
767 Removed: .progress-search {
768 Removed: display: flex;
769 Removed: flex-wrap: wrap;
770 Removed: gap: 0.5rem;
771 Removed: align-items: end;
772 Removed: margin: 1rem 0 1.5rem;
766 Added: /* The progress page: one dual-axis line chart per chartable exercise. Each
767 Added: block binds its heading, legend, and chart into one group with a clear gap
768 Added: to the next. */
769 Added: .progress-chart-section {
770 Added: margin: 0 0 2rem;
773 771 }
774 Removed: .progress-search label {
775 Removed: flex: 0 0 100%;
776 Removed: margin: 0;
772 Added: .progress-chart-section h2 {
773 Added: margin-bottom: 0.25rem;
777 774 }
778 Removed: .progress-search input { flex: 1 1 14rem; }
779 Removed: .progress-search button { flex: 0 0 auto; }
780 Removed:
781 Removed: .progress-chart-section { margin-top: 1rem; }
782 775 .chart-legend {
783 776 display: flex;
784 777 flex-wrap: wrap;
785 778 gap: 1rem;
786 Removed: margin: 0.5rem 0 1rem;
779 Added: margin: 0 0 0.35rem;
787 780 font-size: var(--font-size-small);
781 Added: color: var(--muted-ink);
788 782 }
789 783 .chart-legend-item { display: inline-flex; align-items: center; gap: 0.4rem; }
790 784 .chart-legend-item::before {
791 785 content: "";
792 Removed: width: 1.1rem;
793 Removed: height: 0.2rem;
794 Removed: border-radius: 999px;
786 Added: width: 1.4rem;
787 Added: height: 0;
788 Added: border-top: 2px solid;
795 789 }
796 Removed: .chart-legend-item.weight::before { background: var(--oxblood); }
797 Removed: .chart-legend-item.reps::before { background: var(--brass); }
790 Added: .chart-legend-item.weight::before { border-top-color: var(--oxblood); }
791 Added: .chart-legend-item.reps::before {
792 Added: border-top-color: var(--brass);
793 Added: border-top-style: dashed;
794 Added: }
798 795
799 796 /* The chart scales to its container; the viewBox does the layout. */
800 797 .progress-chart {
798 Added: display: block;
801 799 width: 100%;
802 800 height: auto;
803 801 max-width: 40rem;
804 802 }
805 803 .chart-axis { stroke: var(--rule-strong); stroke-width: 1; }
804 Added: .chart-grid { stroke: var(--rule); stroke-width: 1; opacity: 0.5; }
806 805 .chart-tick { font: 500 0.7rem var(--mono); fill: var(--muted-ink); }
806 Added: .chart-axis-title { font: 700 0.72rem var(--sans); }
807 Added: .chart-axis-title.weight { fill: var(--oxblood-dark); }
808 Added: .chart-axis-title.reps { fill: var(--brass); }
809 Added: /* Weight is the primary series: a solid oxblood line leads. Reps is secondary:
810 Added: a dashed brass line, so the two never blur together on a busy chart. */
807 811 .chart-series { fill: none; stroke-width: 2; }
808 812 .chart-series.weight { stroke: var(--oxblood); }
809 Removed: .chart-series.reps { stroke: var(--brass); }
813 Added: .chart-series.reps { stroke: var(--brass); stroke-dasharray: 5 3; }
810 814 @keyframes logbook-detail-open {
811 815 from { opacity: 0; transform: translateY(-0.25rem); }
812 816 to { opacity: 1; transform: none; }