/* ------------------------------------------------------------------------------
 *
 *  # D3.js - hierarchical bar chart
 *
 *  Demo d3.js hierarchical bar chart setup with .json data
 *
 *  Version: 1.0
 *  Latest update: August 1, 2015
 *
 * ---------------------------------------------------------------------------- */

$(function () {

    // Initialize chart
    stackedMultiples('#d3-hierarchical-bars', 400);

    // Chart setup
    function stackedMultiples(element, height) {


        // Basic setup
        // ------------------------------

        // Define main variables
        var d3Container = d3.select(element),
            margin = {top: 25, right: 40, bottom: 20, left: 130},
            width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right,
            height = height - margin.top - margin.bottom - 5,
            barHeight = 30,
            duration = 750,
            delay = 25;



        // Construct scales
        // ------------------------------

        // Horizontal
        var x = d3.scale.linear()
            .range([0, width]);

        // Colors
        var color = d3.scale.ordinal()
            .range(["#26A69A", "#ccc"]);



        // Create axes
        // ------------------------------

        // Horizontal
        var xAxis = d3.svg.axis()
            .scale(x)
            .orient("top");



        // Create chart
        // ------------------------------

        // Add SVG element
        var container = d3Container.append("svg");

        // Add SVG group
        var svg = container
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


        // Construct chart layout
        // ------------------------------

        // Partition
        var partition = d3.layout.partition()
            .value(function(d) { return d.size; });



        // Load data
        // ------------------------------

        d3.json("assets/demo_data/d3/bars/bars_hierarchical.json", function(error, root) {
            partition.nodes(root);
            x.domain([0, root.value]).nice();
            down(root, 0);
        });


        //
        // Append chart elements
        //

        // Add background bars
        svg.append("rect")
            .attr("class", "d3-bars-background")
            .attr("width", width)
            .attr("height", height)
            .style("fill", "#fff")
            .on("click", up);


        // Append axes
        // ------------------------------

        // Horizontal
        svg.append("g")
            .attr("class", "d3-axis d3-axis-horizontal d3-axis-strong");


        // Append bars
        // ------------------------------

        // Create hierarchical structure
        function down(d, i) {
            if (!d.children || this.__transition__) return;
            var end = duration + d.children.length * delay;

            // Mark any currently-displayed bars as exiting.
            var exit = svg.selectAll(".enter")
                .attr("class", "exit");

            // Entering nodes immediately obscure the clicked-on bar, so hide it.
            exit.selectAll("rect").filter(function(p) { return p === d; })
                .style("fill-opacity", 1e-6);

            // Enter the new bars for the clicked-on data.
            // Per above, entering bars are immediately visible.
            var enter = bar(d)
                .attr("transform", stack(i))
                .style("opacity", 1);

            // Have the text fade-in, even though the bars are visible.
            // Color the bars as parents; they will fade to children if appropriate.
            enter.select("text").style("fill-opacity", 1e-6);
            enter.select("rect").style("fill", color(true));

            // Update the x-scale domain.
            x.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice();

            // Update the x-axis.
            svg.selectAll(".d3-axis-horizontal").transition()
                .duration(duration)
                .call(xAxis);

            // Transition entering bars to their new position.
            var enterTransition = enter.transition()
                .duration(duration)
                .delay(function(d, i) { return i * delay; })
                .attr("transform", function(d, i) { return "translate(0," + barHeight * i * 1.2 + ")"; });

            // Transition entering text.
            enterTransition.select("text")
                .style("fill-opacity", 1);

            // Transition entering rects to the new x-scale.
            enterTransition.select("rect")
                .attr("width", function(d) { return x(d.value); })
                .style("fill", function(d) { return color(!!d.children); });

            // Transition exiting bars to fade out.
            var exitTransition = exit.transition()
                .duration(duration)
                .style("opacity", 1e-6)
                .remove();

            // Transition exiting bars to the new x-scale.
            exitTransition.selectAll("rect")
                .attr("width", function(d) { return x(d.value); });

            // Rebind the current node to the background.
            svg.select(".d3-bars-background")
                .datum(d)
                .transition()
                .duration(end);

            d.index = i;
        }

        // Return to parent level
        function up(d) {
            if (!d.parent || this.__transition__) return;
            var end = duration + d.children.length * delay;

            // Mark any currently-displayed bars as exiting.
            var exit = svg.selectAll(".enter")
                .attr("class", "exit");

            // Enter the new bars for the clicked-on data's parent.
            var enter = bar(d.parent)
                .attr("transform", function(d, i) { return "translate(0," + barHeight * i * 1.2 + ")"; })
                .style("opacity", 1e-6);

            // Color the bars as appropriate.
            // Exiting nodes will obscure the parent bar, so hide it.
            enter.select("rect")
                .style("fill", function(d) { return color(!!d.children); })
                .filter(function(p) { return p === d; })
                .style("fill-opacity", 1e-6);

            // Update the x-scale domain.
            x.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice();

            // Update the x-axis.
            svg.selectAll(".d3-axis-horizontal").transition()
                .duration(duration)
                .call(xAxis);

            // Transition entering bars to fade in over the full duration.
            var enterTransition = enter.transition()
                .duration(end)
                .style("opacity", 1);

            // Transition entering rects to the new x-scale.
            // When the entering parent rect is done, make it visible!
            enterTransition.select("rect")
                .attr("width", function(d) { return x(d.value); })
                .each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); });

            // Transition exiting bars to the parent's position.
            var exitTransition = exit.selectAll("g").transition()
                .duration(duration)
                .delay(function(d, i) { return i * delay; })
                .attr("transform", stack(d.index));

            // Transition exiting text to fade out.
            exitTransition.select("text")
                .style("fill-opacity", 1e-6);

            // Transition exiting rects to the new scale and fade to parent color.
            exitTransition.select("rect")
                .attr("width", function(d) { return x(d.value); })
                .style("fill", color(true));

            // Remove exiting nodes when the last child has finished transitioning.
            exit.transition()
                .duration(end)
                .remove();

            // Rebind the current parent to the background.
            svg.select(".d3-bars-background")
                .datum(d.parent)
                .transition()
                .duration(end);
        }

        // Creates a set of bars for the given data node, at the specified index.
        function bar(d) {
            var bar = svg.insert("g", ".d3-axis-vertical")
                .attr("class", "enter")
                .attr("transform", "translate(0,5)")
                .selectAll("g")
                .data(d.children)
                .enter()
                .append("g")
                    .style("cursor", function(d) { return !d.children ? null : "pointer"; })
                    .on("click", down);

            bar.append("text")
                .attr("x", -6)
                .attr("y", barHeight / 2)
                .attr("dy", ".35em")
                .style("text-anchor", "end")
                .text(function(d) { return d.name; });

            bar.append("rect")
                .attr("width", function(d) { return x(d.value); })
                .attr("height", barHeight);

            return bar;
        }

        // A stateful closure for stacking bars horizontally.
        function stack(i) {
            var x0 = 0;
            return function(d) {
                var tx = "translate(" + x0 + "," + barHeight * i * 1.2 + ")";
                x0 += x(d.value);
                return tx;
            };
        }



        // Resize chart
        // ------------------------------

        // Call function on window resize
        $(window).on('resize', resize);

        // Call function on sidebar width change
        $('.sidebar-control').on('click', resize);

        // Resize function
        // 
        // Since D3 doesn't support SVG resize by default,
        // we need to manually specify parts of the graph that need to 
        // be updated on window resize
        function resize() {

            // Layout variables
            width = d3Container.node().getBoundingClientRect().width - margin.left - margin.right;


            // Layout
            // -------------------------

            // Main svg width
            container.attr("width", width + margin.left + margin.right);

            // Width of appended group
            svg.attr("width", width + margin.left + margin.right);


            // Axes
            // -------------------------

            // Horizontal range
            x.range([0, width]);

            // Horizontal axis
            svg.selectAll('.d3-axis-horizontal').call(xAxis);


            // Chart elements
            // -------------------------

            // Bars
            svg.selectAll('.enter rect').attr("width", function(d) { return x(d.value); });
        }
    }
});
