Animation 3 working, WIP.

master
Ben Burlingham 8 years ago
parent e4bd773eb3
commit 7b13230220
  1. 13
      css/animation2.scss
  2. 47
      css/animation3.scss
  3. 20
      css/animation5.scss
  4. 21
      css/index.scss
  5. 90
      css/style.css
  6. 7
      index.html
  7. 1
      js/animation1.js
  8. 5
      js/animation2.js
  9. 178
      js/animation3.js
  10. 98
      js/animation4.js
  11. 117
      js/animation5.js
  12. 2514
      js/bundle.js
  13. 16
      js/dom.js
  14. 15
      js/index.js
  15. 1
      package.json
  16. 21
      res/palette.svg

@ -0,0 +1,13 @@
.anim2-particle {
$side: 100px;
background: url('../res/seahorse.svg') no-repeat center center;
background-size: 20px 20px;
border-color: salmon;
border-style: dashed;
border-radius: 50px;
border-width: 1px;
height: $side;
position: absolute;
width: $side;
}

@ -0,0 +1,47 @@
.anim3-particle {
$side: 20px;
background: url('../res/seahorse.svg') no-repeat center center #aaa;
background-size: $side $side;
border-radius: $side / 2;
color: #fff;
height: $side;
line-height: $side;
margin: #{-$side / 2} 0 0 #{-$side / 2};
position: absolute;
text-align: center;
width: $side;
z-index: 1;
&::after {
border: 50px solid transparent;
border-right-color: lightgreen;
content: ' ';
height: 0;
left: -30px;
opacity: 0.5;
position: absolute;
top: -40px;
width: 0;
}
}
.anim3-movement-circle {
border: 2px dotted darkturquoise;
position: absolute;
transition: all 0.2s;
z-index: 0;
&:after {
background: darkturquoise;
border-radius: 2px;
content:' ';
height: 4px;
left: 50%;
margin-left: -2px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 4px;
}
}

@ -0,0 +1,20 @@
.anim3-particle {
$side: 20px;
background: url('../res/seahorse.svg') no-repeat center top #aaa;
background-size: 20px 20px;
border-color: purple;
color: #fff;
// border-style: dashed;
border-radius: $side / 2;
// border-width: 1px;
height: $side;
line-height: $side;
position: absolute;
text-align: center;
width: $side;
&.scared {
background: #f00;
}
}

@ -1,26 +1,21 @@
.particles {
background: #ddd;
background: rgba(102, 51, 153, 0.1);
// background: url('../res/seigaiha.svg');
background-size: 100px 50px;
// background-size: 100px 50px;
// border-radius: 50px;
box-shadow: 2px 2px 0 #aaa;
height: 600px;
margin: 10px auto;
position: relative;
width: 600px;
}
.particle {
$side: 100px;
background: url('../res/seahorse.svg') no-repeat center center;
background-size: 20px 20px;
border-color: salmon;
border-style: dashed;
border-radius: 50px;
border-width: 1px;
height: $side;
.palette {
background: url('../res/palette.svg');
background-size: 167px 100px;
height: 100px;
position: absolute;
width: $side;
width: 167px;
}
.highlight {

@ -1,21 +1,24 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0; }
body {
font-family: sans-serif; }
.particles {
background: #ddd;
background-size: 100px 50px;
background: rgba(102, 51, 153, 0.1);
box-shadow: 2px 2px 0 #aaa;
height: 600px;
margin: 10px auto;
position: relative;
width: 600px; }
.particle {
background: url(../res/seahorse.svg) no-repeat center center;
background-size: 20px 20px;
border-color: salmon;
border-style: dashed;
border-radius: 50px;
border-width: 1px;
.palette {
background: url(../res/palette.svg);
background-size: 167px 100px;
height: 100px;
position: absolute;
width: 100px; }
width: 167px; }
.highlight {
animation: pulse 0.5s 1;
@ -32,10 +35,65 @@
height: 0px;
margin: 0;
width: 0px; } }
* {
box-sizing: border-box;
margin: 0;
padding: 0; }
.anim2-particle {
background: url(../res/seahorse.svg) no-repeat center center;
background-size: 20px 20px;
border-color: salmon;
border-style: dashed;
border-radius: 50px;
border-width: 1px;
height: 100px;
position: absolute;
width: 100px; }
.anim3-particle {
background: url(../res/seahorse.svg) no-repeat center center #aaa;
background-size: 20px 20px;
border-radius: 10px;
color: #fff;
height: 20px;
line-height: 20px;
margin: -10px 0 0 -10px;
position: absolute;
text-align: center;
width: 20px;
z-index: 1; }
.anim3-particle::after {
border: 50px solid transparent;
border-right-color: lightgreen;
content: ' ';
height: 0;
left: -30px;
opacity: 0.5;
position: absolute;
top: -40px;
width: 0; }
body {
font-family: sans-serif; }
.anim3-movement-circle {
border: 2px dotted darkturquoise;
position: absolute;
transition: all 0.2s;
z-index: 0; }
.anim3-movement-circle:after {
background: darkturquoise;
border-radius: 2px;
content: ' ';
height: 4px;
left: 50%;
margin-left: -2px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 4px; }
.anim3-particle {
background: url(../res/seahorse.svg) no-repeat center top #aaa;
background-size: 20px 20px;
border-color: purple;
color: #fff;
border-radius: 10px;
height: 20px;
line-height: 20px;
position: absolute;
text-align: center;
width: 20px; }
.anim3-particle.scared {
background: #f00; }

@ -7,11 +7,16 @@
</head>
<body>
<h1>Dust</h1>
<h2>Swarm behavior experiments with RxJs</h2>
<h2>Swarm behavior studies with RxJs</h2>
<hr>
<div class="particles"></div>
The ultimate goal is to simulate particles that move in swarms and calculate their
position independently, similar to flocks of birds.
They should take into account disturbances, walls, individual initiative, and landing areas.
<h2>Animation 1</h2>
Goal: Animate several seahorses over an interval.
Key points:

@ -1,3 +1,4 @@
// Simple frame-based movement.
import Rx, { Observable } from 'rxjs';
const Animation1 = {

@ -1,3 +1,4 @@
// Scare mechanic, single particle.
import Rx, { Observable } from 'rxjs';
import AnimationBase from './animation0';
import DOM from './dom';
@ -6,7 +7,7 @@ import Store from './store';
const evtScare = (scareX, scareY) => new CustomEvent("scare", { detail: { scareX, scareY } });
const particleDiv = document.createElement('div');
particleDiv.className = 'particle';
particleDiv.className = 'anim2-particle';
function checkScare([evt, store]) {
const state = store.get();
@ -125,6 +126,6 @@ function init() {
.subscribe(flee);
};
const Animation2 = Object.assign(AnimationBase, { init, reset });
const Animation2 = Object.assign({}, AnimationBase, { init, reset });
export default Animation2;

@ -1,47 +1,159 @@
// Single particle movement.
// Goal: per-frame decisions
// 20 x 20 grid
// The trickiest portion of this iteration was animating the curved paths. Calculating arc length (for scalar speed) along an elliptical
// geometry is quite difficult, so the current solution uses circular paths, which change radius and sometimes rotation after a random
// number of frames have emitted. A smoothstep cubic curve was considered, but maintaining consistent entry and exit angles will affect
// performance for large particle counts.
import Rx, { Observable } from 'rxjs';
import AnimationBase from './animation0';
import DOM from './dom';
import Store from './store';
const speed = 2;
const visionRadius = 50;
const grid = {};
const movementCircle = document.createElement('div');
movementCircle.className = 'anim3-movement-circle';
const visionCircle = document.createElement('div');
// visionCircle.className = 'anim3-vision-circle';
const particle = document.createElement('div');
particle.className = 'anim3-particle';
function move(store) {
let {
clockwise,
interval,
particleX,
particleY,
radius,
theta
} = store.get();
if (interval <= 0) {
interval = Math.round(Math.random() * 10) + 50;
radius = Math.round(Math.random() * 200) + 50
// IF SEEING A WALL: tight 180
// if ( < visionRadius
// if (Math.random() < 0.3) {
// console.warn("turn!")
// state.radius = 20;
// state.interval = Math.PI * 20 / speed;
// }
detectWall(store);
if (Math.random() < 0.5) {
clockwise = !clockwise;
theta = (theta > Math.PI ? theta - Math.PI : theta + Math.PI);
}
}
interval -= 1;
const Animation2 = {
init: () => {
const container = document.querySelector('.particles');
const { left: containerX, top: containerY } = container.getBoundingClientRect();
const prevTheta = theta;
const delta = speed / radius;
theta += (clockwise ? -delta : delta);
const state = { x: 0, y: 75 };
particleX -= radius * Math.cos(theta) - radius * Math.cos(prevTheta);
particleY -= radius * Math.sin(theta) - radius * Math.sin(prevTheta);
const particleDivs = Array.apply(null, { length: 1 }).map((v, i) => {
const div = document.createElement('div');
div.className = 'particle';
// div.style.top = `${i * 75}px`;
// div.style.left = 0;
div.style.top = `${state.y}px`;
div.style.left = `${state.x}px`;
store.set({
clockwise,
interval,
particleX,
particleY,
radius,
theta
});
}
container.appendChild(div);
return div;
});
function detectWall(store) {
const { particleX, particleY, radius, theta } = store.get();
}
const evtScare = new CustomEvent("scare");
function paintMovementCircle(store) {
// TODO UPDATE ONLY IF THETA CHANGED - MUST HAVE PREVSTATE
const { particleX, particleY, radius, theta } = store.get();
const strFps = Rx.Observable.interval(1000 / 32);
const strParticles = Rx.Observable.from(particleDivs);
const strScare = Rx.Observable.fromEvent(container, 'scare');
movementCircle.style.left = `${particleX - radius - radius * Math.cos(theta)}px`;
movementCircle.style.top = `${particleY - radius + radius * Math.sin(theta)}px`;
movementCircle.style.height = `${2 * radius}px`;
movementCircle.style.width = `${2 * radius}px`;
movementCircle.style.borderRadius = `${radius}px`;
}
strScare.subscribe(console.log)
function paintParticle(store) {
const { clockwise, particleX, particleY, theta } = store.get();
const rad = (clockwise ? theta + Math.PI / 2 : theta - Math.PI / 2);
const strClick = Rx.Observable
.fromEvent(container, 'click');
particle.style.left = `${particleX}px`;
particle.style.top = `${particleY}px`;
particle.style.transform = `rotate(${rad}rad)`;
}
strClick.subscribe(evt => {
const { pageX, pageY } = evt;
const offsetX = pageX - containerX;
const offsetY = pageY - containerY;
const diffX = Math.abs(state.x - offsetX);
const diffY = Math.abs(state.y - offsetY);
function reset() {
while (DOM.container.childNodes.length) {
DOM.container.removeChild(DOM.container.firstChild);
}
if (diffX < 50 && diffY < 50) {
container.dispatchEvent(evtScare);
const store = new Store({
clockwise: false,
interval: 10,
particleX: 300,
particleY: 150,
radius: 150,
theta: Math.PI / 2
});
move(store);
DOM.container.appendChild(particle);
DOM.container.appendChild(movementCircle);
return store;
};
function init() {
const store = reset();
for (let x = 0; x <= 600; x += 5) {
grid[x] = {};
for (let y = 0; y <= 600; y += 5) {
grid[x][y] = { type: null };
if (x === 0 || y === 0 || x === 600 || y === 600) {
grid[x][y] = { type: 'wall' };
}
});
},
}
}
const a = Math.round(Math.random() * 180) + 20;
const b = Math.round(Math.random() * 180) + 20;
const stop$ = Rx.Observable.fromEvent(DOM.container, 'stop');
const fps$ = Rx.Observable.interval(1000 / 32)
.map(_ => store)
// .take(100)
.takeUntil(stop$); console.error("CLICK TO STOP");
const click$ = Rx.Observable.fromEvent(DOM.container, 'click');
click$.subscribe(() => {
DOM.container.dispatchEvent(new CustomEvent('stop'));
});
fps$.subscribe(move);
fps$.subscribe(paintParticle);
// fps$.subscribe(paintMovementCircle);
};
export default Animation2;
const Animation3 = Object.assign({}, AnimationBase, { init, reset });
export default Animation3;

@ -0,0 +1,98 @@
// Join mechanic, multiple particles
// ????? Chase, maybe?
// ???? Occupied?
// called each frame update
// find if going to hit a wall
// find if palette nearby
import Rx, { Observable } from 'rxjs';
import AnimationBase from './animation0';
import DOM from './dom';
import Store from './store';
const evtScare = (detail) => new CustomEvent('scare', { detail });
const [particles, state] = (new Array(5)).fill(null).reduce((acc, v, i) => {
const div = document.createElement('div');
div.className = 'anim3-particle';
div.innerHTML = ''
const x = 0;
const y = i * 25;
div.style.left = 0
div.style.top = `${y}px`;
acc[0].push(div);
acc[1].push({ x, y });
return acc;
}, [[], []]);
const palettes = (new Array(1)).fill(null).reduce((acc, v, i) => {
const div = document.createElement('div');
div.className = 'palette';
div.style.left = '200px';
div.style.top = '200px';
acc.push(div);
return acc;
}, []);
function scare(evt) {
const bounds = DOM.container.getBoundingClientRect();
const { evtX: x, evtY: y } = DOM.getEventOffsetCoords(evt, bounds);
const scareRadius = 50;
state.forEach((coord, i) => {
const diffX = Math.abs(coord.x - x + 10);
const diffY = Math.abs(coord.y - y + 10);
if (diffX < 100 && diffY < 100) {
coord.lastScare = { x, y } // TODO set state with last scare, then judge per frame based on that number to avoid jump
DOM.container.dispatchEvent(evtScare({ x, y, i }));
}
});
}
function reset() {
while (DOM.container.childNodes.length) {
DOM.container.removeChild(DOM.container.firstChild);
}
particles.forEach((div) => {
div.innerHTML = '';
DOM.container.appendChild(div)
});
console.warn(palettes)
palettes.forEach((div) => {
DOM.container.appendChild(div)
});
};
function init() {
reset();
const click$ = Rx.Observable.fromEvent(DOM.container, 'click');
const scare$ = Rx.Observable.fromEvent(DOM.container, 'scare').auditTime(100);
scare$.subscribe(evt => {
particles[evt.detail.i].innerHTML = 'S'
DOM.addClass(particles[evt.detail.i], 'scared');
const p = particles[evt.detail.i];
setTimeout(() => {
p.innerHTML = '';
DOM.removeClass(p, 'scared');
}, 1000);
});
click$.subscribe(scare);
};
const Animation3 = Object.assign({}, AnimationBase, { init, reset });
export default Animation3;

@ -0,0 +1,117 @@
// Join mechanic, multiple particles.
// Goal: per-frame decisions
// 20 x 20 grid
import Rx, { Observable } from 'rxjs';
import AnimationBase from './animation0';
import DOM from './dom';
import Store from './store';
//
// const evtScare = (detail) => new CustomEvent('scare', { detail });
// const evtMove = (detail) => new CustomEvent('move', { detail });
//
// const [particles, state] = (new Array(5)).fill(null).reduce((acc, v, i) => {
// // const div = document.createElement('div');
// // div.className = 'anim3-particle';
// // div.innerHTML = ''
// //
// // const x = 0;
// // const y = i * 20;
// //
// // div.style.left = 0
// // div.style.top = `${y}px`;
// //
// // acc[0].push(div);
// // acc[1].push({ x, y });
// //
// // acc[1][`${x}-${y}`] = { occupied: true, type: 'palette', x, y, i };
// //
// // return acc;
// }, [[], []]);
//
// const palettes = (new Array(1)).fill(null).reduce((acc, v, i) => {
// const initialX = 200;
// const initialY = 200;
// const w = 167;
// const h = 100;
// const maxX = initialX + w;
// const maxY = initialY + h;
// const s = 20;
//
// for (let y = initialY; y < maxY; y += s) {
// for (let x = initialX; x < maxX; x += s) {
// state[`${x}-${y}`] = { occupied: true, type: 'palette', i };
// }
// }
//
// const div = document.createElement('div');
// div.className = 'palette';
// div.style.left = `${initialX}px`;
// div.style.top = `${initialY}px`;
//
// acc.push(div);
//
// return acc;
// }, []);
//
// function scare(evt) {
// const bounds = DOM.container.getBoundingClientRect();
// const { evtX: x, evtY: y } = DOM.getEventOffsetCoords(evt, bounds);
// const scareRadius = 50;
//
// state.forEach((coord, i) => {
// const diffX = Math.abs(coord.x - x + 10);
// const diffY = Math.abs(coord.y - y + 10);
//
// if (diffX < scareRadius && diffY < scareRadius) {
// coord.lastScare = { x, y } // TODO set state with last scare, then judge per frame based on that number to avoid jump
// DOM.container.dispatchEvent(evtScare({ x, y, i }));
// }
// });
// }
//
// function move(evt) {
//
// }
//
// function flee(evt) {
// particles[evt.detail.i].innerHTML = 'S'
// DOM.addClass(particles[evt.detail.i], 'scared');
// const p = particles[evt.detail.i];
// DOM.container.dispatchEvent(evtMove(evt.detail));
//
// setTimeout(() => {
// p.innerHTML = '';
// DOM.removeClass(p, 'scared');
// }, 1000);
// }
//
function reset() {
// while (DOM.container.childNodes.length) {
// DOM.container.removeChild(DOM.container.firstChild);
// }
//
// particles.forEach((div) => {
// div.innerHTML = '';
// DOM.container.appendChild(div)
// });
//
// palettes.forEach((div) => {
// DOM.container.appendChild(div)
// });
};
function init() {
// reset();
//
// const click$ = Rx.Observable.fromEvent(DOM.container, 'click');
// const scare$ = Rx.Observable.fromEvent(DOM.container, 'scare').auditTime(100);
// const move$ = Rx.Observable.fromEvent(DOM.container, 'move');
//
// click$.subscribe(scare);
// scare$.subscribe(flee);
// move$.subscribe(console.info);
};
const Animation5 = Object.assign({}, AnimationBase, { init, reset });
export default Animation5;

File diff suppressed because it is too large Load Diff

@ -5,12 +5,12 @@ const DOM = {
container,
containerBounds,
getEventOffsetCoords: (evt, containerCoords) => {
getEventOffsetCoords: (evt, bounds) => {
const { pageX, pageY } = evt;
return {
evtX: (pageX - containerCoords.left),
evtY: (pageY - containerCoords.top)
evtX: (pageX - bounds.left),
evtY: (pageY - bounds.top)
};
},
@ -26,6 +26,16 @@ const DOM = {
setTimeout(() => { DOM.container.removeChild(highlightDiv); }, 500);
},
addClass: (node, str) => {
node.className = node.className.split(' ').concat(str).join(' ');
},
removeClass: (node, str) => {
const arr = node.className.split(' ');
const i = arr.indexOf(str);
node.className = arr.slice(0, i).concat(arr.slice(i + 1)).join(' ');
},
// calcBounds: () => {
// DOM.containerBounds = container.getBoundingClientRect();
// },

@ -1,23 +1,30 @@
import Rx, { Observable } from 'rxjs';
import Animation1 from './animation1';
import Animation2 from './animation2';
import Animation3 from './animation3';
import Animation5 from './animation5';
require('../css/index.scss');
require('../css/reset.scss');
require('../css/index.scss');
require('../css/animation2.scss');
require('../css/animation3.scss');
require('../css/animation5.scss');
Animation2.init();
Animation3.init();
// TODO ANIM2 clicking several times on seahorse creates jumpiness
// TODO ANIM 2 clicking several times on seahorse creates jumpiness
// TODO display file contents in page
// TODO adding core UI breaks bounds
//
// TODO ANIM 3 birds entering, land on palette, find next one if full
// TODO ANIM 4 dog chasing
//
// TODO PR: https://github.com/ReactiveX/rxjs/blob/master/doc/decision-tree-widget/tree.yml#L122 "...time past since the last..."
//
// INTERMEDIATE TOPICS
// === I have one existing Observable and
// I want to group the values based on another Observable for opening a group, and an Observable for closing a group...
// I want to start a new Observable for each value...
// I want to wrap its messages with metadata...
// I want to share a subscription between multiple subscribers...
// I want to change the scheduler...

@ -4,7 +4,6 @@
"description": "Particle management using streams.",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --watch"
},
"repository": {

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<svg id="svg4140" style="display:inline" viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
<defs id="defs4144">
<marker orient="auto" refY="0" refX="0" id="TriangleOutM" style="overflow:visible">
<path id="path4689" d="m 5.77,0 -8.65,5 0,-10 8.65,5 z" style="fill:#828282;fill-rule:evenodd;stroke:#828282;stroke-width:1pt;marker-start:none" transform="scale(0.6,0.6)"/>
</marker>
<marker orient="auto" refY="0" refX="0" id="TriangleInM" style="overflow:visible">
<path id="path4680" d="m 5.77,0 -8.65,5 0,-10 8.65,5 z" style="fill:#828282;fill-rule:evenodd;stroke:#828282;stroke-width:1pt;marker-start:none" transform="scale(-0.6,-0.6)"/>
</marker>
</defs>
<g transform="matrix(1.046025, 0, 0, 0.943396, -539.67157, -252.833008)" bx:origin="0.5 0.583">
<path style="fill: rgb(255, 154, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 783.926 318.003 L 783.926 536.003 L 727.926 536.003 L 727.926 318.003 L 783.926 318.003 Z" id="rect3701-7-1-5"/>
<path style="fill: rgb(255, 154, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 993.926 318.003 L 993.926 536.003 L 937.926 536.003 L 937.926 318.003 L 993.926 318.003 Z" id="rect3701-7-1"/>
<path style="fill: rgb(255, 154, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 571.926 318.003 L 571.926 536.003 L 515.926 536.003 L 515.926 318.003 L 571.926 318.003 Z" id="rect3701-7"/>
<path style="fill: rgb(255, 202, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 515.926 530.003 L 993.926 530.003 L 993.926 576.003 L 983.926 586.003 L 525.926 586.003 L 515.926 576.003 L 515.926 530.003 Z" id="path3772"/>
<path style="fill: rgb(255, 202, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 993.926 325.003 L 515.926 325.003 L 515.926 278.003 L 525.926 268.003 L 983.926 268.003 L 993.926 278.003 L 993.926 325.003 Z" id="rect3701"/>
<path style="fill: rgb(255, 202, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 993.926 382.003 L 515.926 382.003 L 515.926 344.003 L 993.926 344.003 L 993.926 382.003 Z" id="rect3701-0"/>
<path style="fill: rgb(255, 202, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 993.926 512.003 L 515.926 512.003 L 515.926 474.003 L 993.926 474.003 L 993.926 512.003 Z" id="rect3701-0-7"/>
<path style="fill: rgb(255, 202, 101); fill-opacity: 1; stroke: rgb(202, 101, 0); stroke-width: 2; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dashoffset: 0; display: inline;" d="M 993.926 456.003 L 515.926 456.003 L 515.926 400.003 L 993.926 400.003 L 993.926 456.003 Z" id="rect3701-0-6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Loading…
Cancel
Save