g2o-gradient
Version:
g2o LinearGradient and RadialGradient
393 lines (385 loc) • 12.9 kB
JavaScript
/**
* g2o-gradient 1.0.0-alpha.0
* (c) David Geo Holmes david.geo.holmes@gmail.com
* Released under the MIT License.
*/
'use strict';
var g2o = require('g2o');
var g2oReactive = require('g2o-reactive');
let next_unique_id = 0;
const Constants = {
Identifier: 'g2o-gradient-',
/**
* Default amount of vertices to be used for interpreting Arcs and ArcSegments.
*/
Resolution: 12,
uniqueId: function () {
return next_unique_id++;
}
};
class Stop extends g2o.ElementBase {
#offset = g2oReactive.state(0);
#opacity = g2oReactive.state(1);
#color = g2oReactive.state('#fff');
#change = g2o.variable(this);
change$ = this.#change.asObservable();
/**
* @param offset The offset percentage of the stop represented as a zero-to-one value. Default value flip flops from zero-to-one as new stops are created.
* @param color The color of the stop. Default value flip flops from white to black as new stops are created.
* @param opacity The opacity value. Default value is 1, cannot be lower than 0.
*/
constructor(offset, color, opacity) {
super(Constants.Identifier + Constants.uniqueId());
this.offset = typeof offset === 'number' ? offset : Stop.Index <= 0 ? 0 : 1;
this.opacity = typeof opacity === 'number' ? opacity : 1;
this.color = (typeof color === 'string') ? color : Stop.Index <= 0 ? '#fff' : '#000';
Stop.Index = (Stop.Index + 1) % 2;
}
/**
* The current index being referenced for calculating a stop's default offset value.
*/
static Index = 0;
get color() {
return this.#color.get();
}
set color(color) {
this.#color.set(color);
if (this.parent && this.parent instanceof Gradient) {
this.parent._flagStops = true;
}
this.#change.set(this);
}
get offset() {
return this.#offset.get();
}
set offset(offset) {
this.#offset.set(offset);
if (this.parent && this.parent instanceof Gradient) {
this.parent._flagStops = true;
}
this.#change.set(this);
}
get opacity() {
return this.#opacity.get();
}
set opacity(opacity) {
this.#opacity.set(opacity);
if (this.parent && this.parent instanceof Gradient) {
this.parent._flagStops = true;
}
this.#change.set(this);
}
}
/**
*
*/
class Gradient extends g2o.ElementBase {
#refCount = 0;
_flagStops = false;
#spreadMethod = g2oReactive.state('pad');
#units = g2oReactive.state('userSpaceOnUse');
#stops = g2oReactive.state([]);
_change = g2o.variable(this);
change$ = this._change.asObservable();
constructor(stops = [], options = {}) {
super(ensure_identifier(options));
if (typeof options.spreadMethod === 'string') {
this.spreadMethod = options.spreadMethod;
}
if (typeof options.units === 'string') {
this.units = options.units;
}
this.#stops = g2oReactive.state(map_to_stops(stops));
}
dispose() {
super.dispose();
}
serialize() {
return `url(#${this.id})`;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
render(viewDOM, defs) {
this.zzz.disposables.push(g2oReactive.effect(() => {
while (viewDOM.getLastChild(this.zzz.viewee)) {
viewDOM.removeChild(this.zzz.viewee, viewDOM.getLastChild(this.zzz.viewee));
}
const stops = this.stops;
const N = stops.length;
for (let i = 0; i < N; i++) {
const stop = stops[i];
{
const attrs = { id: stop.id };
const stopElement = viewDOM.createSVGElement('stop', attrs);
stop.zzz.viewee = stopElement;
viewDOM.appendChild(this.zzz.viewee, stopElement);
stop.zzz.disposables.push(g2oReactive.effect(() => {
viewDOM.setAttribute(stopElement, 'offset', 100 * stop.offset + '%');
}));
stop.zzz.disposables.push(g2oReactive.effect(() => {
viewDOM.setAttribute(stopElement, 'stop-color', stop.color);
}));
stop.zzz.disposables.push(g2oReactive.effect(() => {
viewDOM.setAttribute(stopElement, 'stop-opacity', `${stop.opacity}`);
}));
}
}
}));
}
incrementUse(viewDOM, defs) {
this.#refCount++;
if (this.#refCount === 1) {
this.render(viewDOM, defs);
}
}
decrementUse(viewDOM, defs) {
this.#refCount--;
if (this.#refCount === 0) {
viewDOM.removeChild(defs, viewDOM.downcast(this.zzz.viewee));
this.zzz.viewee = null;
}
}
update() {
if (this._flagStops) {
this._change.set(this);
}
return this;
}
flagReset(dirtyFlag = false) {
this._flagStops = dirtyFlag;
return this;
}
/**
* Indicates what happens if the gradient starts or ends inside the bounds of the target rectangle.
* @see {@link https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementSpreadMethodAttribute} for more information
*/
get spreadMethod() {
return this.#spreadMethod.get();
}
set spreadMethod(spread) {
this.#spreadMethod.set(spread);
}
get stops() {
return this.#stops.get();
}
set stops(stops) {
this.#stops.set(stops);
}
/**
* Indicates how coordinate values are interpreted by the renderer.
* @see {@link https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementGradientUnitsAttribute} for more information
*/
get units() {
return this.#units.get();
}
set units(units) {
this.#units.set(units);
}
}
function ensure_identifier(attributes) {
if (typeof attributes.id === 'string') {
return attributes.id;
}
else {
return `${Constants.Identifier}${Constants.uniqueId()}`;
}
}
function map_to_stops(stops) {
const retval = [];
const N = stops.length;
for (let i = 0; i < N; i++) {
const candidate = stops[i];
if (candidate instanceof Stop) {
retval.push(candidate);
}
else if (Array.isArray(candidate)) {
retval.push(new Stop(candidate[0], candidate[1], candidate[2]));
}
else {
throw new Error();
}
}
return retval;
}
class LinearGradient extends Gradient {
#point1;
#point2;
/**
* @param point1 The position of the first end point of the linear gradient.
* @param point2 The position of the second end point of the linear gradient.
* @param stops A list of {@link Stop}s that contain the gradient fill pattern for the gradient.
* The linear gradient lives within the space of the parent object's matrix space.
*/
constructor(point1, point2, stops, options = {}) {
super(stops, options);
this.#point1 = g2o.vector_from_like(point1);
this.#point2 = g2o.vector_from_like(point2);
}
render(viewDOM, defs) {
// If there is no attached DOM element yet,
// create it with all necessary attributes.
if (this.zzz.viewee) ;
else {
{
const changed = {};
changed.id = this.id;
const viewee = viewDOM.createSVGElement('linearGradient', changed);
this.zzz.viewee = viewee;
if (viewDOM.getParentNode(viewee) === null) {
viewDOM.appendChild(defs, viewee);
}
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.x1 = `${this.point1.x}`;
change.y1 = `${this.point1.y}`;
viewDOM.setAttributes(viewee, change);
}));
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.x2 = `${this.point2.x}`;
change.y2 = `${this.point2.y}`;
viewDOM.setAttributes(viewee, change);
}));
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.gradientUnits = this.units;
viewDOM.setAttributes(viewee, change);
}));
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.spreadMethod = this.spreadMethod;
viewDOM.setAttributes(viewee, change);
}));
super.render(viewDOM, defs);
}
}
return this.flagReset();
}
update() {
if (this._flagStops) {
this._change.set(this);
}
return this;
}
flagReset(dirtyFlag = false) {
super.flagReset(dirtyFlag);
return this;
}
get point1() {
return this.#point1;
}
set point1(point1) {
if (point1 instanceof g2o.G20) {
this.#point1.copyVector(point1);
}
}
get point2() {
return this.#point2;
}
set point2(point2) {
if (point2 instanceof g2o.G20) {
this.#point2.copyVector(point2);
}
}
}
class RadialGradient extends Gradient {
#center;
#radius = g2oReactive.state(1);
#focal;
/**
*
* @param center The position of the origin of the radial gradient.
* @param stops A list of {@link Stop}s that contain the gradient fill pattern for the gradient.
* @param options
*/
constructor(center, stops = [], options = {}) {
super(stops, options);
this.#center = g2o.vector_from_like(center);
if (typeof options.radius === 'number') {
this.#radius.set(options.radius);
}
this.#focal = this.center.clone();
if (typeof options.fx === 'number') {
this.focal.x = options.fx;
}
if (typeof options.fy === 'number') {
this.focal.y = options.fy;
}
}
render(viewDOM, defs) {
if (this.zzz.viewee) ;
else {
const changed = {};
changed.id = this.id;
const viewee = viewDOM.createSVGElement('radialGradient', changed);
this.zzz.viewee = viewee;
viewDOM.appendChild(defs, viewee);
super.render(viewDOM, defs);
// center
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.cx = `${this.center.x}`;
change.cy = `${this.center.y}`;
viewDOM.setAttributes(viewee, change);
}));
// focal
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.fx = `${this.focal.x}`;
change.fy = `${this.focal.y}`;
viewDOM.setAttributes(viewee, change);
}));
// gradientUnits
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.gradientUnits = this.units;
viewDOM.setAttributes(viewee, change);
}));
// radius
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.r = `${this.radius}`;
viewDOM.setAttributes(viewee, change);
}));
// spreadMethod
this.zzz.disposables.push(g2oReactive.effect(() => {
const change = {};
change.spreadMethod = this.spreadMethod;
viewDOM.setAttributes(viewee, change);
}));
}
return this.flagReset();
}
update() {
if (this._flagStops) {
this._change.set(this);
}
return this;
}
flagReset(dirtyFlag = false) {
super.flagReset(dirtyFlag);
return this;
}
get center() {
return this.#center;
}
set center(center) {
this.#center.copyVector(center);
}
get focal() {
return this.#focal;
}
set focal(focal) {
this.#focal.copyVector(focal);
}
get radius() {
return this.#radius.get();
}
set radius(radius) {
this.#radius.set(radius);
}
}
exports.Gradient = Gradient;
exports.LinearGradient = LinearGradient;
exports.RadialGradient = RadialGradient;
exports.Stop = Stop;
//# sourceMappingURL=index.js.map