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">
    <h2>Build Your Pizza</h2>

    <!-- <pre>{{pizzaState}}</pre> -->
    <div title="{{pizzaState}}">
      <svg version="1.1"
        style="display: block; margin: 10px auto"
        width="300" height="300"
    <!-- ... -->
  <script type="application/dart" src="x_pizza.dart"></script>
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:
class XPizza extends PolymerElement {
  // ...
  updatePizzaState([_]) {

  _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);
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();
        ..['cx'] = "${150 + (90 * cos(PI * rand.nextDouble()))}"
        ..['cy'] = "${150 + (90 * cos(PI * rand.nextDouble()))}";
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