From 02ccf5fe1a62ae136bbb62f7118cd94e7d4a58b4 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Wed, 11 May 2022 19:52:28 +0200 Subject: [PATCH] Added rounded corners to Paid mix --- .../dashboard/v5/charts/paid-mix.js | 222 +++++++++++++++++- 1 file changed, 217 insertions(+), 5 deletions(-) diff --git a/ghost/admin/app/components/dashboard/v5/charts/paid-mix.js b/ghost/admin/app/components/dashboard/v5/charts/paid-mix.js index 57f1f4009f..886417f3ca 100644 --- a/ghost/admin/app/components/dashboard/v5/charts/paid-mix.js +++ b/ghost/admin/app/components/dashboard/v5/charts/paid-mix.js @@ -1,3 +1,5 @@ +/* globals Chart */ + import Component from '@glimmer/component'; import {action} from '@ember/object'; import {inject as service} from '@ember/service'; @@ -11,6 +13,190 @@ const MODE_OPTIONS = [{ value: 'tiers' }]; +// Custom ChartJS rounded rectangle +Chart.elements.Rectangle.prototype.draw = function () { + var ctx = this._chart.ctx; + var vm = this._view; + var left, right, top, bottom, borderSkipped, radius; + + // If radius is less than 0 or is large enough to cause drawing errors a max + // radius is imposed. If cornerRadius is not defined set it to 0. + var cornerRadius = this._chart.config.options.cornerRadius; + var fullCornerRadius = this._chart.config.options.fullCornerRadius; + var stackedRounded = this._chart.config.options.stackedRounded; + var typeOfChart = this._chart.config.type; + + if (cornerRadius < 0) { + cornerRadius = 0; + } + if (typeof cornerRadius === 'undefined') { + cornerRadius = 0; + } + if (typeof fullCornerRadius === 'undefined') { + fullCornerRadius = true; + } + if (typeof stackedRounded === 'undefined') { + stackedRounded = false; + } + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + borderSkipped = vm.borderSkipped || 'left'; + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [left, bottom], + [left, top], + [right, top], + [right, bottom] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + var nextCornerId, width, height, x, y; + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + nextCornerId = i + 1; + if (nextCornerId === 4) { + nextCornerId = 0; + } + + width = corners[2][0] - corners[1][0]; + height = corners[0][1] - corners[1][1]; + x = corners[1][0]; + y = corners[1][1]; + + radius = cornerRadius; + // Fix radius being too large + if (radius > Math.abs(height) / 2) { + radius = Math.floor(Math.abs(height) / 2); + } + if (radius > Math.abs(width) / 2) { + radius = Math.floor(Math.abs(width) / 2); + } + + var xTL, xTR, yTL, yTR, xBL, xBR, yBL, yBR; + if (width < 0) { + // Negative values in a horizontal bar chart + xTL = x + width; + xTR = x; + yTL = y; + yTR = y; + + xBL = x + width; + xBR = x; + yBL = y + height; + yBR = y + height; + + // Draw + ctx.moveTo(xBL + radius, yBL); + ctx.lineTo(xBR - radius, yBR); + + // Bottom right corner + fullCornerRadius ? ctx.quadraticCurveTo(xBR, yBR, xBR, yBR - radius) : ctx.lineTo(xBR, yBR, xBR, yBR - radius); + ctx.lineTo(xTR, yTR + radius); + + // top right Corner + fullCornerRadius ? ctx.quadraticCurveTo(xTR, yTR, xTR - radius, yTR) : ctx.lineTo(xTR, yTR, xTR - radius, yTR); + ctx.lineTo(xTL + radius, yTL); + + // top left corner + ctx.quadraticCurveTo(xTL, yTL, xTL, yTL + radius); + ctx.lineTo(xBL, yBL - radius); + + // bttom left corner + ctx.quadraticCurveTo(xBL, yBL, xBL + radius, yBL); + } else { + var lastVisible = 0; + for (var findLast = 0, findLastTo = this._chart.data.datasets.length; findLast < findLastTo; findLast++) { + if (!this._chart.getDatasetMeta(findLast).hidden) { + lastVisible = findLast; + } + } + var rounded = this._datasetIndex === lastVisible; + + if (rounded) { + //Positive Value + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + + // top right + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + + // bottom right + if (fullCornerRadius || typeOfChart === 'horizontalBar') { + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + } else { + ctx.lineTo(x + width, y + height, x + width - radius, y + height); + } + + ctx.lineTo(x + radius, y + height); + + // bottom left + if (fullCornerRadius) { + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + } else { + ctx.lineTo(x, y + height, x, y + height - radius); + } + + ctx.lineTo(x, y + radius); + + // top left + if (fullCornerRadius || typeOfChart === 'bar') { + ctx.quadraticCurveTo(x, y, x + radius, y); + } else { + ctx.lineTo(x, y, x + radius, y); + } + } else { + ctx.moveTo(x, y); + ctx.lineTo(x + width, y); + ctx.lineTo(x + width, y + height); + ctx.lineTo(x, y + height); + ctx.lineTo(x, y); + } + } + } + + ctx.fill(); +}; + +// Chart.defaults.roundedBar = Chart.defaults.bar; +// Chart.controllers.roundedBar = Chart.controllers.bar.extend({ +// dataElementType: Chart.elements.RoundedRectangle +// }); + export default class PaidMix extends Component { @service dashboardStats; @@ -91,11 +277,12 @@ export default class PaidMix extends Component { } if (this.mode === 'cadence') { + // The first value has to be negative to make rounded corners work return { labels: ['Cadence'], datasets: [{ label: 'Monthly', - data: [monthlyPercentage], + data: [-monthlyPercentage], backgroundColor: '#8E42FF', barThickness }, { @@ -119,6 +306,11 @@ export default class PaidMix extends Component { let datasets = []; for (let i = 0; i < data.length; i++) { let tierPercentage = Math.round(data[i] / totalTiersAmount * 100); + + // The first value has to be negative to make rounded corners work + if (i === 0) { + tierPercentage = -tierPercentage; + } datasets.push({ data: [tierPercentage], label: labels[i], @@ -134,9 +326,28 @@ export default class PaidMix extends Component { } get chartOptions() { + let minTickValue, maxTickValue, ticksY; + + if (this.mode === 'cadence') { + const totalCadence = this.dashboardStats.paidMembersByCadence.month + this.dashboardStats.paidMembersByCadence.year; + minTickValue = -(Math.round(this.dashboardStats.paidMembersByCadence.month / totalCadence * 100)); + maxTickValue = Math.round(this.dashboardStats.paidMembersByCadence.year / totalCadence * 100); + ticksY = { + display: false, + min: minTickValue, + max: maxTickValue + }; + } else { + ticksY = { + display: false + }; + } + return { responsive: true, maintainAspectRatio: false, + cornerRadius: 50, + fullCornerRadius: false, legend: { display: false }, @@ -192,7 +403,10 @@ export default class PaidMix extends Component { tooltipTextEl.innerHTML = 'Currently has no data'; } else { const label = data.datasets[tooltipItems.datasetIndex].label || ''; - const value = data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index] || 0; + var value = data.datasets[tooltipItems.datasetIndex].data[tooltipItems.index] || 0; + if (value < 0) { + value = -value; + } tooltipTextEl.innerHTML = `${value}%${label}`; } }, @@ -216,9 +430,7 @@ export default class PaidMix extends Component { gridLines: { display: false }, - ticks: { - display: false - } + ticks: ticksY }] } };