# Pie Chart

The code is under file `awesome-pie-chart-demo.html`. The chart is draw for a set of dummy spends
<link rel="import" href="https://polygit.org/polymer+polymer+v2.0.0-rc.3/webcomponentsjs+webcomponents+v1.0.0-rc.6/components/polymer/polymer-element.html">
<link rel="import" href="awesome-charts/awesome-pie-chart.html">
<dom-module id="awesome-pie-chart-demo">
<template>
  <style>
     :host {
        display: block;
      }
      
      .container{
        margin:10px;
        background-color:#fafafa;
        padding:10px;
        border:1px solid #e5e5e5;
      }
      
      pre{
        font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
        margin-bottom: 10px;
        overflow: auto;
        width: auto;
        padding: 5px;
        background-color: #eee;
        width: 650px!ie7;
        padding-bottom: 20px!ie7;
        max-height: 600px;
      }
      
      .chart1{
        --label-text-size : 15px;
        --label-stroke-width : 0.3;
        --polyline-stroke-color:#666666;  
      }
      
      .chart2{
        width:350px;
        margin:auto;
      }
      
  </style>
  <div class="container">
    <h2>Basic Pie chart</h2>
    <blockquote>
      <pre>
        <code>
  #1 : Charts binded with data and basic minimal configuration
  #2 : mixins used for styling
        </code>
      </pre>
    </blockquote>
    <div class="chart1">
      <awesome-pie-chart data={{data1}} chart-config={{config1}}></awesome-pie-chart>
    </div>
  </div>
  
  <div class="container">
    <h2>Configurable Pie chart with events</h2>
    <blockquote>
      <pre>
        <code>
  #1 : Charts binded with data and few configuration factors
  #2 : The hover tooltip here is not default its resolved via a hook 'hover-labels'
  #3 : Click events are binded, click any slice to see in action
        </code>
      </pre>
    </blockquote>
    <div class="chart2">
      <awesome-pie-chart data={{data2}} chart-config={{config2}} pie-click="{{pieClick2}}" hover-labels={{hoverLables2}}></awesome-pie-chart>
    </div>
  </div>
  
  <div class="container">
    <h2>Closed Pie chart with events</h2>
    <blockquote>
      <pre>
        <code>
  #1 : Charts binded with data and few configuration factors including closed pie due to inner radius factor being set to 0
  #2 : The labels here is not default its resolved via a hook 'span-labels'
  #3 : Click events are binded, click any slice to see in action
        </code>
      </pre>
    </blockquote>
    <div class="chart3">
      <awesome-pie-chart data={{data3}} chart-config={{config3}} pie-click="{{pieClick3}}" span-labels={{spanLabels3}}></awesome-pie-chart>
    </div>
  </div>
</template>
<script>
  // Define the class for a new element called custom-element
  class AwesomePieChartDemo extends Polymer.Element {
    static get is() { return "awesome-pie-chart-demo"; }
    static get properties(){
      return{
        data1:{
          type:Array,
          value:[],
          notify:true
        },
         data2:{
          type:Array,
          value:[],
          notify:true
        },
         data3:{
          type:Array,
          value:[],
          notify:true
        },
        config1:{
          type:Object,
          value:{
            prop:"value",
            label:"id"
          }
        },
        config2:{
          type:Object,
          value:{
           prop:"value",
           label:"id",
           color:["#ff6961","#ffb861","#61f7ff","#ff61f7","#61a8ff"],
           hoverRadius:0.78,
           padding:0.030,
           hoverAnimationDuration:500,
           labelsInside:true,
           outerRadius:0.8,
           innerRadius:0.6,
           radiusSize:0.85,
          } 
        },
        config3:{
          type:Object,
          value:{
           prop:"value",
           label:"id",
           innerRadius:0,
           padding:0.030,
           disableHoverAnimation:true
          }
        }
      }
    }
    ready(){
      super.ready();
      this.data1=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
      this.data2=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
      this.data3=[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}];
    }
    hoverLables2(data,p,el){
      return data.id + ': $ ' + data.value.toLocaleString('en')  + ' ( ' + p.toFixed(2) + '% )';
    }
    pieClick2(data,p,el){
      alert(data.id + " clicked");
      console.log(data,p,el);
    }
    spanLabels3(data,p,el){
      return data.id + ': $ ' + data.value.toLocaleString('en')
    }
    pieClick3(data,p,el){
      alert(data.id + " clicked");
      console.log(data,p,el);
    }
  }
  // Register the new element with the browser
  customElements.define(AwesomePieChartDemo.is, AwesomePieChartDemo);
</script>
</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/webcomponentsjs+1.0.0-rc.5/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="awesome-pie-chart-demo.html">
  </head>
  <body>
    <awesome-pie-chart-demo></awesome-pie-chart-demo>
  </body>
</html>
<!--
MIT License

Copyright (c) 2017 Vinod Louis

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-->

<link rel="import" href="https://polygit.org/polymer+polymer+v2.0.0-rc.3/webcomponentsjs+webcomponents+v1.0.0-rc.6/components/polymer/polymer-element.html">
<script src="https://d3js.org/d3.v4.min.js"></script>
<dom-module id="awesome-pie-chart">
    <template>
        <style>
            :host {
                display: block;
            }

           polyline{
                opacity: var(--polyline-opacity,0.5);
                stroke: var(--polyline-stroke-color,#666666);
                stroke-width: var(--polyline-stroke-width,2px);
                fill: var(--polyline-fill,none);
                pointer-events:none;
            }

            .cursor-pointer{
                cursor:pointer;
            }

            .pie-label-text{
                font-size:var(--label-text-size,12px);
                stroke:var(--label-stroke-color,#999999);
                stroke-width:var(--label-stroke-width,0.5);
            }
        </style>
        <div id="spanningPieChart"></div>
    </template>

    <script>
    /**
     * `awesome-pie-chart`
     *
     *
     * @AwesomePieChart
     * @polymer
     * @demo demo/awesome-pie-chart.html
     * 
     ------------
      Properties
     ------------
     #1: Data is the array of object with minimal 2 key value pair which can act as label and prop for pie chart   
     *REQUIRED
     data : {
        type: Array,
        value:[{id:'grocery',value:10000},{id:'rent',value:5000},{id:'shopping',value:35000},{id:'bills',value:20000},{id:'other expense',value:1500}]
     }

     #2 chart-config an composite object of properties not all are required provides additional customization to chart
     chartConfig:{
        type: object,
        value:{
           //provide a size for chart else its takes the available width 
           chartSize:300,  

           //The size of the radius of the pie max to be 1, useful in adjusting the chart in case of huge labels
           radiusSize:0.8, 

           //In case of true labels are draw inside the circle of pie/donught good for small chart
           labelsInside:false,

           //Array of color size to be equal or greater than size of input data array
           color:[],

           //on pie hover control animation enable/disable
           disableHoverAnimation:false,
           
           //duration of animation on pie hover in milliseconds 
           hoverAnimationDuration:1000,
            
           //The point at which the chart should animateon hover if greater than 'outerRadius' chart animates outside if less animates inside
           hoverRadius:0.8,
           
           *REQUIRED
           //label or context text name on which pie chart is to be drawn must be from input object array
           label:"",
           
           *REQUIRED
           //property value to be considered for pie calculation
           prop:"",
           
           //outer radius of the pie should be less than 'radiusSize'
           outerRadius:0.7,

           //inner radius of the pie if 0 behaves as a full pie chart else gives a feel of donught
           innerRadius:0.4,

           //corner radius of each pie 
           cornerRadius:3,

           //padding between each slice in chart
           padding:0.015,

           //The position of the polyline to be drawn for labels should be greater than 'outerRadius' 
           spanningLine:0.8,

           //The position of the labels outside chart make sense to be equal to outer radius always but can experiment 
           textPositionRadius:0.7
        }
     }

     ----------
      Methods
     ----------
      #1 : pie-click
       Function params include : (param1:<data of the pie clicked>,param2:percentage value of pie clicked:param3:svg object clicked)

     --------
      Events
     --------
     #1 : span-labels
        Hook so that user can define his own text in the text labels
        Return type : String
        params passed include : (param1:<data of the pie in context>,param2:percentage value of pie in context:param3:svg object)

     #2 : hover-labels
        Hook so that user can define his own text in the hover tooltip
        Return type : String
        params passed include : (param1:<data of the pie in context>,param2:percentage value of pie in context:param3:svg object)
   
     ------------------------------
      mixins supported for styling
     ------------------------------
      --polyline-opacity : To set opacity of the line for labels
      --polyline-stroke-color, : To set stroke color of line
      --polyline-stroke-width : To set stroke width of line
      --polyline-fill : To set fill color for line 
      
      --label-text-size : To set label font size
      --label-stroke-color : To set label color
      --label-stroke-width : To set label stroke width
      
     */
    class AwesomePieChart extends Polymer.Element {
      static get is() { return 'awesome-pie-chart'; }
      static get properties() {
        return {
          chartConfig:{
            type: Object
          },
          __defaultConfig:{
            type: Object,
            value:{
               chartSize:300,
               radiusSize:0.8,
               labelsInside:false,
               color:[],
               disableHoverAnimation:false,
               label:"",
               prop:"",
               hoverAnimationDuration:1000,
               outerRadius:0.7,
               innerRadius:0.4,
               cornerRadius:3,
               padding:0.015,
               spanningLine:0.8,
               hoverRadius:0.8,
               textPositionRadius:0.7
            },
            observer:'_dataChanged'
          },
          data:{
            type: Array,
            value:[],
            observer:'_dataChanged'
          }
        };
      }
      ready(){
        super.ready();
      }
      _dataChanged(){
        if(this.data){
            //Check for label property 
            if(!this.chartConfig.label || typeof this.chartConfig.label !== "string"){
                throw new Error("'label' property of type string not defined for chartConfig");
                return;
            }
            //Check for prop property
            if(!this.chartConfig.prop || typeof this.chartConfig.prop !== "string"){
                throw new Error("'prop' property of type string not defined for chartConfig");
                return;
            }

            if(this.chartConfig.radiusSize && (typeof this.chartConfig.radiusSize !== "number" || this.chartConfig.radiusSize < 0 || this.chartConfig.radiusSize > 1)){
                throw new Error("'radiusSize' should be a numerical value between 0 to 1");
                return;
            }

            this.paintDelayedChart();
        }
      }
      paintDelayedChart(){
        var self = this;
        setTimeout(()=>self.drawPieChartViz(),0)
      }
      drawPieChartViz(){
        this.chartConfig = Object.assign(JSON.parse(JSON.stringify(this.__defaultConfig)),this.chartConfig);

        //Remove the old scraps
        d3.select(this.$.spanningPieChart).html("");

        //Size and margin set
        var self = this;
        var margin = {top: 20, right: 0, bottom: 0, left: 0};
        var width = (this.offsetWidth == 0) ? this.chartConfig.chartSize : this.offsetWidth;
        var height = width;
        width = width - margin.left - margin.right,
        height = height - margin.top - margin.bottom;

        //Calculate the size of the radius
        var radius = Math.floor((Math.min(width, height) / 2) * this.chartConfig.radiusSize) ;

        //some cosmetics
        var legendRectSize = (radius * 0.05);
        var legendSpacing = radius * 0.04;


        //Set the color scale
        var color = (this.chartConfig.color && this.chartConfig.color.length > 0) ? this.chartConfig.color : d3.schemeCategory20;

        //Begin the chart drawing by property passed
        var pie = d3.pie()
        .value((d)=>d[this.chartConfig.prop])
        .sort(null);

        //Get the basic arc in place based on passed params
        var arc = d3.arc()
        .outerRadius(radius * this.chartConfig.outerRadius)
        .innerRadius(radius * this.chartConfig.innerRadius)
        .cornerRadius(this.chartConfig.cornerRadius)
        .padAngle(this.chartConfig.padding);

        //Get the outer arc padded so that spanning polyline can be drawn
        var outerArc = d3.arc()
        .outerRadius(radius * this.chartConfig.spanningLine)
        .innerRadius(radius * this.chartConfig.spanningLine);

        //Define Arc params on hover effect
        var outerArcAnimate = d3.arc()
        .outerRadius(radius * this.chartConfig.hoverRadius)
        .cornerRadius(this.chartConfig.cornerRadius)
        .padAngle(this.chartConfig.padding)
        .innerRadius(radius * this.chartConfig.innerRadius);

        //Lets get the ground ready for charts
        var svg = d3.select(this.$.spanningPieChart).append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('g')
        .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');

        //Group elements such as they adhere to a modular approach
        var slices = svg.append('g').attr('class', 'slices');
        var labels = svg.append('g').attr('class', 'labelName');
        var lines = svg.append('g').attr('class', 'lines');
        var legends = svg.append('g').attr('class','legend')

        //Add the basic shape using `arc` defined earlier
        var path = slices.selectAll("path")
        .data(pie(this.data))
        .enter().append('path')
        .attr("class",()=>(this.pieClick && typeof this.pieClick === "function")?"cursor-pointer":"")
        .attr('fill',(d,i)=> color[i])
        .attr('d', arc)
        .on("mouseenter",function(d){
            if(self.chartConfig.disableHoverAnimation)
                return;
            d3.select(this)
            .transition()
            .duration(self.chartConfig.hoverAnimationDuration)
            .attr("d", outerArcAnimate)
        })
        .on("mouseleave",function(d){
            if(self.chartConfig.disableHoverAnimation)
                return;
            d3.select(this).transition()
            .attr("d", arc)
        })
        .on('click',function(d){
            if(self.pieClick && typeof self.pieClick === "function")
                self.pieClick(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this);
        })
        .append("title").text(function(d){
            return (self.hoverLabels && typeof self.hoverLabels === "function") ? self.hoverLabels(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this) : ((d.endAngle - d.startAngle) / (2 * Math.PI) * 100).toFixed(1) + " %";
        });

        if(!this.chartConfig.labelsInside){
            //draw the spanning labels
            var label = labels.selectAll('text')
            .data(pie(this.data))
            .enter().append('text')
            .attr('dy', '.35em')
            .html(function(d){
                //add "key: value" for given category.
                return (self.spanLabels && typeof self.spanLabels === "function") ? self.spanLabels(d.data,((d.endAngle - d.startAngle) / (2 * Math.PI) * 100),this) : d.data[self.chartConfig.label] + ': <tspan>' + d.data[self.chartConfig.prop] + '</tspan>';
            })
            .attr('class',"pie-label-text")
            .attr('transform',(d)=>{
                // effectively computes the centre of the slice.
                // see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid
                var pos = outerArc.centroid(d);
                // changes the point to be on left or right depending on where label is.
                pos[0] = radius * this.chartConfig.textPositionRadius * (midAngle(d) < Math.PI ? 1 : -1);
                return 'translate(' + pos + ')';
            })
            .style('text-anchor',(d)=>{
                // if slice centre is on the left, anchor text to start, otherwise anchor to end
                return (midAngle(d)) < Math.PI ? 'start' : 'end';
            });

            // add lines connecting labels to slice. A polyline creates straight lines connecting several points
            var polyline = lines.selectAll('polyline')
            .data(pie(this.data))
            .enter().append('polyline')
            .attr('points',(d)=>{
                // see label transform function for explanations of these three lines.
                var pos = outerArc.centroid(d);
                pos[0] = radius * this.chartConfig.textPositionRadius * (midAngle(d) < Math.PI ? 1 : -1);
                return [arc.centroid(d), outerArc.centroid(d), pos]
            });

            // calculates the angle for the middle of a slice
            function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; }
        }else{
            var legend = legends.selectAll('.legend')
            .data(this.data)
            .enter()
            .append('g')
            .attr('class', 'legend')
            .attr('transform',(d,i)=> {
                var height = legendRectSize + legendSpacing;
                var offset = height * this.data.length / 2;
                var horz = -6 * legendRectSize;
                var vert = i * height - offset;
                return 'translate(' + horz + ',' + vert + ')';
            });

            legend.append('rect')
            .attr('width', legendRectSize)
            .attr('height', legendRectSize)
            .style('fill', (d,i)=> color[i])
            .style('stroke', (d,i)=> color[i]);

            legend.append('text')
            .attr('x', legendRectSize + legendSpacing)
            .attr('y', legendRectSize)
            .text(function(d) {
                return d[self.chartConfig.label]
            });
        }
      }
    }

    window.customElements.define(AwesomePieChart.is, AwesomePieChart);
  </script>
</dom-module>