Skip to content
Snippets Groups Projects
detail-donut-script.blade.php 9.04 KiB
Newer Older
function DetailDonut(data, element) {

    // chart dimensions
    var width = 800;
    var height = 800;

    // a circle chart needs a radius
    var radius = Math.min(width, height) / 2;
    var donutWidth = 100; // size of donut hole. not needed if doing pie chart

    // legend dimensions
    var legendRectSize = 25; // defines the size of the colored squares in legend
    var legendSpacing = 6; // defines spacing between squares

    // define color scale
    var color = d3.scaleOrdinal(d3.schemePuBu[9]);

    // calculate new total
    var total = d3.sum(data, d => d.value);

    // define new total section
    var newTotal = d3.select('.new-total-holder')
        .append('span')
        .attr('class', 'newTotal').text(total);

    var svg = d3.select(element) // select element provided to the method
        .append('svg') // append an svg element to the element we've selected
        .attr('width', width) // set the width of the svg element we just added
        .attr('height', height) // set the height of the svg element we just added
        .append('g') // append 'g' element to the svg element
        .attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')'); // our reference is now to the 'g' element. centerting the 'g' element to the svg element

    var arc = d3.arc()
        .innerRadius(radius - donutWidth) // radius - donutWidth = size of donut hole. use 0 for pie chart
        .outerRadius(radius); // size of overall chart

    var pie = d3.pie() // start and end angles of the segments
        .value(function(d) { return d.value; }) // how to extract the numerical data from each entry in our dataset
        .sort(null); // by default, data sorts in oescending value. this will mess with our animation so we set it to null

    //**********************
    //        TOOLTIP
    //**********************

    //var tooltip = d3.select(element) // select element in the DOM provided to the method
    //    .append('div') // append a div element to the element we've selected                                    
    //    .attr('class', 'tooltip'); // add class 'tooltip' on the divs we just selected

    var tooltip = d3.select(element)
            .append('div')
            .attr('class', 'd3-tooltip')
            .style('position', 'absolute')
            .style('z-index', '10')
            .style('visibility', 'hidden')
            .style('padding', '10px')
            .style('background', 'rgba(0,0,0,0.6)')
            .style('border-radius', '4px')
            .style('color', '#fff');

    tooltip.append('div') // add divs to the tooltip defined above
        .attr('class', 'label'); // add class 'label' on the selection
    tooltip.append('div') // add divs to the tooltip defined above   
        .attr('class', 'value'); // add class 'value' on the selection                  
    tooltip.append('div') // add divs to the tooltip defined above  
        .attr('class', 'percent'); // add class 'percent' on the selection

    data.forEach(function(d) {
        d.value = +d.value; // calculate value as we iterate through the data
        d.enabled = true; // add enabled property to track which entries are checked
    });

    // creating the chart
    var path = svg.selectAll('path') // select all path elements inside the svg. specifically the 'g' element. they don't exist yet but they will be created below
        .data(pie(data)) //associate dataset wit he path elements we're about to create. must pass through the pie function. it magically knows how to extract values and bakes it into the pie
        .enter() //creates placeholder nodes for each of the values
        .append('path') // replace placeholders with path elements
        .attr('d', arc) // define d attribute with arc function above
        .attr('fill', function(d) { return color(d.data.label); }) // use color scale to define fill of each label in dataset
        .each(function(d) { this._current - d; }); // creates a smooth animation for each track

    // mouse event handlers are attached to path so they need to come after its definition
    path.on('mouseover', function(event, d, i) {  // when mouse enters div      
        var total = d3.sum(data.map(function(d) { // calculate the total number of tickets in the dataset         
            return (d.enabled) ? d.value : 0; // checking to see if the entry is enabled. if it isn't, we return 0 and cause other percentages to increase                                      
        }));                                                      
        var percent = Math.round(1000 * d.data.value / total) / 10; // calculate percent
        tooltip.select('.label').html('Agent: ' + d.data.label); // set current label           
        tooltip.select('.value').html('Value: ' + d.data.value); // set current value            
        tooltip.select('.percent').html('Percentage: ' + percent + '%'); // set percent calculated above          
        tooltip.style('visibility', 'visible'); // set visibility                     
    });                                                           

    path.on('mouseout', function(event, d) { // when mouse leaves div                        
        tooltip.style('visibility', 'hidden'); // hide tooltip for that element
    });

    path.on('mousemove', function(event, d) { // when mouse moves                  
        tooltip.style('top', (event.layerY + 10) + 'px') // always 10px below the cursor
            .style('left', (event.layerX + 10) + 'px'); // always 10px to the right of the mouse
    });

    // define legend
    var legend = svg.selectAll('.legend') // selecting elements with class 'legend'
        .data(color.domain()) // refers to an array of labels from our dataset
        .enter() // creates placeholder
        .append('g') // replace placeholders with g elements
        .attr('class', 'legend') // each g is given a legend class
        .attr('transform', function(d, i) {                   
            var height = legendRectSize + legendSpacing; // height of element is the height of the colored square plus the spacing      
            var offset =  height * color.domain().length / 2; // vertical offset of the entire legend = height of a single element & half the total number of elements  
            var horz = -2 * legendRectSize; // the legend is shifted to the left to make room for the text
            var vert = i * height - offset; // the top of the element is hifted up or down from the center using the offset defiend earlier and the index of the current element 'i'               
            return 'translate(' + horz + ',' + vert + ')'; //return translation       
        });

    // adding colored squares to legend
    legend.append('rect') // append rectangle squares to legend                                   
    .attr('width', legendRectSize) // width of rect size is defined above                        
    .attr('height', legendRectSize) // height of rect size is defined above                      
    .style('fill', color) // each fill is passed a color
    .style('stroke', color) // each stroke is passed a color
    .on('click', function(label) {
        var rect = d3.select(this); // this refers to the colored squared just clicked
        var enabled = true; // set enabled true to default
        var totalEnabled = d3.sum(data.map(function(d) { // can't disable all options
            return (d.enabled) ? 1 : 0; // return 1 for each enabled entry. and summing it up
        }));
        if (rect.attr('class') === 'disabled') { // if class is disabled
            rect.attr('class', ''); // remove class disabled
        } else { // else
            if (totalEnabled < 2) return; // if less than two labels are flagged, exit
            rect.attr('class', 'disabled'); // otherwise flag the square disabled
            enabled = false; // set enabled to false
        }

        pie.value(function(d) { 
        if (d.label === label) d.enabled = enabled; // if entry label matches legend label
            return (d.enabled) ? d.value : 0; // update enabled property and return value or 0 based on the entry's status
        });

        path = path.data(pie(data)); // update pie with new data

        path.transition() // transition of redrawn pie
        .duration(750) // 
        .attrTween('d', function(d) { // 'd' specifies the d attribute that we'll be animating
            var interpolate = d3.interpolate(this._current, d); // this = current path element
            this._current = interpolate(0); // interpolate between current value and the new value of 'd'
            return function(t) {
            return arc(interpolate(t));
            };
        });
        
        // calculate new total
        var newTotalCalc = d3.sum(data.filter(function(d) { return d.enabled;}), d => d.value)
     console.log(newTotalCalc);
    
        // append newTotalCalc to newTotal which is defined above
        newTotal.text(newTotalCalc);
    });

    // adding text to legend
    legend.append('text')                                    
        .attr('x', legendRectSize + legendSpacing)
        .attr('y', legendRectSize - legendSpacing)
        .text(function(d) { return d; }); // return label
}