|  | 
|  | 1 | +<template> | 
|  | 2 | +  <svg  | 
|  | 3 | +    :height="height"  | 
|  | 4 | +    :width="width"  | 
|  | 5 | +    @click="addPoint"  | 
|  | 6 | +    @contextmenu="clearPoints"  | 
|  | 7 | +    :style="svgStyle"> | 
|  | 8 | +   | 
|  | 9 | +    <polygon  | 
|  | 10 | +      :points="pointsString"  | 
|  | 11 | +      :fill="fill"  | 
|  | 12 | +      :fill-opacity="fillOpacity"  | 
|  | 13 | +      :stroke="stroke"  | 
|  | 14 | +      :stroke-width="strokeWidth" /> | 
|  | 15 | +   | 
|  | 16 | +    <circle  | 
|  | 17 | +      v-for="point in points"  | 
|  | 18 | +      :cx="point.x"  | 
|  | 19 | +      :cy="point.y"  | 
|  | 20 | +      :r="pointsRadius"  | 
|  | 21 | +      :fill="getPointFill(point)"  | 
|  | 22 | +      :fill-opacity="getPointFillOpacity(point)"  | 
|  | 23 | +      @click="removePointHoveringOn"  | 
|  | 24 | +      @mouseenter="hoveringOn(point)"  | 
|  | 25 | +      @mouseleave="hoveringOn(null)" /> | 
|  | 26 | + | 
|  | 27 | +  </svg> | 
|  | 28 | +</template> | 
|  | 29 | + | 
|  | 30 | +<script> | 
|  | 31 | +export default { | 
|  | 32 | +   | 
|  | 33 | +  name: 'polygon-editor', | 
|  | 34 | +   | 
|  | 35 | +  props: { | 
|  | 36 | +    value: { type: String, default: null }, | 
|  | 37 | +    height: { type: String, default: "100%" }, | 
|  | 38 | +    width: { type: String, default: "100%" }, | 
|  | 39 | +    pointsRadius: { type: Number, default: 5 }, | 
|  | 40 | +    pointsHoverColor: { type: String, default: "red" }, | 
|  | 41 | +    pointsColor: { type: String, default: "teal" }, | 
|  | 42 | +    fill: { type: String, default: "lightgray" }, | 
|  | 43 | +    fillOpacity: { type: Number, default: 0.5 }, | 
|  | 44 | +    stroke: { type: String, default: "gray" }, | 
|  | 45 | +    strokeWidth: { type: Number, default: 1 }, | 
|  | 46 | +    backgroundImage: { type: String, default: null } | 
|  | 47 | +  }, | 
|  | 48 | +   | 
|  | 49 | +  data() { | 
|  | 50 | +    return { | 
|  | 51 | +      points: this.extractPoints(this.value), | 
|  | 52 | +      pointHoveringOn: null | 
|  | 53 | +    } | 
|  | 54 | +  }, | 
|  | 55 | +
 | 
|  | 56 | +  computed: { | 
|  | 57 | +    hasPoints() { | 
|  | 58 | +      return this.points && this.points.length > 0; | 
|  | 59 | +    }, | 
|  | 60 | +    lastPoint() { | 
|  | 61 | +      if (this.hasPoints) { | 
|  | 62 | +        return this.points[this.points.length - 1]; | 
|  | 63 | +      } | 
|  | 64 | +      return null; | 
|  | 65 | +    }, | 
|  | 66 | +    pointsString() { | 
|  | 67 | +      let pointStrings = this.points.map(function (point) { | 
|  | 68 | +        return point.x + ',' + point.y; | 
|  | 69 | +      }, this); | 
|  | 70 | +      return pointStrings.join(' '); | 
|  | 71 | +    }, | 
|  | 72 | +    svgStyle() { | 
|  | 73 | +      let style = ''; | 
|  | 74 | +      if (this.backgroundImage) { | 
|  | 75 | +        style += 'background:url("' + this.backgroundImage + '") no-repeat left top' | 
|  | 76 | +      } | 
|  | 77 | +      return style; | 
|  | 78 | +    } | 
|  | 79 | +  }, | 
|  | 80 | +
 | 
|  | 81 | +  watch: { | 
|  | 82 | +    pointsString() { | 
|  | 83 | +      this.$emit('input', this.pointsString) | 
|  | 84 | +    } | 
|  | 85 | +  }, | 
|  | 86 | +
 | 
|  | 87 | +  methods: { | 
|  | 88 | +    extractPoints() { | 
|  | 89 | +      const valuePattern = /^(\d+,\d+ )*\d+,\d+$/; | 
|  | 90 | +      let points = []; | 
|  | 91 | +      if (!this.value) { return points; } | 
|  | 92 | +      if (!valuePattern.test(this.value)) { | 
|  | 93 | +        console.warn('Value provided is not in the correct format. Value must bew in the format of polygon "points" attribute (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/points)'); | 
|  | 94 | +        return points; | 
|  | 95 | +      } | 
|  | 96 | +      this.value.split(' ').forEach(point => { | 
|  | 97 | +        points.push( | 
|  | 98 | +          { | 
|  | 99 | +            x: parseInt(point.split(',')[0], 10), | 
|  | 100 | +            y: parseInt(point.split(',')[1], 10), | 
|  | 101 | +          } | 
|  | 102 | +        ); | 
|  | 103 | +      }, this); | 
|  | 104 | +      return points; | 
|  | 105 | +    }, | 
|  | 106 | +    addPoint(evt) { | 
|  | 107 | +      this.points.push( | 
|  | 108 | +        { | 
|  | 109 | +          x: evt.offsetX, | 
|  | 110 | +          y: evt.offsetY | 
|  | 111 | +        } | 
|  | 112 | +      ) | 
|  | 113 | +    }, | 
|  | 114 | +    removePointHoveringOn(evt) { | 
|  | 115 | +      if (!this.pointHoveringOn) { | 
|  | 116 | +        return; | 
|  | 117 | +      } | 
|  | 118 | +      evt.stopImmediatePropagation(); | 
|  | 119 | +      this.points.splice(this.points.indexOf(this.pointHoveringOn), 1); | 
|  | 120 | +    }, | 
|  | 121 | +    clearPoints(evt) { | 
|  | 122 | +      this.points = []; | 
|  | 123 | +      evt.preventDefault(); | 
|  | 124 | +    }, | 
|  | 125 | +    getPointFill(point) { | 
|  | 126 | +      if (point === this.pointHoveringOn) { | 
|  | 127 | +        return this.pointsHoverColor; | 
|  | 128 | +      } | 
|  | 129 | +      return this.pointsColor; | 
|  | 130 | +    }, | 
|  | 131 | +    getPointFillOpacity(point) { | 
|  | 132 | +      if (point === this.pointHoveringOn || point === this.lastPoint) { | 
|  | 133 | +        return 1; | 
|  | 134 | +      } | 
|  | 135 | +      return 0; | 
|  | 136 | +    }, | 
|  | 137 | +    hoveringOn(point) { | 
|  | 138 | +      this.pointHoveringOn = point; | 
|  | 139 | +    }, | 
|  | 140 | +  } | 
|  | 141 | +   | 
|  | 142 | +} | 
|  | 143 | +
 | 
|  | 144 | +</script> | 
|  | 145 | + | 
|  | 146 | +<style lang="scss"> | 
|  | 147 | +
 | 
|  | 148 | +</style> | 
0 commit comments