Matt Clarkson
Matt Clarkson

Reputation: 14426

Flatten points into SVG polyline Polymer

I have the following data structure:

'points': [
  {
    'x': 5535,
    'y': 5535
  },
  {
    'x': 5535,
    'y': 60000
  },
  {
    'x': 60000,
    'y': 60000
  },
  {
    'x': 60000,
    'y': 5535
  }
];

I would like to flatten it to 5535,5535 5535,60000 60000,60000 60000,5535 to use as a polyline points attribute.

I have the following in Polymer <template>

<polyline
    id="polygon",
    points="{{points | polygonPoints | toSvgPoints(width, height)}}"
    stroke="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{opacity}})"
    fill="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{opacity * 0.6}})"
    stroke-width="{{lineWidth}}"
    stroke-linecap="round"
    stroke-linejoin="round"
/>

Where polygonPoints and toSvgPoints filters look like so:

/**
 * Retrieves the polygon points for this object.
 * @param point optionally provide a list of normalized points
 * @returns the polygon points or all points if a line
 */
polygonPoints: function(points) {
  var array = points.slice(0);
  points = points || this.points;
  array.push(points[0])
  return array;
},

/**
 * Retrieves the polygon points for this object.
 * @param point optionally provide a list of normalized points
 * @returns the polygon points or all points if a line
 */
toSvgPoints: function(points, width, height) {
  var i, string = '';
  points = points || this.points;
  for (i = 0; i < points.length; ++i) {
    string += this.normalizedToActual(points[i].x, width);
    string += ',';
    string += this.normalizedToActual(points[i].y, height);
    string += ' ';
  }
  return string;
},

This works, the toSvgPoints returns the correct points but the bindings to the point values do not get set up automagically by Polymer. For example, if I modify this.points[0].x = 4219 the polygon doesn't update because the binding hasn't been created to the polygon attribute.

Is this something that just can't be solved without providing some other method that invokes a redraw? Ideally I'd just want to do this:

<polyline
    id="polygon",
    points="{{x,y for (points | polygonPoints)}}"
    ...
/>

Which would stamp out the x and y values in the points attribute and set up the bindings.

Upvotes: 1

Views: 341

Answers (3)

Justin Fagnani
Justin Fagnani

Reputation: 11201

PolymerExpressions only observes objects that are directly referenced in an expression, so in this case it observes points, but not the properties on the elements in the array. If you replace a point, the expression will update, but it won't if you modify a point.

There are a few ways to deal with this. If you know where a point is being modified, at you have a reference to the list there, you can manually notify on the list. I usually work with Polymer.Dart, but I think that the notify() function in observe-js is what you're looking for: https://github.com/Polymer/observe-js/blob/master/src/observe.js#L1076

Alternatively, instead of returning a static projection of the points, have toSvgPoints listen to all of its dependencies: points, x and y for each point, width and height. When an input changes, update the output array. This will cause a chain of updates that propagates to your view.

Use Polymer's observe-js library to do observation. It polyfills Object.observe() on platforms that don't have it. https://github.com/Polymer/observe-js#objectobserver

Upvotes: 2

Matt Clarkson
Matt Clarkson

Reputation: 14426

Well that took ages. Don't use a polyline use a path:

<path
    id="zone"
    on-down="{{dragStart}}"
    on-up="{{dragEnd}}"
    d="M {{points[0].x| max(width)}} {{points[0].y | max(height)}} {{points | polygonPoints | toSvgPoints(width, height)}}"
    fill="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{(selected ? 0.8 : 0.6) * 0.6}})"
    stroke="rgba({{colour.r}}, {{colour.g}}, {{colour.b}}, {{(selected ? 0.8 : 0.6)}})"
    stroke-linecap="round"
    stroke-linejoin="round"
/>

The M {{points[0].x| max(width)}} {{points[0].y | max(height)}} at the start of the d attribute forces the redraw.

Upvotes: 0

&#220;mit
&#220;mit

Reputation: 17489

I don't know for sure but I guess Polymer doesn't observe individual attributes inside an array. You could try to replace the item at that position:

this.points[0] = {x:4219,y:this.points[0].y}

or alternatively create a new array and set it to this.points ?

Upvotes: 1

Related Questions