Selecting a range with brushing

In this section, we will create an area chart to display stock prices and use brushing to allow the user to select an interval and get additional information about that time interval.

Selecting a range with brushing

Selecting a time interval with brushing

We will use the time series of the prices of the AAPL stock, available as a TSV file in the D3 examples gallery. The file contains the date and closing price for the date, covering almost 5 years of activity, as shown in the following code:

date        close
1-May-12    582.13
30-Apr-12   583.98
27-Apr-12   603.00
26-Apr-12   607.70
...

Creating the area chart

We begin by creating the structure of a reusable chart; we will add the width, height, and margin as the chart attributes, and add their corresponding accessors. The complete code is available in the chapter05/02-brushing.html file. In the charting function, we initialize and set the size of the svg element as follows:

// Chart Creation
function chart(selection) {
    selection.each(function(data) {

        // Select the container element and create the svg selection
        var div = d3.select(this),
            svg = div.selectAll('svg').data([data]);

        // Initialize the svg element
        svg.enter().append('svg')
            .call(svgInit);

        // Initialize the svg element
        svg.attr('width', width).attr('height', height);

        // Creation of the inner elements...
    });
}

The svgInit function will be called only on enter, and it will create groups for the axis and the chart content. We will parse the input data, so we don't have to transform each item later. To parse the date, we will use the d3.time.format D3 method:

// Configure the time parser
var parseDate = d3.time.format(timeFormat).parse;

// Parse the data
data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
});

The timeFormat variable is defined as a chart attribute; we also added an accessor function so that the user can use the chart for other datasets. In this case, the input date format is %d-%b-%y, that is, the day, abbreviated month name, and year. We can now create the x and y axis:

// Create the scales and axis
var xScale = d3.time.scale()
    .domain(d3.extent(data, function(d) { return d.date; }))
    .range([0, width - margin.left - margin.right]);

// Create the x axis
var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient('bottom'),

// Invoke the xAxis function on the corresponding group
svg.select('g.xaxis').call(xAxis);

We do the same with the y axis, but we will use a linear scale instead of the time scale and orient the axis to the left side. We can now create the chart content; we create and configure an area generator that will compute the path and then append the path to the chart group, as follows:

// Create and configure the area generator
var area = d3.svg.area()
    .x(function(d) { return xScale(d.date); })
    .y0(height - margin.top - margin.bottom)
    .y1(function(d) { return yScale(d.close); });

// Create the area path
svg.select('g.chart').append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

We will modify the styles for the classes of the axis groups and the area to have a better-looking chart, as follows:

<style>
.axis path, line{
    fill: none;
    stroke: #222;
    shape-rendering: crispEdges;
}
.axis text {
    font-size: 11px;
}

.area {
    fill: #ddd;
}
</style>

We load the dataset using the d3.tsv function, which retrieves and parses tabular delimited data. Next, we will configure the chart, select the container element, and bind the dataset to the selection as follows:

// Load the TSV Stock Data
d3.tsv('/chapter05/aapl.tsv', function(error, data) {

    // Handle errors getting or parsing the data
    if (error) { 
       console.error(error); 
       throw error;
    }

    // Create and configure the area chart
    var chart = areaChart();

    // Bind the chart to the container div
    d3.select('div#chart')
        .datum(data)
        .call(chart);
});
Creating the area chart

The first version of the area chart

Adding brushing

A brush is a control that allows you to select a range in a chart. D3 provides built-in support for brushing. We will use brushing to select time intervals in our area chart, and use it to show the price and date of the edges of the selected interval. We will also add a label that shows the relative price variation in the interval. We will create an SVG group to contain the brush elements. We will add this group in the svgInit method, as follows:

// Create and translate the brush container group
svg.append('g')
    .attr('class', 'brush')
    .attr('transform', function() {
        var dx = margin.left, dy = margin.top;
        return 'translate(' + [dx, dy] + ')';
    });

The group should be added at the end of the svg element to avoid getting it hidden by the other elements. With the group created, we can add the brush control in the charting function as follows:

function chart(selection) {
    selection.each(function(data) {
        // Charting function contents...

        // Create and  configure the brush
        var brush = d3.svg.brush()
            .x(xScale)
            .on('brush', brushListener);
    });
}

We set the scale of the brush in the horizontal axis, and add a listener for the brush event. The brush can be configured to select a vertical interval by setting the y attribute with an appropriate scale and even be used to select areas by setting both the x and y attributes.

The brushListener function will be invoked if the brush extent changes. The brushstart and brushend events are also available, but we don't need to use them at the moment. In the following code, we apply the brush function to the brush group using the call method of the selection:

var gBrush = svg.select('g.brush').call(brush);

When we invoke the brush function in a group, a series of elements are created. A background rectangle will capture the brush events. There will also be a rectangle of the extent class, which will resize as the user changes the brush area. Also, there are two invisible vertical rectangles at the brush edges; so, it's easier for the user to select the brush boundary. The rectangles will initially have zero height; we will set the height to cover the chart area:

// Change the height of the brushing rectangle
gBrush.selectAll('rect')
    .attr('height', height - margin.top - margin.bottom);

We will modify the extent class, so the selected region is visible. We will set its color to gray and set the fill opacity to 0.05.

Adding brushing

Adding brushing to the chart

The brush listener

We will add lines to mark the prices at the beginning and end of the selected period, and add a label to display the price variation in the interval. We begin by adding the elements in the chart.svgInit function and set some of its attributes. We will create groups for the line markers and for the text elements that will display the price and date. We also add a text element for the price variation. We create the brushListener function in the charting function scope, as shown in the following code:

// Brush Listener function
function brushListener() {
    var s = d3.event.target.extent();

    // Filter the items within the brush extent
    var items = data.filter(function(d) {
        return (s[0] <= d.date) && (d.date<= s[1]);
    });
}

When the brush event is triggered, the brush listener will have access to the event attributes through the d3.event object. Here, we get the brush extent and use it to filter the dates that lie within the selected interval. Note that the selection is an approximation, because there are a limited number of pixels in the screen. At the beginning of the brush event, the time interval might be too small to contain data items. We will compute the prices only when at least two items have been selected. We then select the first and last elements of the item array, as follows:

// Compute the percentual variation of the period
if (items.length > 2) {
    // Get the prices in the period
    priceB = items[0].close;
    priceA = Math.max(items[n - 1].close, 1e-8);

    // Set the lines and text position...
}

Having the first and last elements of the selected period, we can compute the relative price variation and set the position of the marker lines and labels. We will also set the color of the variation label to blue if the variation is positive and to red if it's negative. As the configuration of the positions and labels is rather large, we won't include the code here. However, the code is available in the chapter05/02-brushing.html file for reference.

The brush listener

The area chart with brushing and annotations

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset