diff --git a/docs/axes/radial/linear.md b/docs/axes/radial/linear.md index a7cd8435eab..839a635b583 100644 --- a/docs/axes/radial/linear.md +++ b/docs/axes/radial/linear.md @@ -125,6 +125,7 @@ Namespace: `options.scales[scaleId].pointLabels` | `color` | [`Color`](../../general/colors.md) | Yes | `Chart.defaults.color` | Color of label. | `font` | `Font` | Yes | `Chart.defaults.font` | See [Fonts](../../general/fonts.md) | `padding` | `number` | Yes | 5 | Padding between chart and point labels. +| `centerPointLabels` | `boolean` | | `false` | if true, point labels are centered. The scriptable context is described in [Options](../../general/options.md#scale) section. diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index cbe37238e66..5eeb13988ac 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -1,6 +1,6 @@ import defaults from '../core/core.defaults'; import {_longestText, renderText} from '../helpers/helpers.canvas'; -import {HALF_PI, isNumber, TAU, toDegrees, toRadians, _normalizeAngle} from '../helpers/helpers.math'; +import {HALF_PI, isNumber, TAU, toDegrees, toRadians, _normalizeAngle, PI} from '../helpers/helpers.math'; import LinearScaleBase from './scale.linearbase'; import Ticks from '../core/core.ticks'; import {valueOrDefault, isArray, isFinite, callback as callCallback, isNullOrUndef} from '../helpers/helpers.core'; @@ -133,12 +133,13 @@ function buildPointLabelItems(scale, labelSizes, padding) { const opts = scale.options; const tickBackdropHeight = getTickBackdropHeight(opts); const outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0; for (let i = 0; i < valueCount; i++) { // Extra pixels out for some label spacing const extra = (i === 0 ? tickBackdropHeight / 2 : 0); - const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i]); - const angle = toDegrees(scale.getIndexAngle(i)); + const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle); + const angle = toDegrees(pointLabelPosition.angle + HALF_PI); const size = labelSizes[i]; const y = yForAngle(pointLabelPosition.y, size.h, angle); const textAlign = getTextAlignForAngle(angle); @@ -404,8 +405,8 @@ export default class RadialLinearScale extends LinearScaleBase { } } - getPointPosition(index, distanceFromCenter) { - const angle = this.getIndexAngle(index) - HALF_PI; + getPointPosition(index, distanceFromCenter, additionalAngle = 0) { + const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle; return { x: Math.cos(angle) * distanceFromCenter + this.xCenter, y: Math.sin(angle) * distanceFromCenter + this.yCenter, @@ -618,7 +619,10 @@ RadialLinearScale.defaults = { }, // Number - Additionl padding between scale and pointLabel - padding: 5 + padding: 5, + + // Boolean - if true, center point labels to slices in polar chart + centerPointLabels: false } }; diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index b2e545b58ae..a7f6d08c49a 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -48,7 +48,8 @@ describe('Test the radial linear scale', function() { size: 10 }, callback: defaultConfig.pointLabels.callback, - padding: 5 + padding: 5, + centerPointLabels: false } }); @@ -590,4 +591,59 @@ describe('Test the radial linear scale', function() { }); }); }); + + it('should correctly get the point positions in center', function() { + var chart = window.acquireChart({ + type: 'polarArea', + data: { + datasets: [{ + data: [10, 5, 0, 25, 78] + }], + labels: ['label1', 'label2', 'label3', 'label4', 'label5'] + }, + options: { + scales: { + r: { + pointLabels: { + display: true, + padding: 5, + centerPointLabels: true + }, + ticks: { + display: false + } + } + } + } + }); + + const PI = Math.PI; + const lavelNum = 5; + const padding = 5; + const pointLabelItems = chart.scales.r._pointLabelItems; + const additionalAngle = PI / lavelNum; + const opts = chart.scales.r.options; + const outerDistance = chart.scales.r.getDistanceFromCenterForValue(opts.ticks.reverse ? chart.scales.r.min : chart.scales.r.max); + const tickBackdropHeight = 0; + const yForAngle = function(y, h, angle) { + if (angle === 90 || angle === 270) { + y -= (h / 2); + } else if (angle > 270 || angle < 90) { + y -= h; + } + return y; + }; + const toDegrees = function(radians) { + return radians * (180 / PI); + }; + + for (var i = 0; i < 5; i++) { + const extra = (i === 0 ? tickBackdropHeight / 2 : 0); + const pointLabelItem = pointLabelItems[i]; + const pointPosition = chart.scales.r.getPointPosition(i, outerDistance + extra + padding, additionalAngle); + expect(pointLabelItem.x).toBe(pointPosition.x); + expect(pointLabelItem.y).toBe(yForAngle(pointPosition.y, 12, toDegrees(pointPosition.angle + PI / 2))); + } + + }); }); diff --git a/types/index.esm.d.ts b/types/index.esm.d.ts index 7d7721a7d4d..a750dbbe88e 100644 --- a/types/index.esm.d.ts +++ b/types/index.esm.d.ts @@ -3342,6 +3342,12 @@ export type RadialLinearScaleOptions = CoreScaleOptions & { * Callback function to transform data labels to point labels. The default implementation simply returns the current string. */ callback: (label: string, index: number) => string | string[] | number | number[]; + + /** + * if true, point labels are centered. + * @default false + */ + centerPointLabels: boolean; }; /**