({ series, valueKey, label, formatter, min = null, max = null })
| 892 | } |
| 893 | |
| 894 | function lineChart({ series, valueKey, label, formatter, min = null, max = null }) { |
| 895 | const values = series |
| 896 | .map((point, index) => ({ |
| 897 | index, |
| 898 | label: point.label || point.minute || String(index + 1), |
| 899 | value: toNumber(point[valueKey]), |
| 900 | })) |
| 901 | .filter((point) => point.value !== null); |
| 902 | |
| 903 | if (values.length < 2) { |
| 904 | return emptyBlock(`Not enough data to draw ${label.toLowerCase()}.`); |
| 905 | } |
| 906 | |
| 907 | const width = 620; |
| 908 | const height = 220; |
| 909 | const padding = 24; |
| 910 | const yMin = min !== null ? min : Math.min(...values.map((point) => point.value)); |
| 911 | const yMax = max !== null ? max : Math.max(...values.map((point) => point.value)); |
| 912 | const yRange = Math.max(yMax - yMin, 0.0001); |
| 913 | const xStep = (width - padding * 2) / Math.max(values.length - 1, 1); |
| 914 | |
| 915 | const points = values.map((point, index) => { |
| 916 | const x = padding + index * xStep; |
| 917 | const y = height - padding - ((point.value - yMin) / yRange) * (height - padding * 2); |
| 918 | return { ...point, x, y }; |
| 919 | }); |
| 920 | |
| 921 | const polyline = points.map((point) => `${point.x},${point.y}`).join(" "); |
| 922 | |
| 923 | return ` |
| 924 | <svg class="svg-chart" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeHtml(label)}"> |
| 925 | <line x1="${padding}" y1="${height - padding}" x2="${width - padding}" y2="${height - padding}" stroke="currentColor" opacity="0.15"></line> |
| 926 | <polyline points="${polyline}" fill="none" stroke="currentColor" stroke-width="3"></polyline> |
| 927 | ${points |
| 928 | .map( |
| 929 | (point) => `<circle cx="${point.x}" cy="${point.y}" r="3.5" fill="currentColor"><title>${escapeHtml(point.label)}: ${escapeHtml(formatter(point.value))}</title></circle>` |
| 930 | ) |
| 931 | .join("")} |
| 932 | </svg> |
| 933 | <div class="chart-caption"> |
| 934 | <span>${escapeHtml(label)}</span> |
| 935 | <span>${escapeHtml(formatter(points[0].value))} to ${escapeHtml(formatter(points[points.length - 1].value))}</span> |
| 936 | </div> |
| 937 | `; |
| 938 | } |
| 939 | |
| 940 | function compareLineChart({ runs, valueKey, label, formatter, min = null, max = null }) { |
| 941 | const datasets = runs.map((run, index) => { |
no test coverage detected