Merge pull request #1940 from Caden-Hornyak/feature/regular-polygon-corner-radius
Some checks failed
Test Browser / build (20.x) (push) Has been cancelled
Test NodeJS / build (23.x) (push) Has been cancelled

Add corner radius for Regular Polygon
This commit is contained in:
Anton Lavrenov 2025-08-11 06:54:12 -05:00 committed by GitHub
commit 7b9ccd18ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 139 additions and 5 deletions

View File

@ -1076,4 +1076,47 @@ export const Util = {
false
);
},
drawRoundedPolygonPath(
context: Context,
points: Vector2d[],
sides: number,
radius: number,
cornerRadius: number | number[]
) {
radius = Math.abs(radius);
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);
}
}
};

View File

@ -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<RegularPolygonConfig> {
_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<RegularPolygonConfig> {
radius: GetSet<number, this>;
sides: GetSet<number, this>;
cornerRadius: GetSet<number | number[], this>;
}
RegularPolygon.prototype.className = 'RegularPolygon';
@ -127,3 +138,26 @@ 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 = poly.cornerRadius();
*
* // set corner radius
* poly.cornerRadius(10);
*
* // set different corner radius values (pentagon)
* poly.cornerRadius([0, 10, 20, 30, 40]);
*/
Factory.addGetterSetter(
RegularPolygon,
'cornerRadius',
0,
getNumberOrArrayOfNumbersValidator(4)
);

View File

@ -5,6 +5,8 @@ import {
Konva,
cloneAndCompareLayer,
assertAlmostEqual,
createCanvas,
compareLayerAndCanvas,
} from './test-utils';
describe('RegularPolygon', function () {
@ -206,4 +208,59 @@ 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 sides = 5;
var radius = 50;
var poly = new Konva.RegularPolygon({
x: 100,
y: 100,
sides: sides,
radius: radius,
fill: 'black',
cornerRadius: 25,
});
var resultCircleRadius = radius * Math.cos(Math.PI / sides);
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, resultCircleRadius, 0, Math.PI * 2);
context.fillStyle = 'black';
context.fill();
compareLayerAndCanvas(layer, canvas, 200);
});
// ======================================================
it('negative polygon radius with cornerRadius', function () {
var stage = addStage();
var layer = new Konva.Layer();
var poly = new Konva.RegularPolygon({
x: 100,
y: 100,
sides: 5,
radius: -100,
fill: 'black',
cornerRadius: 20,
});
layer.add(poly);
stage.add(layer);
layer.draw();
var trace = layer.getContext().getTrace();
assert.equal(
trace,
'clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();moveTo(26.18,80.979);arcTo(0,100,-26.18,80.979,32.361);lineTo(-68.925,49.923);arcTo(-95.106,30.902,-85.106,0.125,32.361);lineTo(-68.779,-50.125);arcTo(-58.779,-80.902,-26.418,-80.902,32.361);lineTo(26.418,-80.902);arcTo(58.779,-80.902,68.779,-50.125,32.361);lineTo(85.106,0.125);arcTo(95.106,30.902,68.925,49.923,32.361);closePath();fillStyle=black;fill();restore();clearRect(0,0,578,200);save();transform(1,0,0,1,100,100);beginPath();moveTo(26.18,80.979);arcTo(0,100,-26.18,80.979,32.361);lineTo(-68.925,49.923);arcTo(-95.106,30.902,-85.106,0.125,32.361);lineTo(-68.779,-50.125);arcTo(-58.779,-80.902,-26.418,-80.902,32.361);lineTo(26.418,-80.902);arcTo(58.779,-80.902,68.779,-50.125,32.361);lineTo(85.106,0.125);arcTo(95.106,30.902,68.925,49.923,32.361);closePath();fillStyle=black;fill();restore();'
);
});
});