Skip to content

Commit 48bbc57

Browse files
committed
Improve the graph
1 parent 5eaac80 commit 48bbc57

6 files changed

Lines changed: 184 additions & 55 deletions

File tree

app/assets/javascripts/kanaui/kiddo/charts/line_chart.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,61 +68,55 @@
6868
return dataset.name;
6969
}));
7070

71-
// Create legend container at the top
71+
// Create legend container to the right of the chart area
7272
var legendContainer = svg
7373
.append("g")
7474
.attr("class", "chart-legend")
75-
.attr("transform", "translate(" + (self.width - 100) + ", -25)");
75+
.attr("transform", "translate(" + (self.width + self.margin_left + 15) + ", 10)");
7676

7777
// Calculate total values for legend
7878
var legendData = datasets.map(function (dataset, index) {
7979
var latestValue = dataset.values[dataset.values.length - 1];
80-
var totalCount = dataset.values.length;
8180
return {
8281
name: dataset.name,
8382
value: latestValue ? latestValue.y : 0,
84-
count: totalCount,
8583
color: self.color(dataset.name),
8684
index: index,
8785
};
8886
});
8987

90-
// Create legend items
88+
// Create legend items as a vertical column
9189
var legendItems = legendContainer
9290
.selectAll(".legend-item")
9391
.data(legendData)
9492
.enter()
9593
.append("g")
9694
.attr("class", "legend-item");
9795

98-
var xOffset = 0;
99-
legendItems.each(function (d, i) {
96+
var yOffset = 0;
97+
legendItems.each(function (d) {
10098
var legendItem = d3.select(this);
10199

102-
// Add colored circle
103100
legendItem
104101
.append("circle")
105-
.attr("cx", xOffset + 6)
106-
.attr("cy", 0)
102+
.attr("cx", 6)
103+
.attr("cy", yOffset)
107104
.attr("r", 6)
108105
.style("fill", d.color);
109106

110-
// Add text label
111107
var labelText =
112-
helper.formatSeriesName(d.name) + " (" + d.count + "): " + d3.format(",.2f")(d.value);
108+
helper.formatSeriesName(d.name, self.reportName) + ": " + helper.formatValue(d.value);
113109
legendItem
114110
.append("text")
115-
.attr("x", xOffset + 18)
116-
.attr("y", 0)
111+
.attr("x", 18)
112+
.attr("y", yOffset)
117113
.attr("dy", "0.35em")
118114
.style("font-size", "0.875rem")
119115
.style("font-weight", "500")
120116
.style("fill", "#6B7280")
121117
.text(labelText);
122118

123-
// Calculate width for next item
124-
var textWidth = this.getBBox().width;
125-
xOffset += textWidth + 40; // Add spacing between items
119+
yOffset += 22;
126120
});
127121

128122
// Render data lines

app/assets/javascripts/kanaui/kiddo/charts/utils/mouse_over.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,16 @@
5959
var infoBox = info.node().getBBox();
6060
var infoTitleBox = infoTitle.node().getBBox();
6161
var margin = 40;
62+
var minWidth = Math.max(box.width, infoTitleBox.width) + margin;
6263

6364
info.attr("height", infoBox.height + box.height + 7);
64-
if (infoBox.width < box.width) {
65-
info.attr("width", box.width + margin);
66-
infoTitleBg.attr("width", box.width + margin);
65+
if (infoBox.width < minWidth) {
66+
info.attr("width", minWidth);
67+
infoTitleBg.attr("width", minWidth);
6768

6869
$("#mouseover_canvas #info-title").attr(
6970
"dx",
70-
(box.width + margin) / 2 - infoTitleBox.width / 2
71+
minWidth / 2 - infoTitleBox.width / 2
7172
);
7273
}
7374
};
@@ -81,7 +82,9 @@
8182
});
8283

8384
function mousemove(event) {
84-
var mouseX = d3.pointer(event, this)[0];
85+
var pointer = d3.pointer(event, this);
86+
var mouseX = pointer[0];
87+
var mouseY = pointer[1];
8588

8689
$("#mouseover_canvas .chart_values").detach().remove();
8790
$("#mouseover_canvas .chart_circles").detach().remove();
@@ -115,11 +118,7 @@
115118
.attr("cy", y(d.y))
116119
.style("fill", self.color(name));
117120

118-
var canvasPosition = x(x0) > self.width / 2 ? 50 : self.width / 2;
119-
120-
canvas.attr("transform", "translate(" + canvasPosition + ",0)");
121-
122-
canvas.select("#info-title").text(d.date);
121+
canvas.select("#info-title").text(helper.formatDate(d.date));
123122

124123
elementsForLegend.push({ element: element, d: d });
125124
});
@@ -150,11 +149,21 @@
150149
.text(
151150
element.d === undefined
152151
? element.element.name
153-
: helper.formatValueDisplay(element.element.name, element.d)
152+
: helper.formatValueDisplay(element.element.name, element.d, self.reportName)
154153
);
155154

156155
addInfoDimensions(text);
157156
});
157+
158+
// Position tooltip to follow the cursor; flip left when near the right edge
159+
var tooltipOffset = 15;
160+
var tooltipWidth = info.node().getBBox().width;
161+
var tooltipHeight = info.node().getBBox().height;
162+
var canvasX = (mouseX + tooltipOffset + tooltipWidth > self.width)
163+
? mouseX - tooltipWidth - tooltipOffset
164+
: mouseX + tooltipOffset;
165+
var canvasY = Math.max(0, mouseY - 150);
166+
canvas.attr("transform", "translate(" + canvasX + "," + canvasY + ")");
158167
}
159168
},
160169
};

app/assets/javascripts/kanaui/kiddo/helper.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
(function (Kiddo, d3) {
22
Kiddo.Helper = function () {
3+
var formatValue = function (d) {
4+
return d % 1 === 0 ? d3.format(",d")(d) : d3.format(",.2f")(d);
5+
};
36
var formatCurrency = function (d) {
47
return "$" + formatValue(d);
58
};
6-
var formatValue = d3.format(",.2f");
79

810
var humanizeSegment = function (segment) {
911
segment = String(segment || "")
@@ -30,33 +32,64 @@
3032
.join(" ");
3133
};
3234

33-
var formatSeriesName = function (name) {
35+
var formatSeriesName = function (name, reportName) {
3436
return String(name || "")
3537
.split(/\s*::\s*/)
38+
.filter(function (segment) {
39+
// Remove numeric-only segments (tenant record id)
40+
return !/^\d+$/.test(segment.trim());
41+
})
3642
.map(function (segment) {
3743
var parts = segment.split(/\s*:\s*/);
3844
if (parts.length > 1) {
39-
return humanizeSegment(parts[0]) + " (" + humanizeSegment(parts.slice(1).join(": ")) + ")";
45+
var label = (reportName && parts[0].trim().toLowerCase() === "count")
46+
? reportName
47+
: humanizeSegment(parts[0]);
48+
return label + " (" + humanizeSegment(parts.slice(1).join(": ")) + ")";
4049
}
41-
4250
return humanizeSegment(segment);
4351
})
4452
.filter(function (segment) {
4553
return segment.length > 0;
4654
})
47-
.join(" / ");
55+
.join(" : ");
56+
};
57+
58+
// Extracts just the qualifier part (e.g. "EUR" from "count: EUR :: 1") for compact tooltip labels
59+
var formatSeriesLabel = function (name) {
60+
var segments = String(name || "")
61+
.split(/\s*::\s*/)
62+
.filter(function (segment) {
63+
return !/^\d+$/.test(segment.trim());
64+
});
65+
66+
if (segments.length === 0) return humanizeSegment(name);
67+
68+
var parts = segments[0].split(/\s*:\s*/);
69+
if (parts.length > 1) {
70+
return humanizeSegment(parts.slice(1).join(": "));
71+
}
72+
return humanizeSegment(segments[0]);
73+
};
74+
75+
var parseDateFn = d3.timeParse("%Y-%m-%d");
76+
var formatDateFn = d3.timeFormat("%b %d, %Y");
77+
var formatDate = function (dateStr) {
78+
var parsed = parseDateFn(dateStr);
79+
return parsed ? formatDateFn(parsed) : dateStr;
4880
};
4981

5082
return {
51-
parseDate: d3.timeParse("%Y-%m-%d"),
83+
parseDate: parseDateFn,
5284
bisectDate: d3.bisector(function (d) {
5385
return d.x;
5486
}).left,
5587
formatCurrency: formatCurrency,
5688
formatValue: formatValue,
5789
formatSeriesName: formatSeriesName,
90+
formatDate: formatDate,
5891
formatValueDisplay: function (name, d) {
59-
return formatSeriesName(name) + ": " + formatValue(d.y); // Add currency boolean on backend later -- formatCurrency(d.y); }
92+
return formatSeriesLabel(name) + ": " + formatValue(d.y);
6093
},
6194
};
6295
};

app/assets/javascripts/kanaui/kiddo/settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
raw_height: raw_height,
2020
width: raw_width - margin_left - margin_right,
2121
height: raw_height - margin_top - margin_bottom,
22+
reportName: $("#chartAnchor").data("report-name") || "",
2223
};
2324
};
2425
})((window.Kiddo = window.Kiddo || {}), d3);

app/assets/stylesheets/kanaui/kanaui.css

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,40 @@
103103

104104
.kenui-analytics-dashboard-index .well ul {
105105
list-style-type: none;
106-
margin-top: 0.625rem;
106+
margin: 0.625rem 0 0;
107107
background: #fafafa;
108-
width: fit-content;
109-
padding: 0.625rem;
108+
width: 100%;
109+
padding: 1rem 1.25rem;
110110
border-radius: 0.5rem;
111+
display: flex;
112+
flex-wrap: wrap;
113+
gap: 0.5rem 1.25rem;
114+
align-items: flex-start;
115+
}
116+
117+
/* Smoothing option links (Weekly/Monthly average/sum) render as inline pills */
118+
.kenui-analytics-dashboard-index .well > ul > li:not(.advanced-controls):not(:has(.query-label)) {
119+
display: inline-flex;
120+
}
121+
122+
.kenui-analytics-dashboard-index .well > ul > li:not(.advanced-controls):not(:has(.query-label)) > a {
123+
display: inline-flex;
124+
align-items: center;
125+
background: #ffffff;
126+
border: 0.0625rem solid #d5d7da;
127+
border-radius: 999px;
128+
padding: 0.375rem 0.75rem;
129+
text-decoration: none;
130+
color: #414651;
131+
font-weight: 500;
132+
font-size: 0.8125rem;
133+
line-height: 1.25rem;
134+
transition: all 0.15s ease-in-out;
135+
}
136+
137+
.kenui-analytics-dashboard-index .well > ul > li:not(.advanced-controls):not(:has(.query-label)) > a:hover {
138+
border-color: #1570ef;
139+
color: #1570ef;
111140
}
112141

113142
.kenui-analytics-dashboard-index .well ul li a {
@@ -168,6 +197,26 @@
168197
background: #ffffff;
169198
}
170199

200+
.kenui-analytics-dashboard-index .chart-title-container {
201+
width: auto;
202+
height: auto;
203+
display: block;
204+
justify-content: unset;
205+
align-items: unset;
206+
position: static;
207+
margin-top: 15px;
208+
}
209+
210+
.kenui-analytics-dashboard-index .chart-title {
211+
font-weight: 600;
212+
font-size: 1.25rem;
213+
line-height: 1.75rem;
214+
color: #111827;
215+
white-space: normal;
216+
transform: none;
217+
text-align: left;
218+
}
219+
171220
.kenui-analytics-dashboard-index #chartAnchor svg {
172221
background: #ffffff;
173222
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
@@ -619,20 +668,32 @@
619668
.kenui-analytics-dashboard-index .advanced-controls {
620669
margin: 0.625rem 0;
621670
padding: 0;
671+
flex: 1 1 100%;
622672
}
623673

624674
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
625-
background: #fafafa;
675+
background: #ffffff;
626676
border: 0.0625rem solid #e9eaeb;
627677
border-radius: 0.5rem;
628-
padding: 0.625rem;
678+
padding: 1rem 1.25rem;
629679
margin-top: 0.625rem;
680+
display: grid;
681+
grid-template-columns: repeat(2, minmax(0, 1fr));
682+
gap: 1rem 1.5rem;
683+
align-items: start;
684+
}
685+
686+
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal > .form-group:last-of-type {
687+
grid-column: 1 / -1;
688+
justify-self: end;
689+
margin-top: 0;
630690
}
631691

632692
.kenui-analytics-dashboard-index .advanced-controls fieldset {
633693
border: none;
634694
margin: 0;
635695
padding: 0;
696+
min-width: 0;
636697
}
637698

638699
.kenui-analytics-dashboard-index .advanced-controls legend {
@@ -647,10 +708,10 @@
647708
}
648709

649710
.kenui-analytics-dashboard-index .advanced-controls .form-group {
650-
margin-bottom: 1rem;
711+
margin-bottom: 0.75rem;
651712
display: flex;
652713
flex-direction: column;
653-
align-items: center;
714+
align-items: stretch;
654715
}
655716

656717
.kenui-analytics-dashboard-index .advanced-controls .control-label {
@@ -675,7 +736,7 @@
675736

676737
.kenui-analytics-dashboard-index .advanced-controls select.form-control {
677738
height: 6rem;
678-
min-width: 16rem;
739+
min-width: 0;
679740
width: 100%;
680741
}
681742

@@ -716,14 +777,15 @@
716777

717778
/* Current Analytics Query Section */
718779
.kenui-analytics-dashboard-index .well ul li:has(.query-label) {
719-
background: #f8f9fa;
780+
background: #ffffff;
720781
border: 0.0625rem solid #e9eaeb;
721782
border-radius: 0.5rem;
722-
padding: 0.625rem;
723-
margin: 0.625rem 0;
783+
padding: 0.75rem 1rem;
784+
margin: 0;
724785
display: flex;
725786
flex-direction: column;
726-
gap: 0.75rem;
787+
gap: 0.5rem;
788+
flex: 1 1 100%;
727789
}
728790

729791
.kenui-analytics-dashboard-index .well ul li:has(.query-label) a {
@@ -770,6 +832,10 @@
770832

771833
/* Responsive adjustments for smaller screens */
772834
@media (max-width: 768px) {
835+
.kenui-analytics-dashboard-index .advanced-controls .form-horizontal {
836+
grid-template-columns: 1fr;
837+
}
838+
773839
.kenui-analytics-dashboard-index .advanced-controls .form-group {
774840
flex-direction: column;
775841
align-items: flex-start;

0 commit comments

Comments
 (0)