Tags

, , , ,

While experimenting with D3.js, I came across a nice bar chart example. Not only did this example help me understand some aspects of D3, but also became the base for a column chart.

Below is the “object” version of the chart.

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
        <title>Bar Chart</title>
        <meta http-equiv="X-UA-Compatible" content="IE=9">
        <meta charset='utf-8'>
        <!--<script src="http://d3js.org/d3.v3.min.js"></script>-->
        <script src="js/d3.v3-3.3.6.js"></script>
</head>
<body>
 
<!--<div id="chart" style="width:1000px; height:400px;"></div>-->
<div id="chart" style="width:1000; height:600;"></div>
 
<script>
/* initial methods
function getElementWidth(elem)
{
        return parseInt(elem.style.width);
}
 
function getElementHeight(elem)
{
        return parseInt(elem.style.height);
}
*/

/* get style of element */
/* http://atomicrobotdesign.com/blog/javascript/get-the-style-property-of-an-element-using-javascript/ */
function getStyle(elem, prop)
{
  return window.getComputedStyle(elem, null).getPropertyValue(prop);
}
 
function getElementComputedStyleValue(elem, property, defaultValue)
{
        var retValue = defaultValue;
        var value = getStyle(elem, property);
         
        if ( value != null && value != "" && value == 0 ) {
                retValue = parseInt(value);
        }
         
        return retValue;
}
 
function BarChart(chartID)
{
        /* object variables */
        this.version = "1.0.0";
        this.id = chartID;
         
        /* object methods */
         
        this.draw = function(divID, dataFormat, dataSource, dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName)
        {
                var data = null;
                if ( dataFormat == "csv" ) {
                        if ( dataSource == "text" ) {
                                data = d3.csv.parse(dataSet);
                        } else if ( dataSource == "url" ) {
                                data = dataSet;
                        }
                } else if ( dataFormat == "tsv" ) {
                        data = dataSet;
                } else if ( dataFormat == "json" ) {
                        data = dataSet;
                }
                 
                if ( divID == null || divID == "") {
                        divID = "body";
                }
                 
                /* get the width and height of the element, to set height and width of chart accordingly */
                var selectedElement = document.getElementById(divID);
                chartConfig.width = getElementComputedStyleValue(selectedElement, "width", chartConfig.width);
                chartConfig.height = getElementComputedStyleValue(selectedElement, "height", chartConfig.height);
                 
                var chartWidth = chartConfig.width - (chartConfig.barLabelWidth + chartConfig.valueLabelWidth + chartConfig.paddingY);
                var chartHeight = chartConfig.height - chartConfig.gridLabelHeight - chartConfig.chartOffsetY;
                chartConfig.barHeight = chartHeight / data.length;
                 
                // accessor functions
                var barLabel = function(d) { return d[labelColumnName]; };
                var barValue = function(d) { return parseFloat(d[dataColumnName]); };
                 
                // scales
                var x = d3.scale.linear().domain([0, d3.max(data, barValue)]).range([0, chartWidth]);
                //var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, data.length * chartConfig.barHeight]);
                var yScale = d3.scale.ordinal().domain(d3.range(0, data.length)).rangeBands([0, chartHeight]);
                var y = function(d, i) { return yScale(i); };
                var yText = function(d, i) { return y(d, i) + yScale.rangeBand() / 2; };
                 
                // svg container element
                var chart = d3.select('#' + divID).append("svg")
                        .attr('width', chartConfig.width)
                        .attr('height', chartConfig.height);

                // Ruler text and grid lines
                if ( gridLineConfig.showLabels == true || gridLineConfig.showLines == true ) {
                        var gridContainer = chart.append('g')
                                .attr('transform', 'translate(' + chartConfig.barLabelWidth + ',' + chartConfig.gridLabelHeight + ')');
                                 
                        // grid line labels
                        if ( gridLabelConfig.showLabels == true ) {
                                gridContainer.selectAll("text")
                                        .data(x.ticks(gridLineConfig.numTicks)) // deliberately using gridLineConfig.numTicks
                                        .enter()
                                        .append("text")
                                        .attr("x", x)
                                        .attr("dy", gridLabelConfig.offsetY)
                                        .attr("text-anchor", gridLabelConfig.alignment)
                                        .attr("font-size", gridLabelConfig.fontSize)
                                        .style("font-family", gridLabelConfig.font)
                                        .style("fill", gridLabelConfig.fillColor)
                                        .style("stroke", gridLabelConfig.strokeColor)
                                        .text(String);
                        }
                         
                        // grid lines
                        if ( gridLineConfig.showLines == true ) {
                                gridContainer.selectAll("line")
                                        .data(x.ticks(gridLineConfig.numTicks))
                                        .enter()
                                        .append("line")
                                        .attr("x1", x)
                                        .attr("x2", x)
                                        .attr("y1", 0)
                                        .attr("y2", yScale.rangeExtent()[1] + chartConfig.chartOffsetY)
                                        .style("fill", gridLineConfig.fillColor)
                                        .style("stroke", gridLineConfig.strokeColor);
                        }
                }
                         
                // bar labels
                var labelsContainer = chart.append('g')
                        .attr('transform', 'translate(' + (chartConfig.barLabelWidth - chartConfig.barLabelPadding) + ',' + (chartConfig.gridLabelHeight + chartConfig.chartOffsetY) + ')');
                labelsContainer.selectAll('text')
                        .data(data)
                        .enter()
                        .append('text')
                        .attr('y', yText)
                        .attr('stroke', barLabelConfig.strokeColor)
                        .attr('fill', barLabelConfig.fillColor)
                        .attr("dy", barLabelConfig.yOffset)
                        .attr('text-anchor', barLabelConfig.alignment)
                        .attr("font-size", barLabelConfig.fontSize)
                        .style("font-family", barLabelConfig.font)
                        .style("fill", barLabelConfig.fillColor)
                        .style("stroke", barLabelConfig.strokeColor)
                        .text(barLabel);
                         
                // bars
                var barsContainer = chart.append('g')
                        .attr('transform', 'translate(' + chartConfig.barLabelWidth + ',' + (chartConfig.gridLabelHeight + chartConfig.chartOffsetY) + ')');
                barsContainer.selectAll("rect").data(data).enter().append("rect")
                        .attr('y', y)
                        .attr('height', yScale.rangeBand())
                        .attr('width', function(d) { return x(barValue(d)); })
                        .attr('stroke', barConfig.strokeColor)
                        .style("fill",
                                function(d, i) {
                                        if ( barConfig.fillColor == "single" ) {
                                                return barConfig.fillColorScale;
                                        } else if ( barConfig.fillColor == "range" ) {
                                                var idx = i % barConfig.fillColorScale.length;
                                                return barConfig.fillColorScale[idx];
                                        } else if ( barConfig.fillColor == "scale" ) {
                                                return barConfig.fillColorScale(i);
                                        }
                                }
                        );
                 
                // bar value labels
                if ( barValueLabelConfig.showLabels == true ) {
                        barsContainer.selectAll("text")
                                .data(data)
                                .enter()
                                .append("text")
                                .attr("x", function(d) { return x(barValue(d)); })
                                .attr("y", yText)
                                .attr("dx", barValueLabelConfig.paddingX)
                                .attr("dy", barValueLabelConfig.paddingY)
                                .attr("text-anchor", barValueLabelConfig.alignment)
                                .attr("font-size", barValueLabelConfig.fontSize)
                                .style("font-family", barValueLabelConfig.font)
                                .style("fill", barValueLabelConfig.fillColor)
                                .style("stroke", barValueLabelConfig.strokeColor);
                        if ( barValueLabelConfig.roundDecimalsPlaces != null || barValueLabelConfig.roundDecimalsPlaces > 0 ) {
                                barsContainer.selectAll("text")
                                        .data(data).text(function(d) { return d3.round(barValue(d), barValueLabelConfig.roundDecimalsPlaces); });
                        }
                }
                 
                // x-axis line
                if ( chartConfig.showXAxis == true ) {
                        barsContainer.append("line")
                                .attr("x1", 0)
                                .attr("x2", chartWidth)
                                .attr("y1", 0)
                                .attr("y2", 0)
                                .style("fill", chartConfig.xAxisFillColor)
                                .style("stroke", chartConfig.xAxisStrokeColor);
                }
                 
                // y-axis line
                if ( chartConfig.showYAxis == true ) {
                        barsContainer.append("line")
                                .attr("x1", 0)
                                .attr("x2", 0)
                                .attr("y1", 0)
                                .attr("y2", chartHeight)
                                .style("fill", chartConfig.yAxisFillColor)
                                .style("stroke", chartConfig.yAxisStrokeColor);
                }
        },
        this.draw_CSV_URL = function(chartObject, divID, dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName)
        {
                d3.csv(dataSet, function(data) {
                        chartObject.draw(divID, "csv", "url", data, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
                });
        },
        this.draw_TSV_URL = function(chartObject, divID, dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName)
        {
                d3.tsv(dataSet, function(data) {
                        chartObject.draw(divID, "tsv", "url", data, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
                });
        },
        this.draw_JSON_Variable = function(chartObject, divID, dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName)
        {
                barChart.draw(divID, "json", "variable", dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        },
        this.draw_JSON_URL = function(chartObject, divID, dataSet, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName)
        {
                d3.json(dataSet, function(data) {
                        chartObject.draw(divID, "json", "url", data.values, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
                });
        }
}
</script>
 
<script id="csv" type="text/csv">Name,Population (mill),Average Life Expectancy,Area (1000 sq mi),Continent
Canada,33.9,80.7,3854.085,America
US,308.3,78.2,3784.191,America
Germany,82.3,79.4,137.847,Europe
Russia,141.9,65.5,6601.668,Europe
Mexico,108.4,76.06,758.449,America
Brazil,193.3,71.99,3287.612,America
Spain,46.9,80.9,195.365,Europe
France,65.4,80.98,244.339,Europe
China,1339,73,3705.407,Asia
Australia,22.4,81.2,2969.907,Australia
UK,62,79.4,93.800,Europe
Italy,60.3,80.5,116.346,Europe
India,1184,64.7,1236.085,Asia
Japan,127.4,82.6,145.920,Asia
Iceland,0.3,81.8,40.000,Europe
Portugal,10.6,78.1,35.560,Europe
South Africa,50,49.3,471.445,Africa
Egypt,78.9,71.3,387.000,Africa
Sweden,9.3,80.9,170.410,Europe
</script>
<script id="tsv" type="text/tsv">Name Population (mill)         Average Life Expectancy         Area (1000 sq mi)        Continent
Canada 33.9    80.7    3854.085        America
US      308.3   78.2    3784.191        America
Germany       82.3    79.4    137.847 Europe
Russia   141.9   65.5    6601.668        Europe
Mexico 108.4   76.06   758.449 America
Brazil   193.3   71.99   3287.612        America
Spain   46.9    80.9    195.365 Europe
France  65.4    80.98   244.339 Europe
China   1339     73       3705.407        Asia
Australia        22.4    81.2    2969.907        Australia
UK      62       79.4    93.800   Europe
Italy     60.3    80.5    116.346 Europe
India    1184     64.7    1236.085        Asia
Japan  127.4   82.6    145.920 Asia
Iceland 0.3      81.8    40.000   Europe
Portugal         10.6    78.1    35.560   Europe
South Africa     50       49.3    471.445 Africa
Egypt   78.9    71.3    387.000 Africa
Sweden         9.3      80.9    170.410 Europe
</script>
 
<script>
var values2 = [
        { "Name": "Canada", "Population (mill)": 33.9, "Average Life Expectancy": 80.7, "Area (1000 sq mi)": 3854.085, "Continent": "America" },
        { "Name": "US", "Population (mill)": 308.3, "Average Life Expectancy": 78.2, "Area (1000 sq mi)": 3784.191, "Continent": "America" },
        { "Name": "Germany", "Population (mill)": 82.3, "Average Life Expectancy": 79.4, "Area (1000 sq mi)": 137.847, "Continent": "Europe" },
        { "Name": "Russia", "Population (mill)": 141.9, "Average Life Expectancy": 65.5, "Area (1000 sq mi)": 6601.668, "Continent": "Europe" },
        { "Name": "Mexico", "Population (mill)": 108.4, "Average Life Expectancy": 76.06, "Area (1000 sq mi)": 758.449, "Continent": "America" },
        { "Name": "Brazil", "Population (mill)": 193.3, "Average Life Expectancy": 71.99, "Area (1000 sq mi)": 3287.612, "Continent": "America" },
        { "Name": "Spain", "Population (mill)": 46.9, "Average Life Expectancy": 80.9, "Area (1000 sq mi)": 195.365, "Continent": "Europe" },
        { "Name": "France", "Population (mill)": 65.4, "Average Life Expectancy": 80.98, "Area (1000 sq mi)": 244.339, "Continent": "Europe" },
        { "Name": "China", "Population (mill)": 1339, "Average Life Expectancy": 73, "Area (1000 sq mi)": 3705.407, "Continent": "Asia" },
        { "Name": "Australia", "Population (mill)": 22.4, "Average Life Expectancy": 81.2, "Area (1000 sq mi)": 2969.907, "Continent": "Australia" },
        { "Name": "UK", "Population (mill)": 62, "Average Life Expectancy": 79.4, "Area (1000 sq mi)": 93.800, "Continent": "Europe" },
        { "Name": "Italy", "Population (mill)": 60.3, "Average Life Expectancy": 80.5, "Area (1000 sq mi)": 116.346, "Continent": "Europe" },
        { "Name": "India", "Population (mill)": 1184, "Average Life Expectancy": 64.7, "Area (1000 sq mi)": 1236.085, "Continent": "Asia" },
        { "Name": "Japan", "Population (mill)": 127.4, "Average Life Expectancy": 82.6, "Area (1000 sq mi)": 145.920, "Continent": "Asia" },
        { "Name": "Iceland", "Population (mill)": 0.3, "Average Life Expectancy": 81.8, "Area (1000 sq mi)": 40.000, "Continent": "Europe" },
        { "Name": "Portugal", "Population (mill)": 10.6, "Average Life Expectancy": 78.1, "Area (1000 sq mi)": 35.560, "Continent": "Europe" },
        { "Name": "South Africa", "Population (mill)": 50, "Average Life Expectancy": 49.3, "Area (1000 sq mi)": 471.445, "Continent": "Africa" },
        { "Name": "Egypt", "Population (mill)": 78.9, "Average Life Expectancy": 71.3, "Area (1000 sq mi)": 387.000, "Continent": "Africa" },
        { "Name": "Sweden", "Population (mill)": 9.3, "Average Life Expectancy": 80.9, "Area (1000 sq mi)": 170.410, "Continent": "Europe" }
];
 
         
var chartConfig = {
        width: 1200,
        height: 400,
        valueLabelWidth: 40, // space reserved for value labels (right)
        barHeight: 20, // height of one bar
        barLabelWidth: 100, // space reserved for bar labels
        barLabelPadding: 5, // padding between bar and bar labels (left)
        gridLabelHeight: 18, // space reserved for gridline labels
        chartOffsetY: 0, // space between start of grid and first bar
        //maxBarWidth: 500, // width of the bar with the max value
        paddingY: 200,
        showXAxis: true,
        xAxisFillColor: "none",
        xAxisStrokeColor: "#000000",
        showYAxis: true,
        yAxisFillColor: "none",
        yAxisStrokeColor: "#000000"
};
 
var gridLineConfig = {
        showLines: true,
        numTicks: 10,
        fillColor: 'none',
        strokeColor: '#ccc'
};
 
var gridLabelConfig = {
        showLabels: true,
        offsetY: -3,
        fontSize: 12,
        font: "sans-serif",
        alignment: 'middle',
        fillColor: 'black',
        strokeColor: 'none'
};
 
var barLabelConfig = {
        fontSize: 12,
        font: "sans-serif",
        fillColor: "#000000",
        strokeColor: "none",
        fillColor: 'black',
        strokeColor: 'none',
        yOffset: ".35em",
        alignment: 'end'
};
 
var colorScale = d3.scale.category20();
 
var barConfig = {
        strokeColor: 'white',
        fillColor: "scale", // "single", "range", "scale"
        //fillColorScale: "steelblue"
        //fillColorScale: ["steelblue", "red"]
        fillColorScale: colorScale
};

var barValueLabelConfig = {
        showLabels: true,
        fontSize: 12,
        font: "sans-serif",
        fillColor: 'black',
        strokeColor: 'none',
        alignment: 'start',
        roundDecimalsPlaces: 2,
        paddingX: 3,
        paddingY: ".35em"
};
 
var labelColumnName = 'Name';
var dataColumnName = 'Population (mill)';
var divID = "chart";
 
var barChart = new BarChart("b1");

var dataFormat = "csv"; // "tsv" / "json" / "csv"
var dataSource = "text"; // "url" / "text" / "variable" (only for json)
if ( dataFormat == "csv" ) {
        if ( dataSource == "text" ) {
                barChart.draw(divID, "csv", "text", d3.select('#csv').text(), chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        } else if ( dataSource == "url" ) {
                barChart.draw_CSV_URL(barChart, divID, "data/bar-column-chart3.csv", chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        }
} else if ( dataFormat == "tsv" ) {
        if ( dataSource == "text" ) {
                // not working as expected
                //barChart.draw(divID, "tsv", "text", d3.select('#tsv').text(), chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
                alert("'tsv - text' functionality not implemented");
        } else if ( dataSource == "url" ) {
                barChart.draw_TSV_URL(barChart, divID, "data/bar-column-chart3.tsv", chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        }
} else if ( dataFormat == "json" ) {
        if ( dataSource == "variable" ) {
                barChart.draw_JSON_Variable(barChart, divID, values2, chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        } else if ( dataSource == "url" ) {
                barChart.draw_JSON_URL(barChart, divID, "data/bar-column-chart3.json", chartConfig, gridLineConfig, gridLabelConfig, barLabelConfig, barConfig, labelColumnName, dataColumnName);
        }
}
</script>
</body>
</html>

Given thatD3.js is BSD licensed, I am making this chart follow the same license – BSD.

Advertisements