export default function() {

    var x = function(d) { return d.x; },
        y = function(d) { return d.y; },
        xScale = d3.scale.identity(),
        yScale = d3.scale.identity(),
        decorate = noop;

    var dataJoin = dataJoinUtil()
        .children(true)
        .selector('g.crosshair')
        .element('g')
        .attr('class', 'crosshair');

    var pointSeries = point()
        .xValue(x)
        .yValue(y);

    var horizontalLine = line()
        .value(y)
        .label(y);

    var verticalLine = line()
        .orient('vertical')
        .value(x)
        .label(x);

    // The line annotations and point series used to render the crosshair are positioned using
    // screen coordinates. This function constructs an identity scale for these components.
    var identityXScale = d3.scale.identity();
    var identityYScale = d3.scale.identity();

    var multi = multiSeries()
        .series([horizontalLine, verticalLine, pointSeries])
        .xScale(identityXScale)
        .yScale(identityYScale)
        .mapping(function() { return [this]; });

    var crosshair = function(selection) {

        selection.each(function(data, index) {

            var container = d3.select(this);

            var crosshairElement = dataJoin(container, data);

            crosshairElement.enter()
                .style('pointer-events', 'none');

            // Assign the identity scales an accurate range to allow the line annotations to cover
            // the full width/height of the chart.
            identityXScale.range(range(xScale));
            identityYScale.range(range(yScale));

            crosshairElement.call(multi);

            decorate(crosshairElement, data, index);
        });
    };

    // Don't use the xValue/yValue convention to indicate that these values are in screen
    // not domain co-ordinates and are therefore not scaled.
    crosshair.x = function(_x) {
        if (!arguments.length) {
            return x;
        }
        x = _x;
        return crosshair;
    };
    crosshair.y = function(_x) {
        if (!arguments.length) {
            return y;
        }
        y = _x;
        return crosshair;
    };
    crosshair.xScale = function(_x) {
        if (!arguments.length) {
            return xScale;
        }
        xScale = _x;
        return crosshair;
    };
    crosshair.yScale = function(_x) {
        if (!arguments.length) {
            return yScale;
        }
        yScale = _x;
        return crosshair;
    };
    crosshair.decorate = function(_x) {
        if (!arguments.length) {
            return decorate;
        }
        decorate = _x;
        return crosshair;
    };

    var lineIncludes = include('label');
    rebindAll(crosshair, horizontalLine, lineIncludes, prefix('y'));
    rebindAll(crosshair, verticalLine, lineIncludes, prefix('x'));

    return crosshair;
}
Example #2
0
export default function() {

    let x = (d) => d.x;
    let y = (d) => d.y;
    let xScale = scaleIdentity();
    let yScale = scaleIdentity();
    let decorate = () => {};

    const join = dataJoin('g', 'annotation-crosshair');

    const point = seriesSvgPoint();

    const horizontalLine = annotationLine();

    const verticalLine = annotationLine()
        .orient('vertical');

    // The line annotations and point series used to render the crosshair are positioned using
    // screen coordinates. This function constructs an identity scale for these components.
    const xIdentity = scaleIdentity();
    const yIdentity = scaleIdentity();

    const multi = seriesSvgMulti()
        .series([horizontalLine, verticalLine, point])
        .xScale(xIdentity)
        .yScale(yIdentity)
        .mapping((data) => [data]);

    const instance = (selection) => {

        selection.each((data, index, nodes) => {

            const g = join(select(nodes[index]), data);

            // Prevent the crosshair triggering pointer events on itself
            g.enter()
                .style('pointer-events', 'none');

            // Assign the identity scales an accurate range to allow the line annotations to cover
            // the full width/height of the chart.
            xIdentity.range(xScale.range());
            yIdentity.range(yScale.range());

            point.xValue(x)
                .yValue(y);

            horizontalLine.value(y);

            verticalLine.value(x);

            g.call(multi);

            decorate(g, data, index);
        });
    };

    // Don't use the xValue/yValue convention to indicate that these values are in screen
    // not domain co-ordinates and are therefore not scaled.
    instance.x = (...args) => {
        if (!args.length) {
            return x;
        }
        x = args[0];
        return instance;
    };
    instance.y = (...args) => {
        if (!args.length) {
            return y;
        }
        y = args[0];
        return instance;
    };
    instance.xScale = (...args) => {
        if (!args.length) {
            return xScale;
        }
        xScale = args[0];
        return instance;
    };
    instance.yScale = (...args) => {
        if (!args.length) {
            return yScale;
        }
        yScale = args[0];
        return instance;
    };
    instance.decorate = (...args) => {
        if (!args.length) {
            return decorate;
        }
        decorate = args[0];
        return instance;
    };

    const lineIncludes = include('label');
    rebindAll(instance, horizontalLine, lineIncludes, prefix('y'));
    rebindAll(instance, verticalLine, lineIncludes, prefix('x'));

    return instance;
}
Example #3
0
export default function() {

    // creates an array with four elements, representing the high, low, open and close
    // values of the given array
    function highLowOpenClose(data) {
        var xValueAccessor = sparkline.xValue(),
            yValueAccessor = sparkline.yValue();

        var high = d3.max(data, yValueAccessor);
        var low = d3.min(data, yValueAccessor);

        function elementWithYValue(value) {
            return data.filter(function(d) {
                return yValueAccessor(d) === value;
            })[0];
        }

        return [{
            x: xValueAccessor(data[0]),
            y: yValueAccessor(data[0])
        }, {
            x: xValueAccessor(elementWithYValue(high)),
            y: high
        }, {
            x: xValueAccessor(elementWithYValue(low)),
            y: low
        }, {
            x: xValueAccessor(data[data.length - 1]),
            y: yValueAccessor(data[data.length - 1])
        }];
    }

    var xScale = dateTime();
    var yScale = d3.scale.linear();
    var radius = 2;
    var line = _line();

    // configure the point series to render the data from the
    // highLowOpenClose function
    var point = _point()
        .xValue(function(d) { return d.x; })
        .yValue(function(d) { return d.y; })
        .decorate(function(sel) {
            sel.attr('class', function(d, i) {
                switch (i) {
                case 0: return 'open';
                case 1: return 'high';
                case 2: return 'low';
                case 3: return 'close';
                }
            });
        });

    var multi = _multi()
        .series([line, point])
        .mapping(function(series) {
            switch (series) {
            case point:
                return highLowOpenClose(this);
            default:
                return this;
            }
        });

    var sparkline = function(selection) {

        point.size(radius * radius * Math.PI);

        selection.each(function(data) {

            var container = d3.select(this);
            var dimensions = innerDimensions(this);
            var margin = radius;

            xScale.range([margin, dimensions.width - margin]);
            yScale.range([dimensions.height - margin, margin]);

            multi.xScale(xScale)
                .yScale(yScale);

            container.call(multi);

        });
    };

    rebindAll(sparkline, xScale, include('discontinuityProvider', 'domain'), prefix('x'));
    rebindAll(sparkline, yScale, include('domain'), prefix('y'));
    rebindAll(sparkline, line, include('xValue', 'yValue'));

    sparkline.xScale = function() { return xScale; };
    sparkline.yScale = function() { return yScale; };
    sparkline.radius = function(x) {
        if (!arguments.length) {
            return radius;
        }
        radius = x;
        return sparkline;
    };

    return sparkline;
}