gerd
11/8/2016 - 2:09 PM

piechart.js

/**
* This javascript provides function for drawing pie chart
* @class lexisnexis.component.drawPieChart
*/
jQuery.namespace('lexisnexis.component');
/**
* Draw the pie chart
* @method  lexisnexis.component.drawPieChart
*/
lexisnexis.component.drawPieChart = function (divId, data, configValues, jsonData) {
    try {
        dataSet=jQuery.parseJSON(data);
    }catch(err) {
        dataSet=data;
    }
    try {
        config=jQuery.parseJSON(configValues);
    }catch(err) {
        config=configValues;
    }

    var defaultConfig = {
        canvasWidth : 380, canvasHeight : 290, viewboxLeft : 0, viewboxTop : 0,
        outerRadius : 80, innerRadius : 0,
        contentOffsetX : 0, contentOffsetY : 0,
        legendBulletOffsetX : 20, legendBulletOffsetY : -5, legendTextOffset : 20, textVerticalSpace : 35, legendVerticalOffset : 30, legendWrappedTextOffsetY : 14,
        enableLinks : false, enableChart : true, labelText : true, legend : false, legendLabel : true, legendLinkLabel : '', linkURL : '', isPdf : false,
        toolTipArcId : 'tooltipArc', toolTipTextId : 'tooltipText', disabledTextColor : '#bababa'
    };
    $.extend(defaultConfig, config);

    defaultConfig.enableChart = jsonData == null || typeof jsonData.enabled == 'undefined' || jsonData.enabled;

    // x and y coordinates for the pie center.
    var pieCenterX = defaultConfig.outerRadius;
    var pieCenterY = defaultConfig.outerRadius;
    var enableLinks = defaultConfig.enableLinks;
    var isStratification = (typeof defaultConfig.isStratification != 'undefined' && defaultConfig.isStratification);
    var legendLabel = defaultConfig.legendLabel;
    var emptyArr = [];
    // The WKHMTLTOPDF webkit does not honor certain attributes when rendering arcs at values of 100%.
    // STROKE and STROKE-WIDTH suffer.  Hence 'singleColor' will be used to determine the border color
    // of the arc.  This eliminates the white line that appears in a single pie wedge at 100%.
    var singleColor = 'white';
    //variable used for specifying arc value
    var numSlices=0;
    var maxLength = 21;
    dataSet.forEach(function (d,i) {
        if(parseFloat(d.value)>0){
            emptyArr.push(true);
            numSlices++;
            singleColor = (numSlices < 2 ? d.color : 'white');
        }else{
            emptyArr.push(false);
        }
        if (!isStratification) {
            d.lines = wordwrap(d.label, maxLength);
        }
    });
    var isEmpty = emptyArr.indexOf(true) < 0;

    // Creating x and y scale to match the pie circumference

    function calcMid(d) {
        var angle = d.startAngle + ((d.endAngle - d.startAngle) / 2);
        var r = defaultConfig.outerRadius+10;
        return {
            x: r * Math.cos(angle),
            y: r * Math.sin(angle)
        };
    };

    var responsiveClass = "ln-piechart-responsive";
    if (isStratification) {
        responsiveClass = "ln-strat-piechart-responsive";
    }
    // Creating svg container element - all drawing components will be wrapped inside this container.
    var canvas=d3.select(divId).classed("ln-svg-container", true).append("svg:svg").classed(responsiveClass, true);

    // Data to be used.  Define viewport for the canvas, or svg container.
    var vboxAttr = [defaultConfig.viewboxLeft,defaultConfig.viewboxTop,defaultConfig.canvasWidth,defaultConfig.canvasHeight].join(' ');
    var groupAll = canvas.data([dataSet]).attr('viewBox',vboxAttr).attr('perserveAspectRatio','xMinYMin meet')
        .style("overflow","visible")
        // Adding group to hold our pie chart and legend.
        .append("svg:g");

    var groupLabel = null;
    var groupPieChart = null;
    var arc = 0;
    if(dataSet.length > 0){
        if (defaultConfig.labelText) {
            var linkClass = defaultConfig.enableChart ? 'ln-pieChart-label-legend' : '';
            // Create the legend label group.
            groupLabel = groupAll.append("svg:g");
            var elem = groupLabel;
            if(legendLabel && enableLinks && defaultConfig.enableChart){
                linkClass = 'ln-pieChart-label-legend-underLine';
                elem = groupLabel.append("svg:a")
                    .attr("xlink:href", LN.getContextPath() + defaultConfig.linkURL);
            }
            elem.append("text")
                .attr("text-anchor", "center")
                .attr("x", 0)
                .attr("y", 0)
                .attr("dx", 0)
                .attr("dy", "5px")
                .attr("class", linkClass)
                .text(defaultConfig.legendLinkLabel);
            if (!defaultConfig.enableChart) {
                elem.selectAll("text").style("fill", defaultConfig.disabledTextColor);
            }
        }
    }
    if(dataSet.length > 0 && defaultConfig.enableChart){
        groupPieChart = groupAll.append("svg:g");
        // Creating the arc both inner as well as outer
        arc = d3.svg.arc().outerRadius(defaultConfig.outerRadius);
        // Creating the pie layout to use the value from the dataset
        var pie = d3.layout.pie().value(function(d) {
            return d.value;
        });

        if (enableLinks) {
            // Creating slices in the group
            arcs = groupPieChart.selectAll("g.slice")
                // Pie to be used as the data. The pie will create the start and end angles for the data passed to it earlier.
                .data(pie)
                // Adding links to the slices.
                .enter().append("svg:a")
                .attr("xlink:href", function(d) { return LN.getContextPath()+d.data.link; })
                .on("mouseover", function(d) {
                    d3.select(this).select('text').style('text-decoration', 'underline');
                })
                .on("mouseleave", function(d) {
                    d3.select(this).select('text').style('text-decoration', 'none');
                })
                .on("mouseout", function(d) {
                    d3.select(this).select('text').style('text-decoration', 'none');
                });
        }else{
            arcs = groupPieChart.selectAll("g.slice").data(pie).enter();
        }

        // Setting the color to each of the slices.
        arcs.append("svg:path")
            .style("fill", function(d) { return d.data.color; })
            .attr("id", function(d) { return divId+"_"+d.data.label; })
            .attr("stroke-width", function(){
                 return "1.5";
            })
            .attr("stroke", function(d) {
                return singleColor;
            })
            .attr("d", arc)
            .on("mouseover", function(d){ mouseOverArcCallback(d.value, d.data.label, d.data.color, defaultConfig, this); })
            .on("mouseleave", function(d) {
                var text = $('#'+defaultConfig.toolTipArcId).text();
                if (text.indexOf(d.data.label) > -1) {
                    removeToolTip(defaultConfig.toolTipArcId);
                }
            })
            .on("mouseout", function(d){ mouseOutArcCallback(this, d.value); });
        arcs.append("svg:text")
            // Set Outer Coordinate
            .attr("transform", function(d) {
                 d.outerRadius = defaultConfig.outerRadius-10;
                 d.innerRadius = defaultConfig.outerRadius/3;
                 return "translate(" + arc.centroid(d) + ")";
            })
            .attr("text-anchor", "middle")
            .attr("dy", ".28em")
            .text(function (d) {
                return numeral(d.value).format('0.00%');
            })
            .attr('class','ln-piechart-label')
            .style('display', function (d) {
                var bb = this.getBBox(),
                    center = arc.centroid(d);

                var topLeft = {
                  x : center[0] + bb.x,
                  y : center[1] + bb.y
                };

                var topRight = {
                  x : topLeft.x + bb.width,
                  y : topLeft.y
                };

                var bottomLeft = {
                  x : topLeft.x,
                  y : topLeft.y + bb.height
                };

                var bottomRight = {
                  x : topLeft.x + bb.width,
                  y : topLeft.y + bb.height
                };

                d.visible = pointIsInArc(topLeft, d, arc) &&
                            pointIsInArc(topRight, d, arc) &&
                            pointIsInArc(bottomLeft, d, arc) &&
                            pointIsInArc(bottomRight, d, arc);
                d.outerRadius = defaultConfig.outerRadius;
                d.innerRadius = defaultConfig.innerRadius;

                return d.visible ? null : "none";
            })
            .on("mouseover", function(d){
                var id="path[id='"+divId+"_"+d.data.label+"']";
                d3.select(id);
                var val=getData(d, d.data.label);
                mouseOverTextCallback(val.value, val.label, val.color, defaultConfig, id);
            })
            .on("mouseleave", function(d){
                var text = $('#'+defaultConfig.toolTipTextId).html();
                if ($('<div/>').html(text).text().indexOf(d.data.label) > -1) {
                    removeToolTip(defaultConfig.toolTipTextId);
                }
            });
    } else {
        // Render a simple circle with a gray border if chart is disabled.
    	groupPieChart = groupAll.append("svg:g");
        groupPieChart.append("circle").attr("r", defaultConfig.outerRadius)
            .attr("fill", "#fff").attr("stroke", "#8e8e8e")
            .attr("stroke-width", "1.5")
            .attr("id", function(d) { return divId+"_DISABLED"; });
    }

    function pointIsInArc(pt, ptData, d3Arc) {
        // Center of the arc is assumed to be 0,0
        // (pt.x, pt.y) are assumed to be relative to the center
        var r1 = arc.innerRadius()(ptData),
            r2 = arc.outerRadius()(ptData),
            theta1 = arc.startAngle()(ptData),
            theta2 = arc.endAngle()(ptData);

        var dist = pt.x * pt.x + pt.y * pt.y,
            angle = Math.atan2(pt.x, -pt.y);

        angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
        return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
               (theta1 <= angle) && (angle <= theta2);
    }

    // Adding bullets for the legend
    var valueOffsetY = 0;
    var groupLegend = null;
    if (!isStratification) {
        groupLegend = groupAll.append("svg:g");
        groupLegend.selectAll("legendRect")
            // Adding bullets to the svg.
            .data(dataSet).enter().append("svg:rect")
            // Setting the circle's x and y coordinates for the center of the legend circle.
            .attr("x", 0)
            .attr("y", function(d, i) {
                var thisY = i*defaultConfig.textVerticalSpace + defaultConfig.legendBulletOffsetY + valueOffsetY;
                valueOffsetY += (defaultConfig.isPdf && !isStratification && d.lines.length>1) ? defaultConfig.legendWrappedTextOffsetY : 0;
                return thisY;
            })
            .attr("width", 10)
            .attr("height", 10)
            .attr("stroke-width", ".5")
            // Setting color to match the pie color
            .style("fill", function(d) { return d.color; })
            .style("stroke","black")
            .attr("r", 5);
        if (!defaultConfig.enableChart) {
            groupLegend.selectAll("rect").style("fill", defaultConfig.disabledTextColor).style("stroke", defaultConfig.disabledTextColor);
        }
    }

    // Setting hyperlinks for the legend text as well.
    if (isStratification) {
        LN.debug('inside stratification');
        var colorStyle = "", borderStyle = "";
        if (!defaultConfig.enableChart) {
            colorStyle = "color:"+defaultConfig.disabledTextColor+";";
        }
        var $legendDiv = $('<div class="pull-left ln-legendDiv" style="margin-left:'+defaultConfig.legendBulletOffsetX+';'+colorStyle+'"></div>');
        var $parent = $(divId).parent();
        $parent.find('.ln-legendDiv').remove();
        var elem = $legendDiv.appendTo($parent);
        var legendLabel;
        dataSet.forEach(function (d,i) {
            LN.debug(d.value);
            colorStyle = !defaultConfig.enableChart ? defaultConfig.disabledTextColor : d.color;
            borderStyle = !defaultConfig.enableChart ? "border:none;" : "";
            legendLabel = ['<div><div style="margin-right:',defaultConfig.legendTextOffset,';background-color:',colorStyle,';',borderStyle,'" class="ln-display-block ln-strat-bullet"></div>'].join('');
            if (defaultConfig.enableChart && enableLinks && !isEmpty && d.value > 0.0){
                LN.debug('inside enable links');
                legendLabel = [legendLabel,
                               '<a id="legendId_',i,'" href="', LN.getContextPath(), d.link, '" class="ln-pieChart-links">',d.label,'</a></div>'].join('');
            } else {
                legendLabel = [legendLabel,
                               '<span id="legendId_',i,'" class="ln-strat-pieChart-noLinks">',d.label,'</span></div>'].join('');
            }
            $(legendLabel).appendTo($legendDiv);
            if (defaultConfig.enableChart) {
                $legendDiv.find('a#legendId_'+i)
                .on("mouseover", function(e) {
                    d3.event = e;
                    var id="path[id='"+divId+"_"+this.textContent+"']";
                    d3.select(id);
                    var val=getData(d, this.textContent);
                    mouseOverArcCallback(val.value, '', val.color, defaultConfig, id);
                    d3.event = null;
                })
                .on("mouseleave", function(e) { d3.event = e; removeToolTip(defaultConfig.toolTipArcId); d3.event = null; })
                .on("mouseout", function(e){
                    d3.event = e;
                    var id= "path[id='"+divId+"_"+this.textContent+"']";
                    mouseOutArcCallback(id, d.value);
                    d3.event = null;
                });
            }
        });
        $(divId).css('padding-bottom','60%');
    } else {
        valueOffsetY = 0;
        var labelClass = 'ln-pieChart-enableLinks';
        dataSet.forEach(function (d,i) {
            LN.debug(d.value);
            var elem = groupLegend;
            if (defaultConfig.enableChart && enableLinks && !isEmpty && d.value > 0.0){
                LN.debug('inside enable links');
                elem = groupLegend.append("svg:a")
                    .attr("xlink:href", LN.getContextPath()+d.link);
            }
            var textElem = elem.append("text")
                .attr("text-anchor", "center")
                .attr("x", defaultConfig.legendTextOffset)
                .attr("y", i*defaultConfig.textVerticalSpace + valueOffsetY)
                .attr("dx", 0)
                .attr("dy", "5px").each(function() {
                    var textSeparation = 0.40;
                    var textNode = d3.select(this);
                    var x = textNode.attr("x"),
                        y = textNode.attr("y");
                    for(var i = 0; i < d.lines.length; i++) {
                        if (i==0) {
                            textNode.text(null);
                        }
                        textNode.append('tspan').attr('x',x).attr('y',y).attr('dy', textSeparation + 'em').text(d.lines[i]);
                        y=parseInt(y) + defaultConfig.legendWrappedTextOffsetY;
                        textSeparation = 0.40;
                    }
                    if (defaultConfig.isPdf) {
                        textNode.append('tspan').attr('x',x).attr('y',y).attr('dy', textSeparation + 'em').text(numeral(d.value).format('0.00%'));
                    }
                    valueOffsetY += (defaultConfig.isPdf && d.lines.length>1) ? defaultConfig.legendWrappedTextOffsetY : 0;
                });
            if (defaultConfig.enableChart) {
                textElem.attr('class',labelClass)
                .on("mouseover", function(d) {
                    if(d.value > 0.0){
                        d3.select(this).selectAll('tspan').style('text-decoration','underline');
                    }
                    var id="path[id='"+divId+"_"+this.textContent+"']";
                    d3.select(id);
                    var val=getData(d, this.textContent);
                    mouseOverArcCallback(val.value, '', val.color, defaultConfig, id);
                })
                .on("mouseleave", function(d) {
                    if(d.value > 0.0){
                        d3.select(this).selectAll('tspan').style('text-decoration','none');
                    }
                    removeToolTip(defaultConfig.toolTipArcId);
                })
                .on("mouseout", function(d){
                    if(d.value > 0.0){
                        d3.select(this).selectAll('tspan').style('text-decoration','none');
                    }
                    var id= "path[id='"+divId+"_"+this.textContent+"']";
                    mouseOutArcCallback(id, d.value);
                });
            } else {
                textElem.attr("fill",defaultConfig.disabledTextColor);
            }
        });
    }
    var gElem = null;
    gElem = $(groupLabel);
    var gLblBBox = gElem != null && gElem.length > 0 ? $(gElem[0])[0].getBBox() : null;
    gElem = $(groupPieChart);
    var gPieBBox = gElem != null && gElem.length > 0 ? $(groupPieChart[0])[0].getBBox() : null;

    // Setting the center of pie3
    var offsetX = defaultConfig.contentOffsetX;
    var offsetY = defaultConfig.contentOffsetY;
    groupAll.attr("transform", ["translate(", offsetX, ',', offsetY, ")"].join(''));

    offsetX = defaultConfig.legendBulletOffsetX;
    offsetY = defaultConfig.legendVerticalOffset;
    if (gPieBBox != null) {
        offsetX += isStratification ? '' : pieCenterX*2;
        offsetY += isStratification ? pieCenterY*2 : 0;
    }
    if (!isStratification) {
        groupLegend.attr("transform", ["translate(", offsetX, ',', offsetY, ")"].join(''));
    }

    if (groupLabel != null) {
        offsetY = 0;
        // offsetX was set from previous operation.
        groupLabel.attr("transform", ["translate(", offsetX, ',', offsetY, ")"].join(''));
    }

    if (groupPieChart != null) {
        offsetX = pieCenterX;
        offsetY = pieCenterY;
        offsetY += isStratification ? (gLblBBox != null ? gLblBBox.height + defaultConfig.textVerticalSpace : 0)
            : defaultConfig.legendVerticalOffset + defaultConfig.legendBulletOffsetY;
        groupPieChart.attr("transform", ['translate(', offsetX, ',', offsetY, ')'].join(''));
    }

    function wordwrap(text, max) {
        var regex = new RegExp(".{0,"+max+"}(?:\\s|$)","g");
        var lines = [];
        var line;
        while ((line = regex.exec(text))!="") {
            lines.push(line);
        }
        return lines;
    }
    function getData(d, text) {
        var value=0;
        $.each(d, function(i) {
            if(!d.label){
                if(text.indexOf(d[i].label) == 0){
                    value= d[i];
                }
            }else if(text.indexOf(d.label) == 0){
                value=d;
            }
        });
        return value;
    }
    function tabulate(divId, data){
        var table = d3.select(divId).append("table").attr("border","2").attr("width","100%"),
        tbody = table.append("tbody");
        data.forEach(function (d,i) {
            var rows = tbody.append("tr");
            // Create a cell in each row for each column

            rows.append("td").text(numeral( d.value).format('0.00%') );
            rows.append("td").text(d.label);
        });
        return table;
    }
    function removeToolTip(id){
        if (id != null && $('#'+id).length > 0) {
            $('#'+id).remove();
        }
    }
    function mouseOutArcCallback(id, value){
        var thisColor = singleColor;
        if (value > 0 && value < 1) {
            thisColor = "white";
        }
        d3.select(id).transition()
          .attr("d", arc)
          .attr("stroke", thisColor);
    }
    function mouseOverArcCallback(value, label, color, defaultConfig, id){
        mouseOverCallback(value, label, color, defaultConfig, id, defaultConfig.toolTipArcId);
    }
    function mouseOverTextCallback(value, label, color, defaultConfig, id){
        mouseOverCallback(value, label, color, defaultConfig, id, defaultConfig.toolTipTextId);
    }
    function mouseOverCallback(value, label, color, defaultConfig, id, toolTipId){
        removeToolTip(toolTipId);

        div = d3.select("body").append("div");
        div.attr("class", "ln-chart-tooltip")
           .attr("id", toolTipId);

        var arcOver = d3.svg.arc().outerRadius(defaultConfig.outerRadius);
        $(id).closest('svg').find('path').each(function() {
            mouseOutArcCallback(this, value);
        });

        if (value > 0) {
            arcOver = d3.svg.arc().outerRadius(defaultConfig.outerRadius + 10);
            d3.select(id).transition()
              .duration(1000)
              .attr("d", arcOver);
        }

        var tip='';
        label = (label) ? ["<br/>",label].join('') : '';
        tip = div.html([numeral(value).format('0.00%'),label].join(''));
        tip.style("left", (d3.event.pageX+12) + "px")
           .style("top", (d3.event.pageY-10) + "px")
           .style("border-color", color)
           .style("opacity", 1)
           .style("display","block");
    }
};