|
@@ -16,41 +16,101 @@ public class AchievementExamSprintReportSvgChartBuilder {
|
|
|
String beforeText,
|
|
String beforeText,
|
|
|
String afterLabel,
|
|
String afterLabel,
|
|
|
double afterValue,
|
|
double afterValue,
|
|
|
- String afterText,
|
|
|
|
|
- String fillColor) {
|
|
|
|
|
|
|
+ String afterText,
|
|
|
|
|
+ String fillColor) {
|
|
|
double maxValue = Math.max(Math.max(beforeValue, afterValue), 1d);
|
|
double maxValue = Math.max(Math.max(beforeValue, afterValue), 1d);
|
|
|
- int beforeHeight = barHeight(beforeValue, maxValue);
|
|
|
|
|
- int afterHeight = barHeight(afterValue, maxValue);
|
|
|
|
|
|
|
+ int axisLeft = 58;
|
|
|
|
|
+ int axisBottom = 180;
|
|
|
|
|
+ int axisTop = 36;
|
|
|
|
|
+ int axisRight = 324;
|
|
|
|
|
+ int beforeX = 118;
|
|
|
|
|
+ int afterX = 222;
|
|
|
|
|
+ int barWidth = 58;
|
|
|
|
|
+ int plotHeight = axisBottom - axisTop;
|
|
|
|
|
+ int beforeHeight = barHeight(beforeValue, maxValue, plotHeight);
|
|
|
|
|
+ int afterHeight = barHeight(afterValue, maxValue, plotHeight);
|
|
|
|
|
|
|
|
return new StringBuilder()
|
|
return new StringBuilder()
|
|
|
.append("<svg class='achievement-bar-chart ").append(safeCssClass(cssClass))
|
|
.append("<svg class='achievement-bar-chart ").append(safeCssClass(cssClass))
|
|
|
.append("' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 360 220' role='img' aria-label='")
|
|
.append("' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 360 220' role='img' aria-label='")
|
|
|
.append(escape(ariaLabel)).append("'>")
|
|
.append(escape(ariaLabel)).append("'>")
|
|
|
- .append("<line class='chart-axis' x1='44' y1='180' x2='318' y2='180' stroke='#d5dde8' stroke-width='1'/>")
|
|
|
|
|
- .append("<rect class='chart-bar chart-bar-before' x='92' y='").append(180 - beforeHeight)
|
|
|
|
|
- .append("' width='64' height='").append(beforeHeight)
|
|
|
|
|
|
|
+ .append(renderAxisGridAndTicks(maxValue, axisLeft, axisRight, axisTop, axisBottom))
|
|
|
|
|
+ .append("<rect class='chart-bar chart-bar-before' x='").append(beforeX)
|
|
|
|
|
+ .append("' y='").append(axisBottom - beforeHeight)
|
|
|
|
|
+ .append("' width='").append(barWidth).append("' height='").append(beforeHeight)
|
|
|
.append("' rx='10' ry='10' fill='#9fb3c8'/>")
|
|
.append("' rx='10' ry='10' fill='#9fb3c8'/>")
|
|
|
- .append("<rect class='chart-bar chart-bar-after' x='204' y='").append(180 - afterHeight)
|
|
|
|
|
- .append("' width='64' height='").append(afterHeight)
|
|
|
|
|
|
|
+ .append("<rect class='chart-bar chart-bar-after' x='").append(afterX)
|
|
|
|
|
+ .append("' y='").append(axisBottom - afterHeight)
|
|
|
|
|
+ .append("' width='").append(barWidth).append("' height='").append(afterHeight)
|
|
|
.append("' rx='10' ry='10' fill='").append(safeColor(fillColor)).append("'/>")
|
|
.append("' rx='10' ry='10' fill='").append(safeColor(fillColor)).append("'/>")
|
|
|
- .append("<text class='chart-value' x='124' y='").append(Math.max(18, 170 - beforeHeight))
|
|
|
|
|
|
|
+ .append("<text class='chart-value' x='").append(beforeX + barWidth / 2)
|
|
|
|
|
+ .append("' y='").append(Math.max(18, axisBottom - 10 - beforeHeight))
|
|
|
.append("' text-anchor='middle'>").append(escape(beforeText)).append("</text>")
|
|
.append("' text-anchor='middle'>").append(escape(beforeText)).append("</text>")
|
|
|
- .append("<text class='chart-value' x='236' y='").append(Math.max(18, 170 - afterHeight))
|
|
|
|
|
|
|
+ .append("<text class='chart-value' x='").append(afterX + barWidth / 2)
|
|
|
|
|
+ .append("' y='").append(Math.max(18, axisBottom - 10 - afterHeight))
|
|
|
.append("' text-anchor='middle'>").append(escape(afterText)).append("</text>")
|
|
.append("' text-anchor='middle'>").append(escape(afterText)).append("</text>")
|
|
|
- .append("<text class='chart-label' x='124' y='202' text-anchor='middle'>")
|
|
|
|
|
|
|
+ .append("<text class='chart-label' x='").append(beforeX + barWidth / 2)
|
|
|
|
|
+ .append("' y='").append(axisBottom + 22).append("' text-anchor='middle'>")
|
|
|
.append(escape(beforeLabel)).append("</text>")
|
|
.append(escape(beforeLabel)).append("</text>")
|
|
|
- .append("<text class='chart-label' x='236' y='202' text-anchor='middle'>")
|
|
|
|
|
|
|
+ .append("<text class='chart-label' x='").append(afterX + barWidth / 2)
|
|
|
|
|
+ .append("' y='").append(axisBottom + 22).append("' text-anchor='middle'>")
|
|
|
.append(escape(afterLabel)).append("</text>")
|
|
.append(escape(afterLabel)).append("</text>")
|
|
|
.append("</svg>")
|
|
.append("</svg>")
|
|
|
.toString();
|
|
.toString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private int barHeight(double value, double maxValue) {
|
|
|
|
|
|
|
+ private String renderAxisGridAndTicks(double maxValue, int axisLeft, int axisRight, int axisTop, int axisBottom) {
|
|
|
|
|
+ double[] tickValues = {maxValue, maxValue / 2d, 0d};
|
|
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
|
|
+ for (double tickValue : tickValues) {
|
|
|
|
|
+ int y = tickY(tickValue, maxValue, axisTop, axisBottom);
|
|
|
|
|
+ builder.append("<line class='chart-grid-line' x1='").append(axisLeft)
|
|
|
|
|
+ .append("' y1='").append(y)
|
|
|
|
|
+ .append("' x2='").append(axisRight)
|
|
|
|
|
+ .append("' y2='").append(y)
|
|
|
|
|
+ .append("' stroke='#edf2f7' stroke-width='1'/>")
|
|
|
|
|
+ .append("<line class='chart-tick' x1='").append(axisLeft - 4)
|
|
|
|
|
+ .append("' y1='").append(y)
|
|
|
|
|
+ .append("' x2='").append(axisLeft)
|
|
|
|
|
+ .append("' y2='").append(y)
|
|
|
|
|
+ .append("' stroke='#a9b4c2' stroke-width='1'/>")
|
|
|
|
|
+ .append("<text class='chart-tick-label' x='").append(axisLeft - 8)
|
|
|
|
|
+ .append("' y='").append(y + 4)
|
|
|
|
|
+ .append("' text-anchor='end'>")
|
|
|
|
|
+ .append(formatTickLabel(tickValue))
|
|
|
|
|
+ .append("</text>");
|
|
|
|
|
+ }
|
|
|
|
|
+ builder.append("<line class='chart-y-axis' x1='").append(axisLeft)
|
|
|
|
|
+ .append("' y1='").append(axisTop)
|
|
|
|
|
+ .append("' x2='").append(axisLeft)
|
|
|
|
|
+ .append("' y2='").append(axisBottom)
|
|
|
|
|
+ .append("' stroke='#9aa6b2' stroke-width='1'/>")
|
|
|
|
|
+ .append("<line class='chart-x-axis' x1='").append(axisLeft)
|
|
|
|
|
+ .append("' y1='").append(axisBottom)
|
|
|
|
|
+ .append("' x2='").append(axisRight)
|
|
|
|
|
+ .append("' y2='").append(axisBottom)
|
|
|
|
|
+ .append("' stroke='#9aa6b2' stroke-width='1'/>");
|
|
|
|
|
+ return builder.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int tickY(double value, double maxValue, int axisTop, int axisBottom) {
|
|
|
|
|
+ if (maxValue <= 0d) {
|
|
|
|
|
+ return axisBottom;
|
|
|
|
|
+ }
|
|
|
|
|
+ double ratio = Math.max(0d, Math.min(1d, value / maxValue));
|
|
|
|
|
+ return axisBottom - (int) Math.round(ratio * (axisBottom - axisTop));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String formatTickLabel(double value) {
|
|
|
|
|
+ return String.format(Locale.ROOT, "%.0f", Math.max(0d, value));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int barHeight(double value, double maxValue, int plotHeight) {
|
|
|
if (value <= 0d || maxValue <= 0d) {
|
|
if (value <= 0d || maxValue <= 0d) {
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
double ratio = Math.max(0d, Math.min(1d, value / maxValue));
|
|
double ratio = Math.max(0d, Math.min(1d, value / maxValue));
|
|
|
- return Math.max(18, (int) Math.round(ratio * 130d));
|
|
|
|
|
|
|
+ return (int) Math.round(ratio * plotHeight);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private String safeCssClass(String value) {
|
|
private String safeCssClass(String value) {
|