Saturday, May 17, 2014

Linear gauge with gradient fill

In my searches for linear gauge control I didn't find any viable solution.
So I've created one based on D3 stacked bar graph.
See JSFiddle for implementation details

The final result will be something like this:


The Html will be pretty simple, only the container where we will put all the drawing

<div id="linearGaugeContainer"></div>

 I'm using SVG gradients definition in this sample

<!--define svg gradients to be used in java script. make sure the svg tag takes no room on your page=> width and height = 0px-->
<svg style="height: 0px; width: 0px;" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <lineargradient id="NORMAL_COLOR" spreadmethod="pad" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#C1DC99" stop-opacity="1">
            <stop offset="100%" stop-color="#669900" stop-opacity="1">
        </stop></stop></lineargradient>
        <lineargradient id="WARNING_COLOR" spreadmethod="pad" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#84C6F6" stop-opacity="1">
            <stop offset="100%" stop-color="#6FA7CF" stop-opacity="1">
        </stop></stop></lineargradient>
        <lineargradient id="MINOR_COLOR" spreadmethod="pad" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#FFE537" stop-opacity="1">
            <stop offset="100%" stop-color="#F9C328" stop-opacity="1">
        </stop></stop></lineargradient>
        <lineargradient id="MAJOR_COLOR" spreadmethod="pad" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#FF9900" stop-opacity="1">
            <stop offset="100%" stop-color="#FF6700" stop-opacity="1">
        </stop></stop></lineargradient>
        <lineargradient id="CRITICAL_COLOR" spreadmethod="pad" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#E70030" stop-opacity="1">
            <stop offset="100%" stop-color="#B30020" stop-opacity="1">
        </stop></stop></lineargradient>
    </defs>
</svg>

The javascript code is a bit long, just follow the comments within the code:

//Linear gauge
//The way to implement it would be: 
// - To take a stacked bar graph as a basis
// - Ignore all the graph related attributes 
//   (like: axis, ticks and so on)
// - Define a single set of data 
// - Swap the x and y axis values
var margins = {
    top: 12,
    left: 48,
    right: 24,
    bottom: 24
},
width = 700 - margins.left - margins.right
    height = 15,
    dataset = [{
        data: [{
            barId: '1', // we assign some random value, the most important to keep it same for all the entries 
            percent: 0.1, // percentage that we want a specific value to occupy as a part of the gauge: values betwee 0-1
            severity: 'NORMAL_COLOR'// color id that we'll used to reference gradient value
        }]
    }, {
        data: [{
            barId: '1',
            percent: 0.4,
            severity: 'WARNING_COLOR'
        }]
    }, {
        data: [{
            barId: '1',
            percent: 0.22,
            severity: 'MINOR_COLOR'
        }]
    }, {
        data: [{
            barId: '1',
            percent: 0.2,
            severity: 'MAJOR_COLOR'
        }]
    }, {
        data: [{
            barId: '1',
            percent: 0.08,
            severity: 'CRITICAL_COLOR'
        }]
    }],
    dataset = dataset.map(function (d) {
        return d.data.map(function (o, i) {
            // Structure it so that your numeric
            // axis (the stacked amount) is y
            return {
                y: o.percent,
                x: o.barId,
                severity:o.severity
            };
        });
    }),
    stack = d3.layout.stack();

stack(dataset);


var dataset = dataset.map(function (group) {
    return group.map(function (d) {
        // Invert the x and y values, and y0 becomes x0
        return {
            x: d.y,
            y: d.x,
            x0: d.y0,
            severity:d.severity
            
        };
    });
}),
    svg = d3.select('#linearGaugeContainer')
        .append('svg')
        .attr('width', width + margins.left + margins.right)
        .attr('height', height + margins.top + margins.bottom)
        .append('g')
        .attr('transform', 'translate(' + margins.left + ',' + margins.top + ')'),
    xMax = d3.max(dataset, function (group) {
        return d3.max(group, function (d) {
            return d.x + d.x0;
        });
    }),
    xScale = d3.scale.linear()
        .domain([0, xMax])
        .range([0, width]),
    yValue = dataset[0].map(function (d) {
        return d.y;
    }),
    yScale = d3.scale.ordinal()
        .domain(yValue)
        .rangeRoundBands([0, height], .1),
    // this color funciton referencing the linearGradient defind in the HTML part by id 
    color = function (index) {
        console.log(dataset[index][0])
        return "url(#" + dataset[index][0].severity + ")";
    },
    groups = svg.selectAll('g')
        .data(dataset)
        .enter()
        .append('g')
        .style('fill', function (d, i) {
        return color(i);
    });
    var rects = groups.selectAll('rect')
        .data(function (d) {
        return d;
    }).enter();
    rects.append('rect')
        .attr('x', function (d) {
        return xScale(d.x0);
    })
        .attr('y', function (d, i) {
        return yScale(d.y);
    })
        .attr('height', function (d) {
        return yScale.rangeBand();
    })
        .attr('width', function (d) {
        return xScale(d.x);
    });
// locate a label with percentage above each section of the gauge    
rects.append("text")
      .text(function(d,i) { return (d.x * 100) + '%' })
      .attr("x", function(d) { return xScale(d.x0); })
      .style("stroke", '#000000')

No comments:

Post a Comment