Example #1
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 #2
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;
}
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 #4
0
export default function(xScale, yScale) {

    xScale = xScale || d3.scale.linear();
    yScale = yScale || d3.scale.linear();

    var margin = {
            bottom: 30,
            right: 30
        },
        yLabel = '',
        xLabel = '',
        xBaseline = null,
        yBaseline = null,
        chartLabel = '',
        plotArea = line(),
        decorate = noop;

    // Each axis-series has a cross-scale which is defined as an identity
    // scale. If no baseline function is supplied, the axis is positioned
    // using the cross-scale range extents. If a baseline function is supplied
    // it is transformed via the respective scale.
    var xAxis = axis()
        .orient('bottom')
        .baseline(function() {
            if (xBaseline !== null) {
                return yScale(xBaseline.apply(this, arguments));
            } else {
                var r = range(yScale);
                return xAxis.orient() === 'bottom' ? r[0] : r[1];
            }
        });

    var yAxis = axis()
        .orient('right')
        .baseline(function() {
            if (yBaseline !== null) {
                return xScale(yBaseline.apply(this, arguments));
            } else {
                var r = range(xScale);
                return yAxis.orient() === 'left' ? r[0] : r[1];
            }
        });

    var containerDataJoin = dataJoin()
        .selector('svg.cartesian-chart')
        .element('svg')
        .attr({'class': 'cartesian-chart', 'layout-style': 'flex: 1'});


    var cartesian = function(selection) {

        selection.each(function(data, index) {

            var container = d3.select(this);

            var svg = containerDataJoin(container, [data]);
            svg.enter().html(
                '<g class="plot-area-container"> \
                    <rect class="background" \
                        layout-style="position: absolute; top: 0; bottom: 0; left: 0; right: 0"/> \
                    <g class="axes-container" \
                        layout-style="position: absolute; top: 0; bottom: 0; left: 0; right: 0"> \
                        <g class="x-axis" layout-style="height: 0; width: 0"/> \
                        <g class="y-axis" layout-style="height: 0; width: 0"/> \
                    </g> \
                    <svg class="plot-area" \
                        layout-style="position: absolute; top: 0; bottom: 0; left: 0; right: 0"/> \
                </g> \
                <g class="x-axis label-container"> \
                    <g layout-style="height: 0; width: 0"> \
                        <text class="label" dy="-0.5em"/> \
                    </g> \
                </g> \
                <g class="y-axis label-container"> \
                    <g layout-style="height: 0; width: 0"> \
                        <text class="label"/> \
                    </g> \
                </g> \
                <g class="title label-container"> \
                    <g layout-style="height: 0; width: 0"> \
                        <text class="label"/> \
                    </g> \
                </g>');

            var expandedMargin = expandRect(margin);

            svg.select('.plot-area-container')
                .layout({
                    position: 'absolute',
                    top: expandedMargin.top,
                    left: expandedMargin.left,
                    bottom: expandedMargin.bottom,
                    right: expandedMargin.right
                });

            svg.select('.title')
                .layout({
                    position: 'absolute',
                    top: 0,
                    alignItems: 'center',
                    left: expandedMargin.left,
                    right: expandedMargin.right
                });

            var yAxisLayout = {
                position: 'absolute',
                top: expandedMargin.top,
                bottom: expandedMargin.bottom,
                alignItems: 'center',
                flexDirection: 'row'
            };
            yAxisLayout[yAxis.orient()] = 0;
            svg.select('.y-axis.label-container')
                .attr('class', 'y-axis label-container ' + yAxis.orient())
                .layout(yAxisLayout);

            var xAxisLayout = {
                position: 'absolute',
                left: expandedMargin.left,
                right: expandedMargin.right,
                alignItems: 'center'
            };
            xAxisLayout[xAxis.orient()] = 0;
            svg.select('.x-axis.label-container')
                .attr('class', 'x-axis label-container ' + xAxis.orient())
                .layout(xAxisLayout);

            // perform the flexbox / css layout
            container.layout();

            // update the label text
            svg.select('.title .label')
                .text(chartLabel);

            svg.select('.y-axis.label-container .label')
                .text(yLabel)
                .attr('transform', yAxis.orient() === 'right' ? 'rotate(90)' : 'rotate(-90)');

            svg.select('.x-axis.label-container .label')
                .text(xLabel);

            // set the axis ranges
            var plotAreaContainer = svg.select('.plot-area');
            setRange(xScale, [0, plotAreaContainer.layout('width')]);
            setRange(yScale, [plotAreaContainer.layout('height'), 0]);

            // render the axes
            xAxis.xScale(xScale)
                .yScale(d3.scale.identity());

            yAxis.yScale(yScale)
                .xScale(d3.scale.identity());

            svg.select('.axes-container .x-axis')
                .call(xAxis);

            svg.select('.axes-container .y-axis')
                .call(yAxis);

            // render the plot area
            plotArea.xScale(xScale)
                .yScale(yScale);
            plotAreaContainer.call(plotArea);

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

    var scaleExclusions = exclude(
        /range\w*/,   // the scale range is set via the component layout
        /tickFormat/  // use axis.tickFormat instead (only present on linear scales)
    );
    rebindAll(cartesian, xScale, scaleExclusions, exclude('ticks'), prefix('x'));
    rebindAll(cartesian, yScale, scaleExclusions, exclude('ticks'), prefix('y'));

    // The scale ticks method is a stateless method that returns (roughly) the number of ticks
    // requested. This is subtley different from the axis ticks methods that simply stores the given arguments
    // for invocation of the scale method at some point in the future.
    // Here we expose the underling scale ticks method in case the user want to generate their own ticks.
    if (!isOrdinal(xScale)) {
        rebindAll(cartesian, xScale, includeMap({'ticks': 'xScaleTicks'}));
    }
    if (!isOrdinal(yScale)) {
        rebindAll(cartesian, yScale, includeMap({'ticks': 'yScaleTicks'}));
    }

    var axisExclusions = exclude(
        'baseline',         // the axis baseline is adapted so is not exposed directly
        'xScale', 'yScale'  // these are set by this components
    );
    rebindAll(cartesian, xAxis, axisExclusions, prefix('x'));
    rebindAll(cartesian, yAxis, axisExclusions, prefix('y'));

    cartesian.xBaseline = function(x) {
        if (!arguments.length) {
            return xBaseline;
        }
        xBaseline = d3.functor(x);
        return cartesian;
    };
    cartesian.yBaseline = function(x) {
        if (!arguments.length) {
            return yBaseline;
        }
        yBaseline = d3.functor(x);
        return cartesian;
    };
    cartesian.chartLabel = function(x) {
        if (!arguments.length) {
            return chartLabel;
        }
        chartLabel = x;
        return cartesian;
    };
    cartesian.plotArea = function(x) {
        if (!arguments.length) {
            return plotArea;
        }
        plotArea = x;
        return cartesian;
    };
    cartesian.xLabel = function(x) {
        if (!arguments.length) {
            return xLabel;
        }
        xLabel = x;
        return cartesian;
    };
    cartesian.margin = function(x) {
        if (!arguments.length) {
            return margin;
        }
        margin = x;
        return cartesian;
    };
    cartesian.yLabel = function(x) {
        if (!arguments.length) {
            return yLabel;
        }
        yLabel = x;
        return cartesian;
    };
    cartesian.decorate = function(x) {
        if (!arguments.length) {
            return decorate;
        }
        decorate = x;
        return cartesian;
    };

    return cartesian;
}