|
1 | 1 | import defaults from '../core/core.defaults.js';
|
2 |
| -import {_longestText, addRoundedRectPath, renderText} from '../helpers/helpers.canvas.js'; |
| 2 | +import {_longestText, addRoundedRectPath, renderText, _isPointInArea} from '../helpers/helpers.canvas.js'; |
3 | 3 | import {HALF_PI, TAU, toDegrees, toRadians, _normalizeAngle, PI} from '../helpers/helpers.math.js';
|
4 | 4 | import LinearScaleBase from './scale.linearbase.js';
|
5 | 5 | import Ticks from '../core/core.ticks.js';
|
@@ -136,36 +136,66 @@ function updateLimits(limits, orig, angle, hLimits, vLimits) {
|
136 | 136 | }
|
137 | 137 | }
|
138 | 138 |
|
| 139 | +function createPointLabelItem(scale, index, itemOpts) { |
| 140 | + const outerDistance = scale.drawingArea; |
| 141 | + const {extra, additionalAngle, padding, size} = itemOpts; |
| 142 | + const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle); |
| 143 | + const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI))); |
| 144 | + const y = yForAngle(pointLabelPosition.y, size.h, angle); |
| 145 | + const textAlign = getTextAlignForAngle(angle); |
| 146 | + const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign); |
| 147 | + return { |
| 148 | + // if to draw or overlapped |
| 149 | + visible: true, |
| 150 | + |
| 151 | + // Text position |
| 152 | + x: pointLabelPosition.x, |
| 153 | + y, |
| 154 | + |
| 155 | + // Text rendering data |
| 156 | + textAlign, |
| 157 | + |
| 158 | + // Bounding box |
| 159 | + left, |
| 160 | + top: y, |
| 161 | + right: left + size.w, |
| 162 | + bottom: y + size.h |
| 163 | + }; |
| 164 | +} |
| 165 | + |
| 166 | +function isNotOverlapped(item, area) { |
| 167 | + if (!area) { |
| 168 | + return true; |
| 169 | + } |
| 170 | + const {left, top, right, bottom} = item; |
| 171 | + const apexesInArea = _isPointInArea({x: left, y: top}, area) || _isPointInArea({x: left, y: bottom}, area) || |
| 172 | + _isPointInArea({x: right, y: top}, area) || _isPointInArea({x: right, y: bottom}, area); |
| 173 | + return !apexesInArea; |
| 174 | +} |
| 175 | + |
139 | 176 | function buildPointLabelItems(scale, labelSizes, padding) {
|
140 | 177 | const items = [];
|
141 | 178 | const valueCount = scale._pointLabels.length;
|
142 | 179 | const opts = scale.options;
|
143 |
| - const extra = getTickBackdropHeight(opts) / 2; |
144 |
| - const outerDistance = scale.drawingArea; |
145 |
| - const additionalAngle = opts.pointLabels.centerPointLabels ? PI / valueCount : 0; |
| 180 | + const {centerPointLabels, display} = opts.pointLabels; |
| 181 | + const itemOpts = { |
| 182 | + extra: getTickBackdropHeight(opts) / 2, |
| 183 | + additionalAngle: centerPointLabels ? PI / valueCount : 0 |
| 184 | + }; |
| 185 | + let area; |
146 | 186 |
|
147 | 187 | for (let i = 0; i < valueCount; i++) {
|
148 |
| - const pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + padding[i], additionalAngle); |
149 |
| - const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI))); |
150 |
| - const size = labelSizes[i]; |
151 |
| - const y = yForAngle(pointLabelPosition.y, size.h, angle); |
152 |
| - const textAlign = getTextAlignForAngle(angle); |
153 |
| - const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign); |
154 |
| - |
155 |
| - items.push({ |
156 |
| - // Text position |
157 |
| - x: pointLabelPosition.x, |
158 |
| - y, |
159 |
| - |
160 |
| - // Text rendering data |
161 |
| - textAlign, |
162 |
| - |
163 |
| - // Bounding box |
164 |
| - left, |
165 |
| - top: y, |
166 |
| - right: left + size.w, |
167 |
| - bottom: y + size.h |
168 |
| - }); |
| 188 | + itemOpts.padding = padding[i]; |
| 189 | + itemOpts.size = labelSizes[i]; |
| 190 | + |
| 191 | + const item = createPointLabelItem(scale, i, itemOpts); |
| 192 | + items.push(item); |
| 193 | + if (display === 'auto') { |
| 194 | + item.visible = isNotOverlapped(item, area); |
| 195 | + if (item.visible) { |
| 196 | + area = item; |
| 197 | + } |
| 198 | + } |
169 | 199 | }
|
170 | 200 | return items;
|
171 | 201 | }
|
@@ -198,39 +228,49 @@ function yForAngle(y, h, angle) {
|
198 | 228 | return y;
|
199 | 229 | }
|
200 | 230 |
|
| 231 | +function drawPointLabelBox(ctx, opts, item) { |
| 232 | + const {left, top, right, bottom} = item; |
| 233 | + const {backdropColor} = opts; |
| 234 | + |
| 235 | + if (!isNullOrUndef(backdropColor)) { |
| 236 | + const borderRadius = toTRBLCorners(opts.borderRadius); |
| 237 | + const padding = toPadding(opts.backdropPadding); |
| 238 | + ctx.fillStyle = backdropColor; |
| 239 | + |
| 240 | + const backdropLeft = left - padding.left; |
| 241 | + const backdropTop = top - padding.top; |
| 242 | + const backdropWidth = right - left + padding.width; |
| 243 | + const backdropHeight = bottom - top + padding.height; |
| 244 | + |
| 245 | + if (Object.values(borderRadius).some(v => v !== 0)) { |
| 246 | + ctx.beginPath(); |
| 247 | + addRoundedRectPath(ctx, { |
| 248 | + x: backdropLeft, |
| 249 | + y: backdropTop, |
| 250 | + w: backdropWidth, |
| 251 | + h: backdropHeight, |
| 252 | + radius: borderRadius, |
| 253 | + }); |
| 254 | + ctx.fill(); |
| 255 | + } else { |
| 256 | + ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight); |
| 257 | + } |
| 258 | + } |
| 259 | +} |
| 260 | + |
201 | 261 | function drawPointLabels(scale, labelCount) {
|
202 | 262 | const {ctx, options: {pointLabels}} = scale;
|
203 | 263 |
|
204 | 264 | for (let i = labelCount - 1; i >= 0; i--) {
|
| 265 | + const item = scale._pointLabelItems[i]; |
| 266 | + if (!item.visible) { |
| 267 | + // overlapping |
| 268 | + continue; |
| 269 | + } |
205 | 270 | const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
|
| 271 | + drawPointLabelBox(ctx, optsAtIndex, item); |
206 | 272 | const plFont = toFont(optsAtIndex.font);
|
207 |
| - const {x, y, textAlign, left, top, right, bottom} = scale._pointLabelItems[i]; |
208 |
| - const {backdropColor} = optsAtIndex; |
209 |
| - |
210 |
| - if (!isNullOrUndef(backdropColor)) { |
211 |
| - const borderRadius = toTRBLCorners(optsAtIndex.borderRadius); |
212 |
| - const padding = toPadding(optsAtIndex.backdropPadding); |
213 |
| - ctx.fillStyle = backdropColor; |
214 |
| - |
215 |
| - const backdropLeft = left - padding.left; |
216 |
| - const backdropTop = top - padding.top; |
217 |
| - const backdropWidth = right - left + padding.width; |
218 |
| - const backdropHeight = bottom - top + padding.height; |
219 |
| - |
220 |
| - if (Object.values(borderRadius).some(v => v !== 0)) { |
221 |
| - ctx.beginPath(); |
222 |
| - addRoundedRectPath(ctx, { |
223 |
| - x: backdropLeft, |
224 |
| - y: backdropTop, |
225 |
| - w: backdropWidth, |
226 |
| - h: backdropHeight, |
227 |
| - radius: borderRadius, |
228 |
| - }); |
229 |
| - ctx.fill(); |
230 |
| - } else { |
231 |
| - ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight); |
232 |
| - } |
233 |
| - } |
| 273 | + const {x, y, textAlign} = item; |
234 | 274 |
|
235 | 275 | renderText(
|
236 | 276 | ctx,
|
|
0 commit comments