Friday, January 17, 2014

Day 999: Polymer and SVG


I can't resist. The temptation to play around with SVG is simply too great and I am helpless before its wiles.
I really meant to start looking at incorporating Polymer elements in Angular. Perhaps tomorrow. Onto the SVG!

I have a pretty cool <x-pizza> Polymer that builds pizzas for ordering:



The problem, of course, is that the current pizza state is in the form of text. Saaaay! SVG can help with that!

I start by adding a blank SVG tag to my <x-pizza> template:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>

    <!-- <pre>{{pizzaState}}</pre> -->
    <div title="{{pizzaState}}">
      <svg version="1.1"
        baseProfile="full"
        id="pizza-graphic"
        style="display: block; margin: 10px auto"
        width="300" height="300"
        xmlns="http://www.w3.org/2000/svg">
      </svg>
    </div>
    <!-- ... -->
  </template>
  <script type="application/dart" src="x_pizza.dart"></script>
</polymer-element>
That creates a 300 by 300 blank SVG canvas on which to paint. I give it an ID so that I can readily look it up using the Polymer $ for automatic node finding.

I already have my Polymer updating the pizza state (text-only so far) when new items are selected from the various menus. To do so, the updatePizzaState() method is called by various change event handlers. I factor the old text-only code out into _updatePizzaText(). In addition to calling this method, I also have updatePizzaState() call my new, SVG powered _updateGraphic() method:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  updatePizzaState([_]) {
    _updateText();
    _updateGraphic();
  }

  _updateGraphic() {
    this.$['pizza-graphic'].innerHtml = '''
      <circle cx="150" cy="150" r="150" fill="tan" />
      <circle cx="150" cy="150" r="140" fill="darkred" />
      <circle cx="150" cy="150" r="135" fill="lightyellow" />
    ''';
  }
  // ...
}
There is not too much to _updateGraphic() so far. It adds three circles in the center of the 300 by 300 SVG element, each slightly smaller than the previous, each representing a different layer of the pizza (crust, sauce, and cheese). I use the $ node finder to ensure that I have the correct SVG element.

With that, I have SVG!



To add toppings, I need a generator for each of the supported toppings:
  _svgPepperoni() =>
    new CircleElement()..attributes = {'r': '10', 'fill': 'red'};
  _svgSausage() =>
    new CircleElement()..attributes = {'r': '3',  'fill': 'brown'};
  _svgGreenPepper() =>
    new CircleElement()..attributes = {'r': '10', 'fill': 'green'};
Next, I need a way to choose the appropriate SVG maker function given a string representing a topping:
  _toppingMaker(String topping) {
    if (topping == 'pepperoni') return _svgPepperoni;
    if (topping == 'sausage') return _svgSausage;
    if (topping == 'green peppers') return _svgGreenPepper;
  }
With that, I can iterate over each topping and add them to the pizza:
  _addWholeToppings() {
    model.wholeToppings.forEach((topping) {
      var maker = _toppingMaker(topping);
      _addWholeTopping(maker);
    });
  }
The maker now points to the appropriate generator function (e.g. _svgPepperoni). Armed with that, I can make 20 randomly placed toppings with:
  _addWholeTopping(maker) {
    var rand = new Random();
    for (var i=0; i<20; i++) {
      var topping = maker();
      topping.attributes
        ..['cx'] = "${150 + (90 * cos(PI * rand.nextDouble()))}"
        ..['cy'] = "${150 + (90 * cos(PI * rand.nextDouble()))}";
      $['pizza-graphic'].append(topping);
    }
  }
And that does the trick. The end result is:



So yeah, I need a better green pepper. And maybe some animation. And random placement probably isn't quite right. But it is definitely better than plain text. So, yay SVG!


Day #999

No comments:

Post a Comment