charges.js 6.99 KB
const margin = {top: 60, right: 350, bottom: 100, left: 90},
    width = 1300 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

const tooltip_offset = {dx: 0, dy: 100}

const color_scale = d3.scaleOrdinal(d3.schemeCategory10);

const legend_cell_size = 15; // colored scare size
const legend_x = width+20; // begin legend a bit after the end of the chart

// TODO: set main scales attr here then domain later
// const x = d3.scaleBand()
//     .range([0, width])
//     .padding(0.2);
//
// const y = d3.scaleLinear()
//     .range([height, 0]);
//

function build_chart(div_selector, data_url, project_name, category) {

    if (category == 'capacity') {
        var chart_title = "Charges par fonction"
        var category_title = "Fonction"
    } else if (category == 'service') {
        var chart_title = "Charges par service"
        var category_title = "Service"
    }

    const svg = d3.select(div_selector).append("svg")
        .attr("id", "svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    const tooltip = d3.select('html')
        .append("div")
        .style("opacity", 0)
        .attr("class", "tooltip")

    var mousemove = function (e, d) {
        tooltip
            .style("left", (e.pageX - tooltip_offset.dx) + "px")
            .style("top", (e.pageY - tooltip_offset.dy) + "px")
    }

    var mouseleave = function (d) {
        tooltip
            .transition()
            .duration(100)
            .style("opacity", 0)
    }

    var mouseover = function (e, d) {
        var category_name = d3.select(this.parentNode).datum().key
        var category_charge = d.data[category_name]
        tooltip
            .transition()
            .duration(200)
            .style("opacity", 1);
        tooltip
            .html("<b>" + category_title + ":</b> " + category_name + "<br>" + "<b>Charge:</b> " + category_charge + "%")
            .style("left", (e.pageX - tooltip_offset.dx) + "px")
            .style("top", (e.pageY - tooltip_offset.dy) + "px")
    }

    var addlegend = function (color_scale) {

        let reverse_keys = color_scale.domain().reverse();

        let legend = svg.append('g')
            .attr('transform', 'translate('+legend_x+', -30)');

        legend.selectAll()
            .data(reverse_keys)
            .enter().append('rect')
            .attr('height', legend_cell_size + 'px')
            .attr('width', legend_cell_size + 'px')
            .attr('class', 'legend')
            .attr('x', 5)
            .attr('y', (d, i) => i * legend_cell_size)
            .style("fill", d => color_scale(d));

        legend.selectAll()
            .data(reverse_keys)
            .enter().append('text')
            .attr("transform", (d, i) => "translate(30, " + (i * legend_cell_size + legend_cell_size / 1.6) + ")")
            .attr('class', 'legend')
            // .style("font-size", "10px")
            // .style("font-weight", "bold")
            .style("fill", "black")
            .text(d => d);
    }

    d3.csv(data_url).then(data => {
        // var categories = data.columns.slice(1)
        var periods = d3.map(data, d => d.period)
        var categories_total_charge = {}

        // Get the charge sum for each categories over periods
        // That will leave '0' to categories with no charge at all
        data.forEach(function (d) {
            Object.keys(d).forEach(function (k) {
                    if (categories_total_charge.hasOwnProperty(k)) {
                        categories_total_charge[k] += +d[k]
                    } else {
                        categories_total_charge[k] = 0
                    }
                }
            )
        })

        // Now, filter only categories that have some charge
        var categories = []
        for (var key in categories_total_charge) {
            if (categories_total_charge[key] > 0) {
                categories.push(key)
            }
        }

        // This forme list is the color_scale domain.
        // And that allows us to build the legend
        color_scale.domain(categories)
        addlegend(color_scale)

        // Build the stacked data for stacked bars
        var stack = d3.stack()
            .keys(categories)
        // .order(d3.stackOrderNone)
        // .offset(d3.stackOffsetNone);
        var stacked_data = stack(data)

        // Xaxis
        //
        // x scale from the periods list
        const x = d3.scaleBand()
            .domain(periods)
            .range([0, width])
            .padding(0.2);

        const xAxis = d3.axisBottom(x)

        // Yaxis
        //
        // Get the max y to plot
        y_max = d3.max(stacked_data[stacked_data.length - 1], d => d[1]);
        if (y_max == 0) {
            y_max = 100
        }
        // Enhance it by 10% to leave room on the top of the graph
        y_max = y_max * 1.1

        const y = d3.scaleLinear()
            .range([height, 0])
            .domain([0, y_max]);

        const yAxis = d3.axisLeft(y)

        // Draw Xaxis
        svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis)
            .selectAll("text")
            .style("text-anchor", "end")
            .attr("dx", "-.9em")
            .attr("dy", ".15em")
            .attr("transform", "rotate(-65)");

        // Draw Yaxis
        svg.append("g")
            .attr("class", "y axis")
            .call(yAxis)

        // Write Y axis title
        svg.append("text")
            .attr("text-anchor", "end")
            .attr("transform", "rotate(-90)")
            .attr("y", -margin.left + 40)
            .attr("x", -margin.top - 70)
            .text("Charge (% ETP)")

        // Write chart Title
        // part 1
        svg.append("text")
            .attr("x", (width / 2))
            .attr("y", 0 - (margin.top / 2) - 10)
            .attr("text-anchor", "middle")
            .style("font-size", "16px")
            .text(project_name);
        // part 2
        svg.append("text")
            .attr("x", (width / 2))
            .attr("y", 0 - (margin.top / 2) + 10)
            .attr("text-anchor", "middle")
            .style("font-size", "12px")
            .text(chart_title);

        // Draw the stacked bars
        //
        let groups = svg.selectAll("g.category")
            .data(stacked_data)
            .enter()
            .append("g")
            .style("fill", (d) => color_scale(d.key));

        let rect = groups.selectAll("rect")
            .data(d => d)
            .enter()
            .append("rect")
            .attr("class", "bar")
            .attr("x", d => x(d.data.period))
            .attr("width", x.bandwidth())
            // .attr("width", 5)
            .attr("y", d => y(d[1]))
            .attr("height", d => height - y(d[1] - d[0]))
            .on("mouseover", mouseover)
            .on("mousemove", mousemove)
            .on("mouseleave", mouseleave);

    });

}