Angular Beers
Scalable Vector Graphic
XML Markup
CSS styling
circle, rect, polygon, path
image, text, filter, mask
SMIL animations (getting deprecated)
CSS animations
Different positioning (viewbox)
160px (or em, ...)
200px (or em, ...)
392
314
xOrigin yOrigin width height
viewBox = "0 0 392 314"
SVG components = HTML components
editable attrs must be used as attr.x
CSS per component
subcomponents are trickier and break linter
<g subComponent></g>
data-binding
events
angular2 team working on it
rect
CSS
mask
unique mask id
<rect>
<circle>
<polygon>
no z-index!!!
<svg
attr.viewBox = "0 0 {{maskWidth}} {{maskHeight}}">
<g
#holder
attr.clip-path = "url(#{{uniqueId}}-mask)">
<rect
x = "0"
y = "0"
[attr.width] = "maskWidth"
[attr.height] = "maskHeight"
[style.fill] = "bgColor"/>
<rect
x = "0"
y = "0"
[attr.width] = "maskWidth"
[attr.height] = "maskHeight"
[style.transform] = "position"
[style.transition] = "'transform ' + animationSecs + 's'"
[style.fill] = "frontColor"/>
</g>
<clipPath
#mask
id = "{{uniqueId}}-mask">
<ng-content></ng-content>
</clipPath>
</svg>
@Component({
selector: 'ng2-kw-masked-stack',
templateUrl: './masked-stack.component.html',
styleUrls: ['./masked-stack.component.scss']
})
export class MaskedStackComponent implements OnInit {
@Input() maskWidth: number = 100;
@Input() maskHeight: number = 100;
@Input() bgColor: string = 'transparent';
@Input() frontColor: string = 'transparent';
@Input() from: string = 'bottom'; // top, bottom, left, right
@Input() goal: number = 100;
@Input()
set value(value: number) {
this._value = value;
Observable.timer(0)
.first()
.subscribe(() => this.valueLoaded = true);
}
_value: number;
@Input() animationSecs: number = 0.5;
// TODO: this sucks but I still haven't found a way to get component unique Id
@Input() uniqueId: string;
valueLoaded: boolean = false;
constructor(
private sanitizer: DomSanitizer
) { }
ngOnInit() {
}
get position() {
let str: string;
switch (this.from) {
case 'top':
str = `translateY(${
this.valueLoaded
? (-this.maskHeight * (1 - this._value / this.goal))
: -this.maskHeight
}px)`;
break;
case 'bottom':
str = `translateY(${
this.valueLoaded
? this.maskHeight * (1 - this._value / this.goal)
: this.maskHeight
}px)`;
break;
case 'left':
str = `translateX(${
this.valueLoaded
? -this.maskWidth * (1 - this._value / this.goal)
: -this.maskWidth
}px)`;
break;
case 'right':
str = `translateX(${
this.valueLoaded
? this.maskWidth * (1 - this._value / this.goal)
: this.maskWidth
}px)`;
break;
}
return this.sanitizer.bypassSecurityTrustStyle(str);
}
}
<ng2-kw-masked-stack
bgColor = "white"
frontColor = "black"
maskWidth = "100"
maskHeight = "100"
[value] = "value"
uniqueId = "orb">
<svg:circle
cx = "50"
cy = "50"
r = "50"/>
</ng2-kw-masked-stack>
https://github.com/subarroca/ng2-masked-stack
npm i ng2-masked-stack
circle
CSS
stroke-dasharray
calculating the length of stroke
10
dash: 5
gap: 25
strokeDasharray: 5 25
10
dash: 5
gap: 5
strokeDasharray: 5 5
10
dash: 10
gap: 40
strokeDasharray: 10 40
<svg viewBox = "0 0 200 200">
<g>
<circle
[attr.r] = "bgRadius"
[style.fill] = bgColor/>
<g
*ngFor = "let segment of sortedSegments">
<circle
[style.stroke] = segment.bgColor
[style.strokeWidth] = segment.borderWidth
[attr.r] = segment.computedRadius/>
<circle
[style.transition] = "'stroke-dasharray ' + animationSecs + 's'"
[style.stroke] = segment.color
[style.strokeWidth] = segment.borderWidth
[style.strokeDasharray] = "segmentsLoaded ? segment.strokeProgress : segment.strokeEmptyProgress"
[style.strokeLinecap] = "rounded ? 'round' : ''"
[attr.r] = segment.computedRadius/>
</g>
</g>
<g
transform = "translate(100, 100)">
<text
*ngFor = "let label of labels"
[attr.x] = label.x
[attr.y] = label.y
[style.fill] = label.color
[style.fontSize] = label.fontSize
text-anchor = middle>
{{label.text}}
</text>
</g>
</svg>
@Component({
selector: 'ng2-kw-gauge',
templateUrl: './gauge.component.html',
styleUrls: ['./gauge.component.scss']
})
export class GaugeComponent implements OnInit {
@Input() bgRadius: number = 100;
@Input() bgColor: string;
@Input() rounded: boolean = true;
@Input() reverse: boolean = false;
@Input() animationSecs: number = 0.5;
@Input() labels: GaugeLabel[];
@Input()
set segments(segments: GaugeSegment[]) {
this.segmentsLoaded = false;
this.sortedSegments = this.sortSegments(segments);
Observable.timer(0)
.first()
.subscribe(() => this.segmentsLoaded = true);
}
sortedSegments: GaugeSegment[];
segmentsLoaded: boolean = false;
constructor() { }
ngOnInit() {
}
sortSegments(segments: GaugeSegment[]) {
return segments.sort((a: GaugeSegment, b: GaugeSegment) => {
if (this.reverse) {
return (a.value / a.goal > b.value / b.goal) ? 1 : -1;
} else {
return (a.value / a.goal > b.value / b.goal) ? -1 : 1;
}
});
}
}
https://github.com/subarroca/ng2-kw-gauge
npm i ng2-kw-gauge
based on: http://codepen.io/jmperez/pen/LGqaxQ
image
filter
color calculations
colorMatrix
blur
blending
shadow
lighting
...
R 0 0 0 0 0 G 0 0 0 0 0 B 0 0 0 0 0 A 0
input R G B A M
R
G
B
A
output
<svg
attr.viewBox = "0 0 {{width}} {{height}}"
[attr.width] = "width"
[attr.height] = "height"
preserveAspectRatio="xMidYMid slice">
<defs>
<filter id="duotone-filter">
<feColorMatrix
type="matrix"
[attr.values]= "duotoneMatrix"/>
</filter>
</defs>
<image
[attr.width] = "width"
[attr.height] = "height"
filter="url(#duotone-filter)"
[attr.xlink:href] = "src"/>
</svg>
@Component({
selector: 'ng2-kw-duotone-image',
templateUrl: './duotone-image.component.html',
styleUrls: ['./duotone-image.component.scss']
})
export class DuotoneImageComponent implements OnInit {
@Input() src: string;
@Input() width: number;
@Input() height: number;
@Input()
set darkColor(color: string) {
this._darkColor = this.hexToRgb(color);
}
@Input()
set lightColor(color: string) {
this._lightColor = this.hexToRgb(color);
}
_darkColor: { r: number, g: number, b: number };
_lightColor: { r: number, g: number, b: number };
constructor() { }
ngOnInit() {
}
hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
get duotoneMatrix() {
let value = [];
value = value.concat(
[this._lightColor.r / 256 - this._darkColor.r / 256, 0, 0, 0, this._darkColor.r / 256]);
value = value.concat(
[this._lightColor.g / 256 - this._darkColor.g / 256, 0, 0, 0, this._darkColor.g / 256]);
value = value.concat(
[this._lightColor.b / 256 - this._darkColor.b / 256, 0, 0, 0, this._darkColor.b / 256]);
value = value.concat([0, 0, 0, 1, 0]);
return value;
}
}
https://subarroca.github.io/ng2-kw-duotone-image
npm i ng2-kw-duotone-image
path
CSS animations
subcomponents
learning to build paths
dealing with linter
The selector of the component "FunnelSegmentComponent" should have prefix "ng2-kw" (https://goo.gl/cix8BY)
<svg [attr.viewBox] = "viewBox">
<g ng2-kw-funnel-segment
*ngFor = "let segment of _segments; let i = index"
[from] = "i ? _segments[i-1].value : undefined"
[to] = "segment.value"
[color] = "segment.color"
[labelColor] = "segment.labelColor"
[max] = "_segments[0].value"
[max] = "_segments[_segments.length-1].value"
[width] = "segmentWidth"
[height] = "funnelHeight"
[graphMode] = "graphMode"
[attr.transform] = "getSegmentOffset(i)">
</g>
</svg>
protected drawSegment(segment: Segment): string {
// from axe right
// to previous right
// to end this.slope
// to current left
// to -current left
// to -end this.slope
// to -previous right
return `
M0, ${this.height / 2}
${Point.getRoundedCorner(
{
x: 0,
y: this.height / 2 - segment.start.y / 2
}, {
x: this.width * this.slope,
y: this.height / 2 - segment.end.y / 2
}, {
x: this.width,
y: this.height / 2 - segment.end.y / 2
},
this.width * this.RADIUS)}
${Point.getRoundedCorner(
{
x: this.width,
y: this.height / 2 + segment.end.y / 2
}, {
x: this.width * this.slope,
y: this.height / 2 + segment.end.y / 2
}, {
x: 0,
y: this.height / 2 + segment.start.y / 2
},
this.width * this.RADIUS)}
Z`;
}
static getRoundedCorner(start: Point, mid: Point, end: Point, radius: number) {
// y = mx + b
let m1 = (start.y - mid.y) / (start.x - mid.x);
let m2 = (mid.y - end.y) / (mid.x - end.x);
let b1 = mid.y - m1 * mid.x;
let b2 = mid.y - m2 * mid.x;
let direction = start.x < end.x ? 1 : -1;
let startmid: Point = new Point({
x: mid.x - radius * direction,
y: m1 * (mid.x - radius * direction) + b1
});
let midend: Point = new Point({
x: mid.x + radius * direction,
y: m2 * (mid.x + radius * direction) + b2
});
return `L${start.x},${start.y}
L${startmid.x},${startmid.y}
Q${mid.x},${mid.y},
${midend.x},${midend.y}
L${end.x},${end.y}`;
}
CSS animations
Morph through script
SMIL gettingh deprecated
Angular Beers