From 5155a240a34fd165c844b1278c79d9321ba31c9a Mon Sep 17 00:00:00 2001 From: CadenH Date: Mon, 30 Jun 2025 01:24:21 -0700 Subject: [PATCH] add corner radius for regular polygon --- src/Util.ts | 42 +++++++++++++++++++++++++++++ src/shapes/RegularPolygon.ts | 45 ++++++++++++++++++++++++++++---- test/unit/RegularPolygon-test.ts | 28 ++++++++++++++++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/Util.ts b/src/Util.ts index ae5075a5..0891118b 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -1042,4 +1042,46 @@ export const Util = { context.lineTo(0, topLeft); context.arc(topLeft, topLeft, topLeft, Math.PI, (Math.PI * 3) / 2, false); }, + drawRoundedPolygonPath( + context: Context, + points: Vector2d[], + sides: number, + radius: number, + cornerRadius: number | number[] + ) { + for (let i = 0; i < sides; i++) { + const prev = points[(i - 1 + sides) % sides]; + const curr = points[i]; + const next = points[(i + 1) % sides]; + const vec1 = {x: curr.x - prev.x, y: curr.y - prev.y}; + const vec2 = {x: next.x - curr.x, y: next.y - curr.y}; + const len1 = Math.hypot(vec1.x, vec1.y); + const len2 = Math.hypot(vec2.x, vec2.y); + let currCornerRadius; + if (typeof cornerRadius === 'number') { + currCornerRadius = cornerRadius; + } else { + currCornerRadius = i < cornerRadius.length ? cornerRadius[i] : 0; + } + const maxCornerRadius = radius * Math.cos(Math.PI / sides); + // cornerRadius creates perfect circle at 1/2 radius + currCornerRadius = maxCornerRadius * Math.min(1, (currCornerRadius / radius) * 2); + const normalVec1 = {x: vec1.x / len1, y: vec1.y / len1}; + const normalVec2 = {x: vec2.x / len2, y: vec2.y / len2}; + const p1 = { + x: curr.x - normalVec1.x * currCornerRadius, + y: curr.y - normalVec1.y * currCornerRadius, + }; + const p2 = { + x: curr.x + normalVec2.x * currCornerRadius, + y: curr.y + normalVec2.y * currCornerRadius, + }; + if (i === 0) { + context.moveTo(p1.x, p1.y); + } else { + context.lineTo(p1.x, p1.y); + } + context.arcTo(curr.x, curr.y, p2.x, p2.y, currCornerRadius); + } + } }; diff --git a/src/shapes/RegularPolygon.ts b/src/shapes/RegularPolygon.ts index 6ade36cf..c1bb627d 100644 --- a/src/shapes/RegularPolygon.ts +++ b/src/shapes/RegularPolygon.ts @@ -1,13 +1,15 @@ import { Factory } from '../Factory'; import { Shape, ShapeConfig } from '../Shape'; import { GetSet, Vector2d } from '../types'; -import { getNumberValidator } from '../Validators'; +import { getNumberOrArrayOfNumbersValidator, getNumberValidator } from '../Validators'; import { _registerNode } from '../Global'; import { Context } from '../Context'; +import { Util } from '../Util'; export interface RegularPolygonConfig extends ShapeConfig { sides: number; radius: number; + cornerRadius?: number | number[]; } /** * RegularPolygon constructor. Examples include triangles, squares, pentagons, hexagons, etc. @@ -15,6 +17,7 @@ export interface RegularPolygonConfig extends ShapeConfig { * @memberof Konva * @augments Konva.Shape * @param {Object} config + * @param {Number} [config.cornerRadius] * @param {Number} config.sides * @param {Number} config.radius * @@shapeParams @@ -32,13 +35,20 @@ export interface RegularPolygonConfig extends ShapeConfig { */ export class RegularPolygon extends Shape { _sceneFunc(context: Context) { - const points = this._getPoints(); + const points = this._getPoints(), + radius = this.radius(), + sides = this.sides(), + cornerRadius = this.cornerRadius(); context.beginPath(); - context.moveTo(points[0].x, points[0].y); - for (let n = 1; n < points.length; n++) { - context.lineTo(points[n].x, points[n].y); + if (!cornerRadius) { + context.moveTo(points[0].x, points[0].y); + for (let n = 1; n < points.length; n++) { + context.lineTo(points[n].x, points[n].y); + } + } else { + Util.drawRoundedPolygonPath(context, points, sides, radius, cornerRadius); } context.closePath(); @@ -91,6 +101,7 @@ export class RegularPolygon extends Shape { radius: GetSet; sides: GetSet; + cornerRadius: GetSet; } RegularPolygon.prototype.className = 'RegularPolygon'; @@ -127,3 +138,27 @@ Factory.addGetterSetter(RegularPolygon, 'radius', 0, getNumberValidator()); * shape.sides(10); */ Factory.addGetterSetter(RegularPolygon, 'sides', 0, getNumberValidator()); + +/** + * get/set corner radius + * @method + * @name Konva.RegularPolygon#cornerRadius + * @param {Number} cornerRadius + * @returns {Number} + * @example + * // get corner radius + * var cornerRadius = rect.cornerRadius(); + * + * // set corner radius + * rect.cornerRadius(10); + * + * // set different corner radius values + * // top-left, top-right, bottom-right, bottom-left + * rect.cornerRadius([0, 10, 20, 30]); + */ +Factory.addGetterSetter( + RegularPolygon, + 'cornerRadius', + 0, + getNumberOrArrayOfNumbersValidator(4) +); diff --git a/test/unit/RegularPolygon-test.ts b/test/unit/RegularPolygon-test.ts index 8e996f80..1370eb33 100644 --- a/test/unit/RegularPolygon-test.ts +++ b/test/unit/RegularPolygon-test.ts @@ -5,6 +5,8 @@ import { Konva, cloneAndCompareLayer, assertAlmostEqual, + createCanvas, + compareLayerAndCanvas, } from './test-utils'; describe('RegularPolygon', function () { @@ -206,4 +208,30 @@ describe('RegularPolygon', function () { assertAlmostEqual(box.width, 91.60254037844388); assertAlmostEqual(box.height, 80.00000000000003); }); + + // ====================================================== + it('limit corner radius', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var poly = new Konva.RegularPolygon({ + x: 50, + y: 50, + sides: 5, + radius: 50, + fill: 'black', + cornerRadius: 25, + }); + + layer.add(poly); + stage.add(layer); + + // corner radius creates perfect circle at 1/2 radius + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.arc(100, 100, 50, 0, Math.PI * 2); + context.fillStyle = 'black'; + context.fill(); + compareLayerAndCanvas(layer, canvas, 100); + }); });