vendor: 13,969 bytes, lines 1-380
1/* * 2 * 3 * Highcharts Border Radius module 4 * 5 * Author: Torstein Honsi 6 * 7 * License: www.highcharts.com/license 8 * 9 * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! 10 * 11 * */ 12'use strict'; 13import D from '../Core/Defaults.js'; 14const { defaultOptions } = D; 15import H from '../Core/Globals.js'; 16const { noop } = H; 17import U from '../Core/Utilities.js'; 18const { addEvent, extend, isObject, merge, relativeLength } = U; 19/* * 20 * 21 * Constants 22 * 23 * */ 24const defaultBorderRadiusOptions = { 25 radius: 0, 26 scope: 'stack', 27 where: void 0 28}; 29/* * 30 * 31 * Variables 32 * 33 * */ 34let oldArc = noop; 35let oldRoundedRect = noop; 36/* * 37 * 38 * Functions 39 * 40 * */ 41/** 42 * @private 43 */ 44function applyBorderRadius(path, i, r) { 45 const a = path[i]; 46 let b = path[i + 1]; 47 if (b[0] === 'Z') { 48 b = path[0]; 49 } 50 let line, arc, fromLineToArc; 51 // From straight line to arc 52 if ((a[0] === 'M' || a[0] === 'L') && b[0] === 'A') { 53 line = a; 54 arc = b; 55 fromLineToArc = true; 56 // From arc to straight line 57 } 58 else if (a[0] === 'A' && (b[0] === 'M' || b[0] === 'L')) { 59 line = b; 60 arc = a; 61 } 62 if (line && arc && arc.params) { 63 const bigR = arc[1], 64 // In our use cases, outer pie slice arcs are clockwise and inner 65 // arcs (donut/sunburst etc) are anti-clockwise 66 clockwise = arc[5], params = arc.params, { start, end, cx, cy } = params; 67 // Some geometric constants 68 const relativeR = clockwise ? (bigR - r) : (bigR + r), 69 // The angle, on the big arc, that the border radius arc takes up 70 angleOfBorderRadius = relativeR ? Math.asin(r / relativeR) : 0, angleOffset = clockwise ? 71 angleOfBorderRadius : 72 -angleOfBorderRadius, 73 // The distance along the radius of the big arc to the starting 74 // point of the small border radius arc 75 distanceBigCenterToStartArc = (Math.cos(angleOfBorderRadius) * 76 relativeR); 77 // From line to arc 78 if (fromLineToArc) { 79 // Update the cache 80 params.start = start + angleOffset; 81 // First move to the start position at the radial line. We want to 82 // start one borderRadius closer to the center. 83 line[1] = cx + distanceBigCenterToStartArc * Math.cos(start); 84 line[2] = cy + distanceBigCenterToStartArc * Math.sin(start); 85 // Now draw an arc towards the point where the small circle touches 86 // the great circle. 87 path.splice(i + 1, 0, [ 88 'A', 89 r, 90 r, 91 0, // Slanting, 92 0, // Long arc 93 1, // Clockwise 94 cx + bigR * Math.cos(params.start), 95 cy + bigR * Math.sin(params.start) 96 ]); 97 // From arc to line 98 } 99 else { 100 // Update the cache 101 params.end = end - angleOffset; 102 // End the big arc a bit earlier 103 arc[6] = cx + bigR * Math.cos(params.end); 104 arc[7] = cy + bigR * Math.sin(params.end); 105 // Draw a small arc towards a point on the end angle, but one 106 // borderRadius closer to the center relative to the perimeter. 107 path.splice(i + 1, 0, [ 108 'A', 109 r, 110 r, 111 0, 112 0, 113 1, 114 cx + distanceBigCenterToStartArc * Math.cos(end), 115 cy + distanceBigCenterToStartArc * Math.sin(end) 116 ]); 117 } 118 // Long or short arc must be reconsidered because we have modified the 119 // start and end points 120 arc[4] = Math.abs(params.end - params.start) < Math.PI ? 0 : 1; 121 } 122} 123/** 124 * Extend arc with borderRadius. 125 * @private 126 */ 127function arc(x, y, w, h, options = {}) { 128 const path = oldArc(x, y, w, h, options), { innerR = 0, r = w, start = 0, end = 0 } = options; 129 if (options.open || !options.borderRadius) { 130 return path; 131 } 132 const alpha = end - start, sinHalfAlpha = Math.sin(alpha / 2), borderRadius = Math.max(Math.min(relativeLength(options.borderRadius || 0, r - innerR), 133 // Cap to half the sector radius 134 (r - innerR) / 2, 135 // For smaller pie slices, cap to the largest small circle that 136 // can be fitted within the sector 137 (r * sinHalfAlpha) / (1 + sinHalfAlpha)), 0), 138 // For the inner radius, we need an extra cap because the inner arc 139 // is shorter than the outer arc 140 innerBorderRadius = Math.min(borderRadius, 2 * (alpha / Math.PI) * innerR); 141 // Apply turn-by-turn border radius. Start at the end since we're 142 // splicing in arc segments. 143 let i = path.length - 1; 144 while (i--) { 145 applyBorderRadius(path, i, i > 1 ? innerBorderRadius : borderRadius); 146 } 147 return path; 148} 149/** @private */ 150function seriesOnAfterColumnTranslate() { 151 if (this.options.borderRadius && 152 !(this.chart.is3d && this.chart.is3d())) { 153 const { options, yAxis } = this, percent = options.stacking === 'percent', seriesDefault = defaultOptions.plotOptions?.[this.type] 154 ?.borderRadius, borderRadius = optionsToObject(options.borderRadius, isObject(seriesDefault) ? seriesDefault : {}), reversed = yAxis.options.reversed; 155 for (const point of this.points) { 156 const { shapeArgs } = point; 157 if (point.shapeType === 'roundedRect' && shapeArgs) { 158 const { width = 0, height = 0, y = 0 } = shapeArgs; 159 let brBoxY = y, brBoxHeight = height; 160 // It would be nice to refactor StackItem.getStackBox/ 161 // setOffset so that we could get a reliable box out of 162 // it. Currently it is close if we remove the label 163 // offset, but we still need to run crispCol and also 164 // flip it if inverted, so atm it is simpler to do it 165 // like the below. 166 if (borderRadius.scope === 'stack' && 167 point.stackTotal) { 168 const stackEnd = yAxis.translate(percent ? 100 : point.stackTotal, false, true, false, true), stackThreshold = yAxis.translate(options.threshold || 0, false, true, false, true), box = this.crispCol(0, Math.min(stackEnd, stackThreshold), 0, Math.abs(stackEnd - stackThreshold)); 169 brBoxY = box.y; 170 brBoxHeight = box.height; 171 } 172 const flip = (point.negative ? -1 : 1) * 173 (reversed ? -1 : 1) === -1; 174 // Handle the where option 175 let where = borderRadius.where; 176 // Waterfall, hanging columns should have rounding on 177 // all sides 178 if (!where && 179 this.is('waterfall') && 180 Math.abs((point.yBottom || 0) - 181 (this.translatedThreshold || 0)) > this.borderWidth) { 182 where = 'all'; 183 } 184 if (!where) { 185 where = 'end'; 186 } 187 // Get the radius 188 const r = Math.min(relativeLength(borderRadius.radius, width), width / 2, 189 // Cap to the height, but not if where is `end` 190 where === 'all' ? height / 2 : Infinity) || 0; 191 // If the `where` option is 'end', cut off the 192 // rectangles by making the border-radius box one r 193 // greater, so that the imaginary radius falls outside 194 // the rectangle. 195 if (where === 'end') { 196 if (flip) { 197 brBoxY -= r; 198 brBoxHeight += r; 199 } 200 else { 201 brBoxHeight += r; 202 } 203 } 204 extend(shapeArgs, { brBoxHeight, brBoxY, r }); 205 } 206 } 207 } 208} 209/** @private */ 210function compose(SeriesClass, SVGElementClass, SVGRendererClass) { 211 const PieSeriesClass = SeriesClass.types.pie; 212 if (!SVGElementClass.symbolCustomAttribs.includes('borderRadius')) { 213 const symbols = SVGRendererClass.prototype.symbols; 214 addEvent(SeriesClass, 'afterColumnTranslate', seriesOnAfterColumnTranslate, { 215 // After columnrange and polar column modifications 216 order: 9 217 }); 218 addEvent(PieSeriesClass, 'afterTranslate', pieSeriesOnAfterTranslate); 219 SVGElementClass.symbolCustomAttribs.push('borderRadius', 'brBoxHeight', 'brBoxY'); 220 oldArc = symbols.arc; 221 oldRoundedRect = symbols.roundedRect; 222 symbols.arc = arc; 223 symbols.roundedRect = roundedRect; 224 } 225} 226/** @private */ 227function optionsToObject(options, seriesBROptions) { 228 if (!isObject(options)) { 229 options = { radius: options || 0 }; 230 } 231 return merge(defaultBorderRadiusOptions, seriesBROptions, options); 232} 233/** @private */ 234function pieSeriesOnAfterTranslate() { 235 const borderRadius = optionsToObject(this.options.borderRadius); 236 for (const point of this.points) { 237 const shapeArgs = point.shapeArgs; 238 if (shapeArgs) { 239 shapeArgs.borderRadius = relativeLength(borderRadius.radius, (shapeArgs.r || 0) - ((shapeArgs.innerR) || 0)); 240 } 241 } 242} 243/** 244 * Extend roundedRect with individual cutting through rOffset. 245 * @private 246 */ 247function roundedRect(x, y, width, height, options = {}) { 248 const path = oldRoundedRect(x, y, width, height, options), { r = 0, brBoxHeight = height, brBoxY = y } = options, brOffsetTop = y - brBoxY, brOffsetBtm = (brBoxY + brBoxHeight) - (y + height), 249 // When the distance to the border-radius box is greater than the r 250 // itself, it means no border radius. The -0.1 accounts for float 251 // rounding errors. 252 rTop = (brOffsetTop - r) > -0.1 ? 0 : r, rBtm = (brOffsetBtm - r) > -0.1 ? 0 : r, cutTop = Math.max(rTop && brOffsetTop, 0), cutBtm = Math.max(rBtm && brOffsetBtm, 0); 253 /* 254 255 The naming of control points: 256 257 / a -------- b \ 258 / \ 259 h c 260 | | 261 | | 262 | | 263 g d 264 \ / 265 \ f -------- e / 266 267 */ 268 const a = [x + rTop, y], b = [x + width - rTop, y], c = [x + width, y + rTop], d = [ 269 x + width, y + height - rBtm 270 ], e = [ 271 x + width - rBtm, 272 y + height 273 ], f = [x + rBtm, y + height], g = [x, y + height - rBtm], h = [x, y + rTop]; 274 const applyPythagoras = (r, altitude) => Math.sqrt(Math.pow(r, 2) - Math.pow(altitude, 2)); 275 // Inside stacks, cut off part of the top 276 if (cutTop) { 277 const base = applyPythagoras(rTop, rTop - cutTop); 278 a[0] -= base; 279 b[0] += base; 280 c[1] = h[1] = y + rTop - cutTop; 281 } 282 // Column is lower than the radius. Cut off bottom inside the top 283 // radius. 284 if (height < rTop - cutTop) { 285 const base = applyPythagoras(rTop, rTop - cutTop - height); 286 c[0] = d[0] = x + width - rTop + base; 287 e[0] = Math.min(c[0], e[0]); 288 f[0] = Math.max(d[0], f[0]); 289 g[0] = h[0] = x + rTop - base; 290 c[1] = h[1] = y + height; 291 } 292 // Inside stacks, cut off part of the bottom 293 if (cutBtm) { 294 const base = applyPythagoras(rBtm, rBtm - cutBtm); 295 e[0] += base; 296 f[0] -= base; 297 d[1] = g[1] = y + height - rBtm + cutBtm; 298 } 299 // Cut off top inside the bottom radius 300 if (height < rBtm - cutBtm) { 301 const base = applyPythagoras(rBtm, rBtm - cutBtm - height); 302 c[0] = d[0] = x + width - rBtm + base; 303 b[0] = Math.min(c[0], b[0]); 304 a[0] = Math.max(d[0], a[0]); 305 g[0] = h[0] = x + rBtm - base; 306 d[1] = g[1] = y; 307 } 308 // Preserve the box for data labels 309 path.length = 0; 310 path.push(['M', ...a], 311 // Top side 312 ['L', ...b], 313 // Top right corner 314 ['A', rTop, rTop, 0, 0, 1, ...c], 315 // Right side 316 ['L', ...d], 317 // Bottom right corner 318 ['A', rBtm, rBtm, 0, 0, 1, ...e], 319 // Bottom side 320 ['L', ...f], 321 // Bottom left corner 322 ['A', rBtm, rBtm, 0, 0, 1, ...g], 323 // Left side 324 ['L', ...h], 325 // Top left corner 326 ['A', rTop, rTop, 0, 0, 1, ...a], ['Z']); 327 return path; 328} 329/* * 330 * 331 * Default Export 332 * 333 * */ 334const BorderRadius = { 335 compose, 336 optionsToObject 337}; 338export default BorderRadius; 339/* * 340 * 341 * API Declarations 342 * 343 * */ 344/** 345 * Detailed options for border radius. 346 * 347 * @sample {highcharts} highcharts/plotoptions/column-borderradius/ 348 * Rounded columns 349 * @sample highcharts/plotoptions/series-border-radius 350 * Column and pie with rounded border 351 * 352 * @interface Highcharts.BorderRadiusOptionsObject 353 */ /** 354* The border radius. A number signifies pixels. A percentage string, like for 355* example `50%`, signifies a relative size. For columns this is relative to the 356* column width, for pies it is relative to the radius and the inner radius. 357* 358* @name Highcharts.BorderRadiusOptionsObject#radius 359* @type {string|number} 360*/ /** 361* The scope of the rounding for column charts. In a stacked column chart, the 362* value `point` means each single point will get rounded corners. The value 363* `stack` means the rounding will apply to the full stack, so that only points 364* close to the top or bottom will receive rounding. 365* 366* @name Highcharts.BorderRadiusOptionsObject#scope 367* @validvalue ["point", "stack"] 368* @type {string} 369*/ /** 370* For column charts, where in the point or stack to apply rounding. The `end` 371* value means only those corners at the point value will be rounded, leaving 372* the corners at the base or threshold unrounded. This is the most intuitive 373* behaviour. The `all` value means also the base will be rounded. 374* 375* @name Highcharts.BorderRadiusOptionsObject#where 376* @validvalue ["all", "end"] 377* @type {string} 378* @default end 379*/ 380(''); // Keeps doclets above in JS file
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.