diff --git a/src/_FullInternals.ts b/src/_FullInternals.ts index d257f4cd8..926f8539c 100644 --- a/src/_FullInternals.ts +++ b/src/_FullInternals.ts @@ -7,6 +7,7 @@ import { Arc } from './shapes/Arc'; import { Arrow } from './shapes/Arrow'; import { Circle } from './shapes/Circle'; import { Ellipse } from './shapes/Ellipse'; +import { EllipseRing } from './shapes/EllipseRing'; import { Image } from './shapes/Image'; import { Label, Tag } from './shapes/Label'; import { Line } from './shapes/Line'; @@ -47,6 +48,7 @@ export const Konva = Core.Util._assign(Core, { Arrow, Circle, Ellipse, + EllipseRing, Image, Label, Tag, diff --git a/src/index-types.d.ts b/src/index-types.d.ts index 77ae5a1cb..cd6ce8c82 100644 --- a/src/index-types.d.ts +++ b/src/index-types.d.ts @@ -110,6 +110,8 @@ declare namespace Konva { export const Ellipse: typeof import('./shapes/Ellipse').Ellipse; export type Ellipse = import('./shapes/Ellipse').Ellipse; export type EllipseConfig = import('./shapes/Ellipse').EllipseConfig; + export type EllipseRing = import('./shapes/EllipseRing').EllipseRing; + export type EllipseRingConfig = import('./shapes/EllipseRing').EllipseRingConfig; export const Image: typeof import('./shapes/Image').Image; export type Image = import('./shapes/Image').Image; export type ImageConfig = import('./shapes/Image').ImageConfig; diff --git a/src/shapes/EllipseRing.ts b/src/shapes/EllipseRing.ts new file mode 100644 index 000000000..68ada44af --- /dev/null +++ b/src/shapes/EllipseRing.ts @@ -0,0 +1,143 @@ +import { Factory } from '../Factory'; +import { Shape, ShapeConfig } from '../Shape'; +import { GetSet } from '../types'; +import { getNumberValidator } from '../Validators'; +import { _registerNode } from '../Global'; +import { Context } from '../Context'; + +export interface EllipseRingConfig extends ShapeConfig { + innerRadiusX: number; + innerRadiusY: number; + outerRadiusX: number; + outerRadiusY: number; +} + +const PIx2 = Math.PI * 2; + +/** + * EllipseRing constructor + * @constructor + * @augments Konva.Shape + * @memberof Konva + * @param {Object} config + * @param {Number} config.innerRadiusX + * @param {Number} config.innerRadiusY + * @param {Number} config.outerRadiusX + * @param {Number} config.outerRadiusY + * @@shapeParams + * @@nodeParams + * @example + * var EllipseRing = new Konva.EllipseRing({ + * innerRadiusX: 40, + * innerRadiusY: 20, + * outerRadiusX: 80, + * outerRadiusY: 40, + * fill: 'red', + * stroke: 'black', + * strokeWidth: 5 + * }); + */ +export class EllipseRing extends Shape { + + _sceneFunc(context: Context) { + const irx = this.innerRadiusX(); + const iry = this.innerRadiusY(); + const orx = this.outerRadiusX(); + const ory = this.outerRadiusY(); + + context.beginPath(); + // context.ellipse(0, 0, orx - ringWidth, ory - ringWidth, 0, 0, PIx2, false); + context.ellipse(0, 0, irx, iry, 0, 0, PIx2, false); + context.moveTo(orx, 0); + context.ellipse(0, 0, orx, ory, 0, PIx2, 0, true); + + context.closePath(); + context.fillStrokeShape(this); + } + + getWidth() { + return this.outerRadiusX() * 2; + } + + getHeight() { + return this.outerRadiusY() * 2; + } + + setWidth(width: number) { + this.outerRadiusX(width / 2); + } + + setHeight(height: number) { + this.outerRadiusY(height / 2); + } + + innerRadiusX: GetSet; + innerRadiusY: GetSet; + outerRadiusX: GetSet; + outerRadiusY: GetSet; +} + +EllipseRing.prototype.className = 'EllipseRing'; +EllipseRing.prototype._centroid = true; +EllipseRing.prototype._attrsAffectingSize = ['innerRadiusX', 'innerRadiusY', 'outerRadiusX', 'outerRadiusY']; +_registerNode(EllipseRing); + +/** + * get/set innerRadiusX + * @method + * @name Konva.Ring#innerRadiusX + * @param {Number} innerRadiusX + * @returns {Number} + * @example + * // get inner radiusX + * var innerRadiusX = ring.innerRadiusX(); + * + * // set inner radiusX + * ring.innerRadiusX(20); + */ +Factory.addGetterSetter(EllipseRing, 'innerRadiusX', 0, getNumberValidator()); + +/** + * get/set innerRadiusY + * @method + * @name Konva.Ring#innerRadiusY + * @param {Number} innerRadiusY + * @returns {Number} + * @example + * // get inner radiusY + * var innerRadiusY = ring.innerRadiusY(); + * + * // set inner radiusY + * ring.innerRadiusY(20); + */ +Factory.addGetterSetter(EllipseRing, 'innerRadiusY', 0, getNumberValidator()); + +/** + * get/set outerRadiusX + * @name Konva.Ring#outerRadiusX + * @method + * @param {Number} outerRadiusX + * @returns {Number} + * @example + * // get outer radiusX + * var outerRadiusX = ring.outerRadiusX(); + * + * // set outer radiusX + * ring.outerRadiusX(20); + */ +Factory.addGetterSetter(EllipseRing, 'outerRadiusX', 0, getNumberValidator()); + +/** + * get/set outerRadiusY + * @name Konva.Ring#outerRadiusY + * @method + * @param {Number} outerRadiusY + * @returns {Number} + * @example + * // get outer radiusY + * var outerRadiusY = ring.outerRadiusY(); + * + * // set outer radiusY + * ring.outerRadiusY(20); + */ +Factory.addGetterSetter(EllipseRing, 'outerRadiusY', 0, getNumberValidator()); diff --git a/test/unit-tests.html b/test/unit-tests.html index 1262ea64f..a65a077c9 100644 --- a/test/unit-tests.html +++ b/test/unit-tests.html @@ -33,6 +33,7 @@ import './unit/Text-test.ts'; import './unit/Blob-test.ts'; import './unit/Ellipse-test.ts'; + import './unit/EllipseRing-test.ts'; import './unit/Polygon-test.ts'; import './unit/Spline-test.ts'; import './unit/Sprite-test.ts'; diff --git a/test/unit/EllipseRing-test.ts b/test/unit/EllipseRing-test.ts new file mode 100644 index 000000000..d92314dba --- /dev/null +++ b/test/unit/EllipseRing-test.ts @@ -0,0 +1,122 @@ +import { assert } from 'chai'; + +import { + addStage, + Konva, + createCanvas, + compareLayerAndCanvas, + compareLayers, +} from './test-utils'; + +describe('EllipseRing', function () { + // ====================================================== + it('add ellipse ring', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.EllipseRing({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadiusX: 40, + innerRadiusY: 20, + outerRadiusX: 80, + outerRadiusY: 40, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + assert.equal(ellipse.getClassName(), 'EllipseRing'); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,289,100);beginPath();ellipse(0,0,40,20,0,0,6.283,false);moveTo(80,0);ellipse(0,0,80,40,0,6.283,0,true);closePath();fillStyle=green;fill();lineWidth=8;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + it('attrs sync', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.EllipseRing({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadiusX: 40, + innerRadiusY: 20, + outerRadiusX: 80, + outerRadiusY: 40, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + + assert.equal(ellipse.getWidth(), 160); + assert.equal(ellipse.getHeight(), 80); + + ellipse.setWidth(100); + assert.equal(ellipse.innerRadiusX(), 40); + assert.equal(ellipse.innerRadiusY(), 20); + assert.equal(ellipse.outerRadiusX(), 50); + assert.equal(ellipse.outerRadiusY(), 40); + + ellipse.setHeight(120); + assert.equal(ellipse.innerRadiusX(), 40); + assert.equal(ellipse.innerRadiusY(), 20); + assert.equal(ellipse.outerRadiusX(), 50); + assert.equal(ellipse.outerRadiusY(), 60); + }); + + it('getSelfRect', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.EllipseRing({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadiusX: 40, + innerRadiusY: 20, + outerRadiusX: 80, + outerRadiusY: 40, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + layer.add(ellipse); + stage.add(layer); + + assert.deepEqual(ellipse.getSelfRect(), { + x: -80, + y: -40, + width: 160, + height: 80, + }); + }); + + it('cache', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + var ellipse = new Konva.EllipseRing({ + x: stage.width() / 2, + y: stage.height() / 2, + innerRadiusX: 40, + innerRadiusY: 20, + outerRadiusX: 80, + outerRadiusY: 40, + fill: 'green', + stroke: 'black', + strokeWidth: 8, + }); + + layer.add(ellipse); + stage.add(layer); + + var layer2 = layer.clone(); + stage.add(layer2); + layer2.hide(); + + compareLayers(layer, layer2); + }); +});