Angular ♥ SVG 

@subarroca

As seen on

angular beers

angular 2.2.0

Angular ❤ SVG

@subarroca

SVG

Angular meets SVG

SVG vs Canvas

4 examples

Animation

Resources

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

Scalable Vector Graphic

XML Markup

CSS styling

@subarroca

Angular ❤ SVG

<circle />

@subarroca

Angular ❤ SVG

<rect/>

<polygon/>

<path/>

<image/>

Iep!

<text/>

SMIL animations (getting deprecated)

CSS animations

@subarroca

Angular ❤ SVG

<clipPath />

<filter/>

colorMatrix

blur

blending

shadow

lighting

...

Viewbox

160px (or em, ...)

200px (or em, ...)

392

314

xOrigin yOrigin width height

viewBox = "0 0 392 314"

BEWARE OF BROWSERS!!!

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

SVG components = HTML components

editable attrs → attr.x

CSS per component

subcomponents are trickier and break linter

<g subComponent></g>

data-binding

events

@subarroca

Angular ❤ SVG

elements → svg:circle

Web animation → angular animation

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

Angular2 introduction

@subarroca

SVG

bind events on elements

Canvas

-

node per element → worse performance

one element →better performance

redraw on demand →better performance

redraw each frame → worse performance

Bad for intense refreshing

Bad for interaction

Good for interactions and few nodes (regular graphics nowadays)

Good for animated textures

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

Masked stack

@subarroca

Angular ❤ SVG

Using:

rect

CSS

mask

Tricky parts:

unique mask id

@subarroca

Angular ❤ SVG

z-index

<rect>

<circle>

<polygon>

no z-index!!!

@subarroca

Angular ❤ SVG

<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>

@subarroca

Angular ❤ 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);
  }

}

@subarroca

Angular ❤ SVG

<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>

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

Gauge

@subarroca

Angular ❤ SVG

Using:

circle

CSS

stroke-dasharray

Tricky parts:

calculating the length of stroke

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

dashArray

10

dash: 5

gap: 5

strokeDasharray: 5 5

10

dash: 15

gap: 40

strokeDasharray: 15 40

@subarroca

Angular ❤ SVG

10

dash: 5

gap: 40

strokeDasharray: 5 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>

@subarroca

Angular ❤ 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;
      }
    });
  }


}

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

Duotone image

@subarroca

Angular ❤ SVG

Using:

image

filter

color calculations

@subarroca

Angular ❤ SVG

Tricky parts:

R 0 0 0 0

0 G 0 0 0

0 0 B 0 0

0 0 0 A 0

color matrix

input
R G B A M
R

G

B

A
output

@subarroca

Angular ❤ SVG

<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>

@subarroca

Angular ❤ 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;
  }
}

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

Funnel Graph

@subarroca

Angular ❤ SVG

Using:

path

CSS animations

subcomponents

Tricky parts:

learning to build paths

dealing with linter

@subarroca

Angular ❤ SVG

PATH

@subarroca

Angular ❤ SVG

The selector of the component "FunnelSegmentComponent" should have prefix "ng2-kw" (https://goo.gl/cix8BY)              

Linter

@subarroca

Angular ❤ SVG

<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>

@subarroca

Angular ❤ 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}`;
  }

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

CSS animations

Morph through script

SMIL getting deprecated

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

@Component({
  selector: 'app-logo',
  templateUrl: './app-logo.component.html',
  styleUrls: ['./app-logo.component.scss'],
  animations: [
    trigger('aState', [
      state('small', style({transform: 'scale(1)'})),
      state('large', style({transform: 'scale(4.2)'})),
      transition('small => large', animate('1s ease', keyframes([
        style({transform: 'scale(1)', offset: 0}),
        style({transform: 'scale(0.7) rotate(15deg)', offset: 0.15}),
        style({transform: 'scale(1)', offset: 0.3}),
        style({transform: 'scale(4.2)', offset: 1})
      ]))),
      transition('large => small', animate('1s ease', keyframes([
        style({transform: 'scale(4.2)', offset: 0}),
        style({transform: 'scale(5) rotate(-15deg)', offset: 0.15}),
        style({transform: 'scale(4.2)', offset: 0.3}),
        style({transform: 'scale(1)', offset: 1})
      ])))
    ])
  ]
})

@subarroca

Angular ❤ SVG

@subarroca

Angular ❤ SVG

SVG

Angular meets SVG

SVG VS CANVAS

4 examples

Animation

Resources

Angular and SVG

Reference

@subarroca

Angular ❤ SVG

Sara Soueidan

She's just... great

 

https://sarasoueidan.com/tags/svg

MDN: SVG

Mozilla SVG reference with examples

 

https://developer.mozilla.org/en-US/docs/Web/SVG

Reference

@subarroca

Angular ❤ SVG

SVG path syntax

Reference

@subarroca

Angular ❤ SVG

Color matrix

Greensock

Animation library with a huge background expertise

 

http://greensock.com

Animation

SMIL alternatives

@subarroca

Angular ❤ SVG

Codrops

Curated examples pushing imagination forward

 

http://tympanus.net/codrops/?s=svg&search-type=posts​

Codepen

See what others are doing

 

http://codepen.io/search/pens?q=svg

Inspiration

@subarroca

Angular ❤ SVG

@subarroca

Moltes gràcies

As seen on

angular beers

Angular ♥ SVG ¦ angularbeers

By Salvador Subarroca

Angular ♥ SVG ¦ angularbeers

  • 1,635