コード例 #1
8
ファイル: events.js プロジェクト: ebemunk/blog
export const byDate = events.reduce((acc, event) => {
  const skey = dateScale(getTime(event.startdate))
  const ekey = dateScale(getTime(event.enddate))
  range(skey, ekey).map(key => acc[key].push(event))
  return acc
}, range(0, 101).map(() => []))
コード例 #2
0
ファイル: kde-test.js プロジェクト: glimpseio/vega
tape('kde generates samples', function(t) {
  var kde = stats.randomKDE(d3.range(0, 1000)
    .map(gaussian.sample));
  check(t, 0, 1, samples(kde, 1000));

  kde = stats.randomKDE(d3.range(0, 1000)
    .map(function() { return 5 * gaussian.sample(); }));
  check(t, 0, 5, samples(kde, 1000));

  t.end();
});
コード例 #3
0
function generateData() {
  const stack = d3Stack()
    .keys(range(NUMBER_OF_LAYERS))
    .offset(stackOffsetWiggle);
  const transposed = transpose(
    range(NUMBER_OF_LAYERS).map(() => bumps(SAMPLES_PER_LAYER, BUMPS_PER_LAYER))
  );
  return stack(transposed).map(series =>
    series.map((row, x) => ({x, y0: row[0], y: row[1]}))
  );
}
コード例 #4
0
ファイル: kde-test.js プロジェクト: glimpseio/vega
tape('kde approximates the cdf', function(t) {
  var data = d3.range(0, 1000).map(gaussian.sample),
      kde = stats.randomKDE(data),
      domain = d3.range(-5, 5.1, 0.5),
      error = domain.map(function(x) {
        return Math.abs(kde.cdf(x) - gaussian.cdf(x));
      });

  t.ok((d3.sum(error) / domain.length) < 0.01);
  t.end();
});
コード例 #5
0
 const sevenDaysDataMixedMinimum = (type = 'cbg') => sinon.stub().returns(
   _.map(range(0, (devices.dexcom.cgmInDay / 4) * extentSize), () => ({
     id: chance.hash({ length: 6 }),
     deviceId: devices.dexcom.id,
     msPer24: chance.integer({ min: 0, max: 864e5 }),
     type,
     value: chance.pickone([lowestBg, 525]),
   })).concat(_.map(range(0, (devices.libre.cgmInDay / 4) * extentSize), () => ({
     id: chance.hash({ length: 6 }),
     deviceId: devices.libre.id,
     msPer24: chance.integer({ min: 0, max: 864e5 }),
     type,
     value: chance.pickone([lowestBg, 525]),
   })))
 );
コード例 #6
0
// Inspired by Bostock's version of Lee Byron’s test data generator.
function bumps(samplesPerLayer, bumpsPerLayer) {
  const dataOutline = new Array(samplesPerLayer).fill(0);
  return range(bumpsPerLayer).reduce(
    res => bump(res, samplesPerLayer),
    dataOutline
  );
}
コード例 #7
0
ファイル: populate-scene.js プロジェクト: jimkang/linkfinds
  function populateScene(opts) {
    var sceneSize;
    var occupiedSpots;

    if (opts) {
      sceneSize = opts.sceneSize;
      occupiedSpots = opts.occupiedSpots;
    }

    var sceneMap = range(sceneSize[0]).map(makeColumn);
    markSpots(sceneMap, occupiedSpots, 'x');
    const sceneType = sceneTable.roll();
    // console.log(sceneType);
    populatorForScene[sceneType]({
      probable: probable,
      sceneMap: sceneMap
    });

    // addFlames

    return sceneMap;

    function makeColumn() {
      return range(sceneSize[1]).map(() => '.');
    }
  }
コード例 #8
0
    grouped.offsetScaleForDatum = (data, d, i) => {
        const width = bandwidth(d, i);
        const offset = alignOffset(align, width);

        const halfWidth = width / 2;
        return offsetScale
          .domain(range(0, data.length))
          .range([-halfWidth + offset, halfWidth + offset]);
    };
コード例 #9
0
 const sevenDaysDataMmol = (device = devices.dexcom, type = 'cbg') => sinon.stub().returns(
   _.map(range(0, device.cgmInDay * extentSize), () => ({
     id: chance.hash({ length: 6 }),
     deviceId: device.id,
     msPer24: chance.integer({ min: 0, max: 864e5 }),
     type,
     value: chance.pickone([lowestBgMmol, 28.4]),
   }))
 );
コード例 #10
0
ファイル: mapillary.js プロジェクト: Stalfur/iD
// partition viewport into `psize` x `psize` regions
function partitionViewport(psize, projection) {
    var dimensions = projection.clipExtent()[1];
    psize = psize || 16;
    var cols = d3_range(0, dimensions[0], psize);
    var rows = d3_range(0, dimensions[1], psize);
    var partitions = [];

    rows.forEach(function(y) {
        cols.forEach(function(x) {
            var min = [x, y + psize];
            var max = [x + psize, y];
            partitions.push(
                geoExtent(projection.invert(min), projection.invert(max)));
        });
    });

    return partitions;
}
コード例 #11
0
ファイル: find-test.js プロジェクト: GerHobbelt/d3-quadtree
tape("quadtree.find(x, y) returns the closest point to the given [x, y]", function(test) {
  var dx = 17,
      dy = 17,
      q = d3_quadtree.quadtree();
  d3_array.range(dx * dy).forEach(function(i) { q.add([i % dx, i / dx | 0]); });
  test.deepEqual(q.find( 0.1,  0.1), [ 0,  0]);
  test.deepEqual(q.find( 7.1,  7.1), [ 7,  7]);
  test.deepEqual(q.find( 0.1, 15.9), [ 0, 16]);
  test.deepEqual(q.find(15.9, 15.9), [16, 16]);
  test.end();
});
コード例 #12
0
const generateData = () => {
    const dataCount = document.getElementById('label-count').value;
    data = range(0, document.getElementById('label-count').value)
        .map((_, i) => {
            return {
                x: Math.random() * width,
                y: Math.random() * height,
                data: 'node-' + i
            };
        });
};
コード例 #13
0
test('axis-utils #generateFit', t => {
  t.deepEqual(generateFit({x: 0, y: 0}, {x: 1, y: 1}), {left: 0, offset: 0, right: 1, slope: 1});
  t.deepEqual(generateFit({x: 0, y: 0}, {x: 0, y: 1}), {left: 0, offset: 0, right: 1, slope: 0});
  t.deepEqual(generateFit({x: 0, y: 0}, {x: 0, y: -1}), {left: 0, offset: 0, right: -1, slope: 0});
  const result = generateFit({x: 175, y: 125}, {x: 17.33044811707665, y: 179.23546738969475});
  const numberOfTicks = 5;
  const left = result.left;
  const right = result.right;
  const pointSlope = (right - left) / numberOfTicks;
  const lengthOfGeneratedPoints = range(left, right + pointSlope, pointSlope).length;
  t.equal(lengthOfGeneratedPoints, 7, 'should be 7, incorrect length of generated points');
  t.end();
});
コード例 #14
0
ファイル: band.js プロジェクト: jheer/d3-scale
 function rescale() {
   var n = domain().length,
       reverse = range[1] < range[0],
       start = range[reverse - 0],
       stop = range[1 - reverse];
   step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2);
   if (round) step = Math.floor(step);
   start += (stop - start - step * (n - paddingInner)) * align;
   bandwidth = step * (1 - paddingInner);
   if (round) start = Math.round(start), bandwidth = Math.round(bandwidth);
   var values = sequence(n).map(function(i) { return start + step * i; });
   return ordinalRange(reverse ? values.reverse() : values);
 }
コード例 #15
0
    function calculateAreaOfPoints(data) {

        var xyData = data.map((point) => [x(point), y(point)]);

        var pointAreas = range(1, xyData.length - 1).map((i) => {
            var lastPoint = xyData[i - 1];
            var thisPoint = xyData[i];
            var nextPoint = xyData[i + 1];

            return 0.5 * Math.abs((lastPoint[0] - nextPoint[0]) * (thisPoint[1] - lastPoint[1]) -
                (lastPoint[0] - thisPoint[0]) * (nextPoint[1] - lastPoint[1]));
        });

        return pointAreas;
    }
コード例 #16
0
 generator(type, absolute, yformat, axis, scale, height, tickCount, min, max) {
     let axisGenerator;
     if (type === "linear" || type === "power") {
         if (tickCount > 0) {
             const stepSize = (max - min) / (tickCount - 1);
             axisGenerator = axis(scale)
                 .tickValues(range(min, max + max / 10000, stepSize))
                 .tickFormat(d => {
                     if (absolute) {
                         return yformat(Math.abs(d));
                     }
                     return yformat(d);
                 })
                 .tickSizeOuter(0);
         } else {
             if (height <= 200) {
                 axisGenerator = axis(scale)
                     .ticks(4)
                     .tickFormat(d => {
                         if (absolute) {
                             return yformat(Math.abs(d));
                         }
                         return yformat(d);
                     })
                     .tickSizeOuter(0);
             } else {
                 axisGenerator = axis(scale)
                     .tickFormat(d => {
                         if (absolute) {
                             return yformat(Math.abs(d));
                         }
                         return yformat(d);
                     })
                     .tickSizeOuter(0);
             }
         }
     } else if (type === "log") {
         if (min === 0) {
             throw Error("In a log scale, minimum value can't be 0");
         }
         axisGenerator = axis(scale)
             .ticks(10, ".2s")
             .tickSizeOuter(0);
     }
     return axisGenerator;
 }
コード例 #17
0
ファイル: axis-utils.js プロジェクト: j143/react-vis
export function generatePoints({axisStart, axisEnd, numberOfTicks, axisDomain}) {
  const {left, right, slope, offset} = generateFit(axisStart, axisEnd);
  // construct a linear band of points, then map them
  const pointSlope = (right - left) / (numberOfTicks);
  const axisScale = scaleLinear().domain([left, right]).range(axisDomain);

  const slopeVertical = axisStart.x === axisEnd.x;
  return {
    slope: slopeVertical ? Infinity : slope,
    points: range(left, right + pointSlope, pointSlope)
      .map(val => {
        if (slopeVertical) {
          return {y: val, x: slope * val + offset, text: axisScale(val)};
        }
        return {x: val, y: slope * val + offset, text: axisScale(val)};
      })
  };
}
コード例 #18
0
      // Applies to ordinal scales (invert not supported in d3)
      function invertOrdinal(val, cmpFunc) {
        cmpFunc = cmpFunc || function (a, b) {
            return (a >= b);
          };

        const scDomain = this.domain();
        let scRange = this.range();

        if (scRange.length === 2 && scDomain.length !== 2) {
          // Special case, interpolate range vals
          scRange = d3Range(scRange[0], scRange[1], (scRange[1] - scRange[0]) / scDomain.length);
        }

        const bias = scRange[0];
        for (let i = 0, len = scRange.length; i < len; i++) {
          if (cmpFunc(scRange[i] + bias, val)) {
            return scDomain[Math.round(i * scDomain.length / scRange.length)];
          }
        }

        return this.domain()[this.domain().length-1];
      }
コード例 #19
0
ファイル: lines.js プロジェクト: Stalfur/iD
        [covered, uncovered].forEach(function(selection) {
            var range = (selection === covered ? d3_range(-10,0) : d3_range(0,11));
            var layergroup = selection
                .selectAll('g.layergroup')
                .data(range);

            layergroup = layergroup.enter()
                .append('g')
                .attr('class', function(d) { return 'layergroup layer' + String(d); })
                .merge(layergroup);

            layergroup
                .selectAll('g.linegroup')
                .data(['shadow', 'casing', 'stroke', 'shadow-highlighted', 'casing-highlighted', 'stroke-highlighted'])
                .enter()
                .append('g')
                .attr('class', function(d) { return 'linegroup line-' + d; });

            layergroup.selectAll('g.line-shadow')
                .call(drawLineGroup, 'shadow', false);
            layergroup.selectAll('g.line-casing')
                .call(drawLineGroup, 'casing', false);
            layergroup.selectAll('g.line-stroke')
                .call(drawLineGroup, 'stroke', false);

            layergroup.selectAll('g.line-shadow-highlighted')
                .call(drawLineGroup, 'shadow', true);
            layergroup.selectAll('g.line-casing-highlighted')
                .call(drawLineGroup, 'casing', true);
            layergroup.selectAll('g.line-stroke-highlighted')
                .call(drawLineGroup, 'stroke', true);


            var onewaygroup = layergroup
                .selectAll('g.onewaygroup')
                .data(['oneway']);

            onewaygroup = onewaygroup.enter()
                .append('g')
                .attr('class', 'onewaygroup')
                .merge(onewaygroup);

            var oneways = onewaygroup
                .selectAll('path')
                .filter(filter)
                .data(
                    function data() { return onewaydata[this.parentNode.__data__] || []; },
                    function key(d) { return [d.id, d.index]; }
                );

            oneways.exit()
                .remove();

            oneways = oneways.enter()
                .append('path')
                .attr('class', 'oneway')
                .attr('marker-mid', 'url(#oneway-marker)')
                .merge(oneways)
                .attr('d', function(d) { return d.d; });

            if (detected.ie) {
                oneways.each(function() { this.parentNode.insertBefore(this, this); });
            }
        });
コード例 #20
0
ファイル: Scatter.js プロジェクト: GordonSmith/Visualization
import { randomNormal as d3RandomNormal } from "d3-random";
import { range as d3Range } from "d3-array";
import { Scatter } from "@hpcc-js/chart";

const randomX = d3RandomNormal(200, 80);
const randomY = d3RandomNormal(200, 80);
const points = d3Range(200).map(function () { return [randomX(), randomY()]; });

new Scatter()
    .target("target")
    .columns(["X", "Y"])
    .data(points)
    .xAxisType("linear")
    .yAxisType("linear")
    .render()
    ;
コード例 #21
0
    return function module() {

        let margin = {
                top: 70,
                right: 30,
                bottom: 60,
                left: 70
            },
            width = 960,
            height = 500,

            xScale, xAxis, xMonthAxis,
            yScale, yAxis,

            aspectRatio = null,

            monthAxisPadding = 30,
            verticalTicks = 5,
            yTickTextYOffset = -8,
            yTickTextXOffset = -20,
            tickPadding = 5,

            colorSchema = colorHelper.colorSchemas.britechartsColorSchema,

            areaOpacity = 0.64,
            categoryColorMap,
            order,

            forceAxisSettings = null,
            forcedXTicks = null,
            forcedXFormat = null,
            locale,

            baseLine,

            layers,
            layersInitial,
            area,

            // Area Animation
            maxAreaNumber = 8,
            areaAnimationDelayStep = 20,
            areaAnimationDelays = d3Array.range(areaAnimationDelayStep, maxAreaNumber* areaAnimationDelayStep, areaAnimationDelayStep),

            overlay,

            verticalMarkerContainer,
            verticalMarker,
            epsilon,

            dataPoints            = {},
            pointsSize            = 1.5,
            pointsColor           = '#c0c6cc',
            pointsBorderColor     = '#ffffff',

            isAnimated = false,
            ease = d3Ease.easeQuadInOut,
            areaAnimationDuration = 1000,

            svg,
            chartWidth, chartHeight,
            data,
            dataByDate,
            dataByDateFormatted,
            dataByDateZeroed,

            verticalGridLines,
            horizontalGridLines,
            grid = null,

            tooltipThreshold = 480,

            xAxisPadding = {
                top: 0,
                left: 15,
                bottom: 0,
                right: 0
            },

            dateLabel = 'date',
            valueLabel = 'value',
            keyLabel = 'name',

            // getters
            getName = ({name}) => name,
            getDate = ({date}) => date,

            // events
            dispatcher = d3Dispatch.dispatch('customMouseOver', 'customMouseOut', 'customMouseMove');

       /**
         * This function creates the graph using the selection and data provided
         * @param {D3Selection} _selection A d3 selection that represents
         * the container(s) where the chart(s) will be rendered
         * @param {areaChartData} _data The data to attach and generate the chart
         */
        function exports(_selection) {
            _selection.each(function(_data) {
                chartWidth = width - margin.left - margin.right;
                chartHeight = height - margin.top - margin.bottom;
                data = cleanData(_data);
                dataByDate = getDataByDate(data);

                buildLayers();
                buildScales();
                buildSVG(this);
                buildAxis();
                drawAxis();
                drawStackedAreas();

                if(shouldShowTooltip()) {
                    drawHoverOverlay();
                    drawVerticalMarker();
                    addMouseEvents();
                }
            });
        }

        /**
         * Adds events to the container group if the environment is not mobile
         * Adding: mouseover, mouseout and mousemove
         */
        function addMouseEvents() {
            svg
                .on('mouseover', handleMouseOver)
                .on('mouseout', handleMouseOut)
                .on('mousemove', handleMouseMove);
        }

        /**
         * Formats the value depending on its characteristics
         * @param  {Number} value Value to format
         * @return {Number}       Formatted value
         */
        function getFormattedValue(value) {
            let format;

            if (isInteger(value)) {
                format = formatIntegerValue;
            } else {
                format = formatDecimalValue;
            }

            return format(value);
        }

        /**
         * Creates the d3 x and y axis, setting orientations
         * @private
         */
        function buildAxis() {
            let dataSpan = yScale.domain()[1] - yScale.domain()[0];
            let yTickNumber = dataSpan < verticalTicks - 1 ? dataSpan : verticalTicks;
            let minor, major;

            if (forceAxisSettings === 'custom' && typeof forcedXFormat === 'string') {
                minor = {
                    tick: forcedXTicks,
                    format:  d3TimeFormat.timeFormat(forcedXFormat)
                };
                major = null;
            } else {
                ({minor, major} = getXAxisSettings(dataByDate, width, forceAxisSettings, locale));

                xMonthAxis = d3Axis.axisBottom(xScale)
                    .ticks(major.tick)
                    .tickSize(0, 0)
                    .tickFormat(major.format);
            }

            xAxis = d3Axis.axisBottom(xScale)
                .ticks(minor.tick)
                .tickSize(10, 0)
                .tickPadding(tickPadding)
                .tickFormat(minor.format);


            yAxis = d3Axis.axisRight(yScale)
                .ticks(yTickNumber)
                .tickSize([0])
                .tickPadding(tickPadding)
                .tickFormat(getFormattedValue);

            drawGridLines(minor.tick, yTickNumber);
        }

        /**
         * Builds containers for the chart, the axis and a wrapper for all of them
         * NOTE: The order of drawing of this group elements is really important,
         * as everything else will be drawn on top of them
         * @private
         */
        function buildContainerGroups() {
            let container = svg
              .append('g')
                .classed('container-group', true)
                .attr('transform', `translate(${margin.left},${margin.top})`);

            container
              .append('g').classed('x-axis-group', true)
              .append('g').classed('x axis', true);
            container.selectAll('.x-axis-group')
              .append('g').classed('month-axis', true);
            container
              .append('g').classed('y-axis-group axis', true);
            container
              .append('g').classed('grid-lines-group', true);
            container
              .append('g').classed('chart-group', true);
            container
              .append('g').classed('metadata-group', true);
        }

        /**
         * Builds the stacked layers layout
         * @return {D3Layout} Layout for drawing the chart
         * @private
         */
        function buildLayers() {
            dataByDateFormatted = dataByDate
                .map(d => assign({}, d, d.values))
                .map(d => {
                    Object.keys(d).forEach(k => {
                        const entry = d[k];

                        if (entry && entry.name) {
                            d[entry.name] = entry.value;
                        }
                    });

                    return assign({}, d, {
                        date: new Date(d['key'])
                    });
                });

            dataByDateZeroed = dataByDate
                .map(d => assign({}, d, d.values))
                .map(d => {
                    Object.keys(d).forEach(k => {
                        const entry = d[k];

                        if (entry && entry.name) {
                            d[entry.name] = 0;
                        }
                    });

                    return assign({}, d, {
                        date: new Date(d['key'])
                    });
                });

            let initialTotalsObject = uniq(data.map(({name}) => name))
                                        .reduce((memo, key) => (
                                            assign({}, memo, {[key]: 0})
                                        ), {});

            let totals = data.reduce((memo, item) => (
                assign({}, memo, {[item.name]: memo[item.name]  += item.value})
            ), initialTotalsObject);

            order = formatOrder(totals);

            let stack3 = d3Shape.stack()
                .keys(order)
                .order(d3Shape.stackOrderNone)
                .offset(d3Shape.stackOffsetNone);

            layersInitial = stack3(dataByDateZeroed);
            layers = stack3(dataByDateFormatted);
        }

        /**
         * Takes an object with all topics as keys and their aggregate totals as values,
         * sorts them into a list by descending total value and
         * moves "Other" to the end
         * @param  {Object} totals  Keys of all the topics and their corresponding totals
         * @return {Array}          List of topic names in aggregate order
         */
        function formatOrder(totals) {
            let order = Object.keys(totals)
                .sort((a, b) => {
                    if (totals[a] > totals[b]) return -1;
                    if (totals[a] === totals[b]) return 0;
                    return 1;
                });

            let otherIndex = order.indexOf('Other');

            if (otherIndex >= 0) {
                let other = order.splice(otherIndex, 1);

                order = order.concat(other);
            }

            return order;
        }

        /**
         * Creates the x, y and color scales of the chart
         * @private
         */
        function buildScales() {
            xScale = d3Scale.scaleTime()
                .domain(d3Array.extent(dataByDate, ({date}) => date))
                .rangeRound([0, chartWidth]);

            yScale = d3Scale.scaleLinear()
                .domain([0, getMaxValueByDate()])
                .rangeRound([chartHeight, 0])
                .nice();

            categoryColorMap =  order.reduce((memo, topic, index) => (
                assign({}, memo, {[topic]: colorSchema[index]})
            ), {});
        }

        /**
         * @param  {HTMLElement} container DOM element that will work as the container of the graph
         * @private
         */
        function buildSVG(container) {
            if (!svg) {
                svg = d3Selection.select(container)
                    .append('svg')
                    .classed('britechart stacked-area', true);

                buildContainerGroups();
            }

            svg
                .attr('width', width)
                .attr('height', height);
        }

        /**
         * Parses dates and values into JS Date objects and numbers
         * @param  {obj} data Raw data from JSON file
         * @return {obj}      Parsed data with values and dates
         */
        function cleanData(data) {
            return data.map((d) => {
                d.date = new Date(d[dateLabel]),
                d.value = +d[valueLabel]

                return d;
            });
        }

        /**
         * Draws the x and y axis on the svg object within their
         * respective groups
         * @private
         */
        function drawAxis() {
            svg.select('.x-axis-group .axis.x')
                .attr('transform', `translate( 0, ${chartHeight} )`)
                .call(xAxis);

            if (forceAxisSettings !== 'custom') {
                svg.select('.x-axis-group .month-axis')
                    .attr('transform', `translate(0, ${(chartHeight + monthAxisPadding)})`)
                    .call(xMonthAxis);
            }

            svg.select('.y-axis-group.axis')
                .attr('transform', `translate( ${-xAxisPadding.left}, 0)`)
                .call(yAxis)
                .call(adjustYTickLabels);

            // Moving the YAxis tick labels to the right side
            // d3Selection.selectAll('.y-axis-group .tick text')
            //     .attr('transform', `translate( ${-chartWidth - yTickTextXOffset}, ${yTickTextYOffset})` );
        }

        /**
         * Adjusts the position of the y axis' ticks
         * @param  {D3Selection} selection Y axis group
         * @return void
         */
        function adjustYTickLabels(selection) {
            selection.selectAll('.tick text')
                .attr('transform', `translate(${yTickTextXOffset}, ${yTickTextYOffset})`);
        }

        /**
         * Creates SVG dot elements for each data entry and draws them
         * TODO: Plug
         */
        function drawDataReferencePoints() {
            // Creates Dots on Data points
            var points = svg.select('.chart-group').selectAll('.dots')
                .data(layers)
              .enter().append('g')
                .attr('class', 'dots')
                .attr('d', ({values}) => area(values))
                .attr('clip-path', 'url(#clip)');

            // Processes the points
            // TODO: Optimize this code
            points.selectAll('.dot')
                .data(({values}, index) => values.map((point) => ({index, point})))
                .enter()
                .append('circle')
                .attr('class','dot')
                .attr('r', () => pointsSize)
                .attr('fill', () => pointsColor)
                .attr('stroke-width', '0')
                .attr('stroke', pointsBorderColor)
                .attr('transform', function(d) {
                    let {point} = d;
                    let key = xScale(point.date);

                    dataPoints[key] = dataPoints[key] || [];
                    dataPoints[key].push(d);

                    let {date, y, y0} = point;
                    return `translate( ${xScale(date)}, ${yScale(y + y0)} )`;
                });
        }

        /**
         * Draws grid lines on the background of the chart
         * @return void
         */
        function drawGridLines(xTicks, yTicks) {
            if (grid === 'horizontal' || grid === 'full') {
                horizontalGridLines = svg.select('.grid-lines-group')
                    .selectAll('line.horizontal-grid-line')
                    .data(yScale.ticks(yTicks))
                    .enter()
                        .append('line')
                        .attr('class', 'horizontal-grid-line')
                        .attr('x1', (-xAxisPadding.left - 30))
                        .attr('x2', chartWidth)
                        .attr('y1', (d) => yScale(d))
                        .attr('y2', (d) => yScale(d));
            }

            if (grid === 'vertical' || grid === 'full') {
                verticalGridLines = svg.select('.grid-lines-group')
                    .selectAll('line.vertical-grid-line')
                    .data(xScale.ticks(xTicks))
                    .enter()
                        .append('line')
                        .attr('class', 'vertical-grid-line')
                        .attr('y1', 0)
                        .attr('y2', chartHeight)
                        .attr('x1', (d) => xScale(d))
                        .attr('x2', (d) => xScale(d));
            }

            //draw a horizontal line to extend x-axis till the edges
            baseLine = svg.select('.grid-lines-group')
                .selectAll('line.extended-x-line')
                .data([0])
                .enter()
              .append('line')
                .attr('class', 'extended-x-line')
                .attr('x1', (-xAxisPadding.left - 30))
                .attr('x2', chartWidth)
                .attr('y1', height - margin.bottom - margin.top)
                .attr('y2', height - margin.bottom - margin.top);
        }

        /**
         * Draws an overlay element over the graph
         * @private
         */
        function drawHoverOverlay() {
            overlay = svg.select('.metadata-group')
                .append('rect')
                .attr('class', 'overlay')
                .attr('y1', 0)
                .attr('y2', chartHeight)
                .attr('height', chartHeight)
                .attr('width', chartWidth)
                .attr('fill', 'rgba(0,0,0,0)')
                .style('display', 'none');
        }

        /**
         * Draws the different areas into the chart-group element
         * @private
         */
        function drawStackedAreas() {
            let series;

            area = d3Shape.area()
                .curve(d3Shape.curveMonotoneX)
                .x( ({data}) => xScale(data.date) )
                .y0( (d) => yScale(d[0]) )
                .y1( (d) => yScale(d[1]) );

            if (isAnimated) {
                series = svg.select('.chart-group').selectAll('.layer')
                    .data(layersInitial)
                    .enter()
                  .append('g')
                    .classed('layer-container', true);

                series
                  .append('path')
                    .attr('class', 'layer')
                    .attr('d', area)
                    .style('fill', ({key}) => categoryColorMap[key]);

                // Update
                svg.select('.chart-group').selectAll('.layer')
                    .data(layers)
                    .transition()
                    .delay( (_, i) => areaAnimationDelays[i])
                    .duration(areaAnimationDuration)
                    .ease(ease)
                    .attr('d', area)
                    .style('opacity', areaOpacity)
                    .style('fill', ({key}) => categoryColorMap[key]);
            } else {
                series = svg.select('.chart-group').selectAll('.layer')
                    .data(layers)
                    .enter()
                  .append('g')
                    .classed('layer-container', true);

                series
                  .append('path')
                    .attr('class', 'layer')
                    .attr('d', area)
                    .style('fill', ({key}) => categoryColorMap[key]);

                // Update
                series
                    .attr('d', area)
                    .style('opacity', areaOpacity)
                    .style('fill', ({key}) => categoryColorMap[key]);
            }

            // Exit
            series.exit()
                .transition()
                .style('opacity', 0)
                .remove();
        }

        /**
         * Creates the vertical marker
         * @return void
         */
        function drawVerticalMarker() {
            verticalMarkerContainer = svg.select('.metadata-group')
                .append('g')
                .attr('class', 'vertical-marker-container')
                .attr('transform', 'translate(9999, 0)');

            verticalMarker = verticalMarkerContainer.selectAll('path')
                .data([{
                    x1: 0,
                    y1: 0,
                    x2: 0,
                    y2: 0
                }])
                .enter()
              .append('line')
                .classed('vertical-marker', true)
                .attr('x1', 0)
                .attr('y1', chartHeight)
                .attr('x2', 0)
                .attr('y2', 0);
        }

        /**
         * Removes all the datapoints highlighter circles added to the marker container
         * @return void
         */
        function eraseDataPointHighlights() {
            verticalMarkerContainer.selectAll('.circle-container').remove();
        }

        /**
         * Orders the data by date for consumption on the chart tooltip
         * @param  {areaChartData} data    Chart data
         * @return {Object[]}               Chart data ordered by date
         * @private
         */
        function getDataByDate(data) {
            return d3Collection.nest()
                                .key(getDate)
                                .entries(
                                    data.sort((a, b) => a.date - b.date)
                                )
                                .map(d => {
                                    return assign({}, d, {
                                        date: new Date(d.key)
                                    });
                                });

            // let b =  d3Collection.nest()
            //                     .key(getDate).sortKeys(d3Array.ascending)
            //                     .entries(data);
        }

        /**
         * Computes the maximum sum of values for any date
         *
         * @return {Number} Max value
         */
        function getMaxValueByDate() {
            let keys = uniq(data.map(o => o.name));
            let maxValueByDate = d3Array.max(dataByDateFormatted, function(d){
                let vals = keys.map((key) => d[key]);

                return d3Array.sum(vals);
            });

            return maxValueByDate;
        }

        /**
         * Extract X position on the chart from a given mouse event
         * @param  {obj} event D3 mouse event
         * @return {Number}       Position on the x axis of the mouse
         * @private
         */
        function getMouseXPosition(event) {
            return d3Selection.mouse(event)[0];
        }

        /**
         * Finds out the data entry that is closer to the given position on pixels
         * @param  {Number} mouseX X position of the mouse
         * @return {obj}        Data entry that is closer to that x axis position
         */
        function getNearestDataPoint(mouseX) {
            let points = dataByDate.filter(({date}) => Math.abs(xScale(date) - mouseX) <= epsilon);

            if (points.length) {
                return points[0];
            }
        }

        /**
         * Epsilon is the value given to the number representing half of the distance in
         * pixels between two date data points
         * @return {Number} half distance between any two points
         */
        function setEpsilon() {
            let dates = dataByDate.map(({date}) => date);

            epsilon = (xScale(dates[1]) - xScale(dates[0])) / 2;
        }

        /**
         * MouseMove handler, calculates the nearest dataPoint to the cursor
         * and updates metadata related to it
         * @private
         */
        function handleMouseMove() {
            epsilon || setEpsilon();

            let dataPoint = getNearestDataPoint(getMouseXPosition(this) - margin.left),
                dataPointXPosition;

            if(dataPoint) {
                dataPointXPosition = xScale(new Date( dataPoint.key ));
                // Move verticalMarker to that datapoint
                moveVerticalMarker(dataPointXPosition);
                // Add data points highlighting
                highlightDataPoints(dataPoint);
                // Emit event with xPosition for tooltip or similar feature
                dispatcher.call('customMouseMove', this, dataPoint, categoryColorMap, dataPointXPosition);
            }
        }

        /**
         * MouseOut handler, hides overlay and removes active class on verticalMarkerLine
         * It also resets the container of the vertical marker
         * @private
         */
        function handleMouseOut(data) {
            overlay.style('display', 'none');
            verticalMarker.classed('bc-is-active', false);
            verticalMarkerContainer.attr('transform', 'translate(9999, 0)');

            dispatcher.call('customMouseOut', this, data);
        }

        /**
         * Mouseover handler, shows overlay and adds active class to verticalMarkerLine
         * @private
         */
        function handleMouseOver(data) {
            overlay.style('display', 'block');
            verticalMarker.classed('bc-is-active', true);

            dispatcher.call('customMouseOver', this, data);
        }

        /**
         * Creates coloured circles marking where the exact data y value is for a given data point
         * @param  {obj} dataPoint Data point to extract info from
         * @private
         */
        function highlightDataPoints({values}) {
            let accumulator = 0;

            eraseDataPointHighlights();

            // ensure order stays constant
            values = values
                        .filter(v => !!v)
                        .sort((a,b) => order.indexOf(a.name) > order.indexOf(b.name))

            values.forEach(({name}, index) => {
                let marker = verticalMarkerContainer
                                .append('g')
                                .classed('circle-container', true),
                    circleSize = 12;

                accumulator = accumulator + values[index][valueLabel];

                marker.append('circle')
                    .classed('data-point-highlighter', true)
                    .attr('cx', circleSize)
                    .attr('cy', 0)
                    .attr('r', 5)
                    .style('stroke-width', 2)
                    .style('stroke', categoryColorMap[name]);

                marker.attr('transform', `translate( ${(- circleSize)}, ${(yScale(accumulator))} )` );
            });
        }

        /**
         * Helper method to update the x position of the vertical marker
         * @param  {obj} dataPoint Data entry to extract info
         * @return void
         */
        function moveVerticalMarker(verticalMarkerXPosition) {
            verticalMarkerContainer.attr('transform', `translate(${verticalMarkerXPosition},0)`);
        }

        /**
         * Determines if we should add the tooltip related logic depending on the
         * size of the chart and the tooltipThreshold variable value
         * @return {boolean} Should we build the tooltip?
         * @private
         */
        function shouldShowTooltip() {
            return width > tooltipThreshold;
        }

        // Accessors

        /**
         * Gets or Sets the opacity of the stacked areas in the chart (all of them will have the same opacity)
         * @param  {Object} _x                  Opacity to get/set
         * @return { opacity | module}          Current opacity or Area Chart module to chain calls
         * @public
         */
        exports.areaOpacity = function(_x) {
            if (!arguments.length) {
                return areaOpacity;
            }
            areaOpacity = _x;

            return this;
        };

        /**
         * Gets or Sets the aspect ratio of the chart
         * @param  {Number} _x Desired aspect ratio for the graph
         * @return { (Number | Module) } Current aspect ratio or Area Chart module to chain calls
         * @public
         */
        exports.aspectRatio = function(_x) {
            if (!arguments.length) {
                return aspectRatio;
            }
            aspectRatio = _x;

            return this;
        };

        /**
         * Gets or Sets the colorSchema of the chart
         * @param  {String[]} _x Desired colorSchema for the graph
         * @return { colorSchema | module} Current colorSchema or Chart module to chain calls
         * @public
         */
        exports.colorSchema = function(_x) {
            if (!arguments.length) {
                return colorSchema;
            }
            colorSchema = _x;

            return this;
        };

        /**
         * Gets or Sets the dateLabel of the chart
         * @param  {Number} _x Desired dateLabel for the graph
         * @return { dateLabel | module} Current dateLabel or Chart module to chain calls
         * @public
         */
        exports.dateLabel = function(_x) {
            if (!arguments.length) {
                return dateLabel;
            }
            dateLabel = _x;

            return this;
        };

        /**
         * Exposes the ability to force the chart to show a certain x axis grouping
         * @param  {String} _x Desired format
         * @return { (String|Module) }    Current format or module to chain calls
         * @example
         *     area.forceAxisFormat(area.axisTimeCombinations.HOUR_DAY)
         */
        exports.forceAxisFormat = function(_x) {
            if (!arguments.length) {
              return forceAxisSettings;
            }
            forceAxisSettings = _x;

            return this;
        };

        /**
         * Exposes the ability to force the chart to show a certain x format
         * It requires a `forceAxisFormat` of 'custom' in order to work.
         * NOTE: localization not supported
         * @param  {String} _x              Desired format for x axis
         * @return { (String|Module) }      Current format or module to chain calls
         */
        exports.forcedXFormat = function(_x) {
            if (!arguments.length) {
              return forcedXFormat;
            }
            forcedXFormat = _x;

            return this;
        };

        /**
         * Exposes the ability to force the chart to show a certain x ticks. It requires a `forceAxisFormat` of 'custom' in order to work.
         * NOTE: This value needs to be a multiple of 2, 5 or 10. They won't always work as expected, as D3 decides at the end
         * how many and where the ticks will appear.
         *
         * @param  {Number} _x              Desired number of x axis ticks (multiple of 2, 5 or 10)
         * @return { (Number|Module) }      Current number or ticks or module to chain calls
         */
        exports.forcedXTicks = function(_x) {
            if (!arguments.length) {
              return forcedXTicks;
            }
            forcedXTicks = _x;

            return this;
        };

        /**
         * Gets or Sets the grid mode.
         *
         * @param  {String} _x Desired mode for the grid ('vertical'|'horizontal'|'full')
         * @return { String | module} Current mode of the grid or Area Chart module to chain calls
         * @public
         */
        exports.grid = function(_x) {
            if (!arguments.length) {
                return grid;
            }
            grid = _x;

            return this;
        };

        /**
         * Gets or Sets the height of the chart
         * @param  {Number} _x Desired width for the graph
         * @return { height | module} Current height or Area Chart module to chain calls
         * @public
         */
        exports.height = function(_x) {
            if (!arguments.length) {
                return height;
            }
            if (aspectRatio) {
                width = Math.ceil(_x / aspectRatio);
            }
            height = _x;

            return this;
        };

        /**
         * Gets or Sets the isAnimated property of the chart, making it to animate when render.
         * By default this is 'false'
         *
         * @param  {Boolean} _x Desired animation flag
         * @return { isAnimated | module} Current isAnimated flag or Chart module
         * @public
         */
        exports.isAnimated = function(_x) {
            if (!arguments.length) {
                return isAnimated;
            }
            isAnimated = _x;

            return this;
        };

        /**
         * Gets or Sets the keyLabel of the chart
         * @param  {Number} _x Desired keyLabel for the graph
         * @return { keyLabel | module} Current keyLabel or Chart module to chain calls
         * @public
         */
        exports.keyLabel = function(_x) {
            if (!arguments.length) {
                return keyLabel;
            }
            keyLabel = _x;

            return this;
        };

        /**
         * Gets or Sets the margin of the chart
         * @param  {Object} _x Margin object to get/set
         * @return { margin | module} Current margin or Area Chart module to chain calls
         * @public
         */
        exports.margin = function(_x) {
            if (!arguments.length) {
                return margin;
            }
            margin = _x;

            return this;
        };

        /**
         * Gets or Sets the minimum width of the graph in order to show the tooltip
         * NOTE: This could also depend on the aspect ratio
         *
         * @param  {Object} _x Margin object to get/set
         * @return { tooltipThreshold | module} Current tooltipThreshold or Area Chart module to chain calls
         * @public
         */
        exports.tooltipThreshold = function(_x) {
            if (!arguments.length) {
                return tooltipThreshold;
            }
            tooltipThreshold = _x;

            return this;
        };

        /**
         * Gets or Sets the valueLabel of the chart
         * @param  {Number} _x Desired valueLabel for the graph
         * @return { valueLabel | module} Current valueLabel or Chart module to chain calls
         * @public
         */
        exports.valueLabel = function(_x) {
            if (!arguments.length) {
                return valueLabel;
            }
            valueLabel = _x;

            return this;
        };

        /**
         * Gets or Sets the number of verticalTicks of the yAxis on the chart
         * @param  {Number} _x Desired verticalTicks
         * @return { verticalTicks | module} Current verticalTicks or Chart module to chain calls
         * @public
         */
        exports.verticalTicks = function(_x) {
            if (!arguments.length) {
                return verticalTicks;
            }
            verticalTicks = _x;

            return this;
        };

        /**
         * Gets or Sets the width of the chart
         * @param  {Number} _x Desired width for the graph
         * @return { width | module} Current width or Area Chart module to chain calls
         * @public
         */
        exports.width = function(_x) {
            if (!arguments.length) {
                return width;
            }
            if (aspectRatio) {
                height = Math.ceil(_x * aspectRatio);
            }
            width = _x;

            return this;
        };

        /**
         * Pass language tag for the tooltip to localize the date.
         * Feature uses Intl.DateTimeFormat, for compatability and support, refer to
         * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
         * @param  {String} _x  must be a language tag (BCP 47) like 'en-US' or 'fr-FR'
         * @return { (String|Module) }    Current locale or module to chain calls
         */
        exports.locale = function(_x) {
            if (!arguments.length) {
                return locale;
            }
            locale = _x;

            return this;
        };

        /**
         * Chart exported to png and a download action is fired
         * @public
         */
        exports.exportChart = function(filename, title) {
            exportChart.call(exports, svg, filename, title);
        };

        /**
         * Exposes an 'on' method that acts as a bridge with the event dispatcher
         * We are going to expose this events:
         * customMouseOver, customMouseMove and customMouseOut
         *
         * @return {module} Bar Chart
         * @public
         */
        exports.on = function() {
            let value = dispatcher.on.apply(dispatcher, arguments);

            return value === dispatcher ? exports : value;
        };

        /**
         * Exposes the constants to be used to force the x axis to respect a certain granularity
         * current options: MINUTE_HOUR, HOUR_DAY, DAY_MONTH, MONTH_YEAR
         * @example
         *     area.forceAxisFormat(area.axisTimeCombinations.HOUR_DAY)
         */
        exports.axisTimeCombinations = axisTimeCombinations;

        return exports;
    };
コード例 #22
0
ファイル: DailyPrintView.js プロジェクト: tidepool-org/viz
  renderLegend() {
    this.doc.fontSize(9);
    const lineHeight = this.doc.currentLineHeight();
    this.doc.fillColor('black').fillOpacity(1)
      .text(t('Legend'), this.margins.left, this.bottomEdge - lineHeight * 8);

    const legendHeight = lineHeight * 4;
    const legendTop = this.bottomEdge - lineHeight * 6;

    this.doc.lineWidth(1)
      .rect(this.margins.left, legendTop, this.width, legendHeight)
      .stroke('black');

    this.doc.fontSize(this.smallFontSize);

    const legendVerticalMiddle = legendTop + lineHeight * 2;
    const legendTextMiddle = legendVerticalMiddle - this.doc.currentLineHeight() / 2;
    const legendItemLeftOffset = 9;
    const legendItemLabelOffset = 6;

    let cursor = this.margins.left + legendItemLeftOffset;

    // rendering the items in the legend
    // cbg
    const vertOffsetAdjustments = [
      2.25,
      1,
      0.25,
      0,
      0,
      -0.25,
      -1,
      -2.25,
    ];
    _.each(_.map(range(0, 16, 2), (d) => ([d, d - 7])), (pair) => {
      const [horizOffset, vertOffset] = pair;
      const adjustedVertOffset = vertOffset + vertOffsetAdjustments[horizOffset / 2];
      let fill;

      if (horizOffset < 4) {
        fill = 'high';
      } else if (horizOffset < 12) {
        fill = 'target';
      } else {
        fill = 'low';
      }

      this.doc
        .circle(cursor + horizOffset, legendVerticalMiddle + adjustedVertOffset, this.cbgRadius)
        .fill(this.colors[fill]);
    });
    cursor += 16 + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('CGM'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('CGM')) + legendItemLeftOffset * 2;

    // smbg
    const smbgPositions = {
      target: [cursor, legendVerticalMiddle],
      high: [cursor + this.smbgRadius * 2, legendVerticalMiddle - this.smbgRadius * 2],
      low: [cursor + this.smbgRadius * 2, legendVerticalMiddle + this.smbgRadius * 2],
    };
    this.doc.circle(cursor, legendVerticalMiddle, this.smbgRadius)
      .fill(this.colors.target);
    this.doc.circle(smbgPositions.high[0], smbgPositions.high[1], this.smbgRadius)
      .fill(this.colors.high);
    this.doc.circle(smbgPositions.low[0], smbgPositions.low[1], this.smbgRadius)
      .fill(this.colors.low);
    cursor += this.smbgRadius * 3 + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('BGM'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('BGM')) + legendItemLeftOffset * 2;

    /* boluses */
    const bolusOpts = {
      bolusWidth: this.bolusWidth,
      extendedLineThickness: this.extendedLineThickness,
      interruptedLineThickness: this.interruptedLineThickness,
      triangleHeight: this.triangleHeight,
    };
    const legendBolusYScale = scaleLinear()
      .domain([0, 10])
      .range([legendTop + legendHeight - legendHeight / 4, legendTop + legendHeight / 4]);

    // (normal) bolus
    const normalBolusXScale = scaleLinear()
      .domain([0, 10])
      .range([cursor, cursor + 10]);
    const normalPaths = getBolusPaths(
      { normal: 10, utc: 0 },
      normalBolusXScale,
      legendBolusYScale,
      bolusOpts
    );
    _.each(normalPaths, (path) => {
      this.renderEventPath(path);
    });
    cursor += this.bolusWidth + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('Bolus'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('Bolus')) + legendItemLeftOffset * 2;

    // underride & override boluses
    const rideBolusXScale = scaleLinear()
      .domain([0, 10])
      .range([cursor, cursor + 10]);
    const overridePaths = getBolusPaths(
      {
        type: 'wizard',
        recommended: {
          net: 8,
          carb: 8,
          correction: 0,
        },
        bolus: {
          normal: 10,
          utc: 0,
        },
      },
      rideBolusXScale,
      legendBolusYScale,
      bolusOpts
    );
    _.each(overridePaths, (path) => {
      this.renderEventPath(path);
    });
    const underridePaths = getBolusPaths(
      {
        type: 'wizard',
        recommended: {
          net: 10,
          carb: 8,
          correction: 2,
        },
        bolus: {
          normal: 5,
          utc: 5,
        },
      },
      rideBolusXScale,
      legendBolusYScale,
      bolusOpts
    );
    _.each(underridePaths, (path) => {
      this.renderEventPath(path);
    });
    cursor += this.bolusWidth * 3 + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('Override up & down'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('Override up & down')) + legendItemLeftOffset * 2;

    // interrupted bolus
    const interruptedBolusXScale = scaleLinear()
      .domain([0, 10])
      .range([cursor, cursor + 10]);
    const interruptedPaths = getBolusPaths(
      {
        normal: 6,
        expectedNormal: 10,
        utc: 0,
      },
      interruptedBolusXScale,
      legendBolusYScale,
      bolusOpts
    );
    _.each(interruptedPaths, (path) => {
      this.renderEventPath(path);
    });
    cursor += this.bolusWidth + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('Interrupted'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('Interrupted')) + legendItemLeftOffset * 2;

    // extended bolus
    const extendedBolusXScale = scaleLinear()
      .domain([0, 10])
      .range([cursor, cursor + 10]);
    const extendedPaths = getBolusPaths(
      {
        normal: 5,
        extended: 5,
        duration: 10,
        utc: 0,
      },
      extendedBolusXScale,
      legendBolusYScale,
      bolusOpts
    );
    _.each(extendedPaths, (path) => {
      this.renderEventPath(path);
    });
    cursor += this.bolusWidth / 2 + 10 + legendItemLabelOffset;
    this.doc
      .fillColor('black')
      .text(t('Combo /'), cursor, legendTextMiddle - this.doc.currentLineHeight() / 2)
      .text(t('Extended'));

    cursor += this.doc.widthOfString(t('Extended')) + legendItemLeftOffset * 2;

    // carbohydrates
    this.doc.circle(cursor, legendVerticalMiddle, this.carbRadius)
      .fill(this.colors.carbs);
    this.doc.fillColor('black')
      .fontSize(this.carbsFontSize)
      .text(
        '25',
        cursor - this.carbRadius,
        legendVerticalMiddle - this.carbRadius / 2,
        { align: 'center', width: this.carbRadius * 2 }
      );
    this.doc.fontSize(this.smallFontSize);
    cursor += this.carbRadius + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('Carbs'), cursor, legendTextMiddle);
    cursor += this.doc.widthOfString(t('Carbs')) + legendItemLeftOffset * 2;

    /* basals */
    const legendBasalYScale = scaleLinear()
      .domain([0, 2.5])
      .range([legendTop + legendHeight - legendHeight / 4, legendTop + legendHeight / 4.5]);

    const legendBasalXScale = scaleLinear()
      .domain([0, 10])
      .range([cursor, cursor + 50]);

    const dynamicBasalType = this.isAutomatedBasalDevice ? 'automated' : 'scheduled';

    const scheduled1 = {
      subType: dynamicBasalType,
      rate: 1.5,
      duration: 2,
      utc: 0,
    };
    const negTemp = {
      subType: 'temp',
      rate: 0.5,
      duration: 2.5,
      utc: 2,
      suppressed: {
        rate: 1.5,
      },
    };
    const scheduled2 = {
      subType: 'scheduled',
      rate: 1.75,
      duration: 1.5,
      utc: 4.5,
    };
    const posTemp = {
      subType: 'temp',
      rate: 2,
      duration: 2,
      utc: 6,
      suppressed: {
        rate: 1.75,
      },
    };
    const suspend = {
      subType: 'suspend',
      rate: 0,
      duration: 2,
      utc: 8,
      suppressed: {
        subType: dynamicBasalType,
        rate: dynamicBasalType === 'automated' ? 0 : 1.75,
      },
    };
    const data = {
      basal: [
        scheduled1,
        negTemp,
        scheduled2,
        posTemp,
        suspend,
      ],
      basalSequences: [
        [scheduled1],
        [negTemp],
        [scheduled2],
        [posTemp],
        [suspend],
      ],
    };
    this.renderBasalPaths({
      basalScale: legendBasalYScale,
      data,
      xScale: legendBasalXScale,
    });
    cursor += 50 + legendItemLabelOffset;
    this.doc.fillColor('black').text(t('Basals'), cursor, legendTextMiddle);

    return this;
  }
コード例 #23
0
var d3 = require('d3-array');

// generate random graph object
var nodeTotal = 11;
var edgeTotal = nodeTotal * 1.5;

var graph = {
  nodes: [],
  edges: [],
  adjacent: []
};

graph.nodes = d3.range(nodeTotal).map(function(i) {
  var node = { id : i };
  return node;
});

graph.edges = d3.range(edgeTotal).map(function(i) {
  var source = i < nodeTotal ? i : Math.floor(Math.random() * nodeTotal);
  var target = Math.floor(Math.random() * nodeTotal);

  while (source == target) {
    target = Math.floor(Math.random() * nodeTotal);
  }

  var edge = {
    source: source,
    target: target
  };

  if (graph.adjacent[source] === undefined) {
コード例 #24
0
ファイル: lines.js プロジェクト: 1ec5/iD
    function drawLines(selection, graph, entities, filter) {


        function waystack(a, b) {
            var selected = context.selectedIDs(),
                scoreA = selected.indexOf(a.id) !== -1 ? 20 : 0,
                scoreB = selected.indexOf(b.id) !== -1 ? 20 : 0;

            if (a.tags.highway) { scoreA -= highway_stack[a.tags.highway]; }
            if (b.tags.highway) { scoreB -= highway_stack[b.tags.highway]; }
            return scoreA - scoreB;
        }


        function drawLineGroup(selection, klass, isSelected) {
            var lines = selection
                .selectAll('path')
                .filter(filter)
                .data(getPathData(isSelected), osmEntity.key);

            lines.exit()
                .remove();

            // Optimization: call simple TagClasses only on enter selection. This
            // works because osmEntity.key is defined to include the entity v attribute.
            lines.enter()
                .append('path')
                .attr('class', function(d) {
                    return 'way line ' + klass + ' ' + d.id + (isSelected ? ' selected' : '') +
                        (oldMultiPolygonOuters[d.id] ? ' old-multipolygon' : '');
                })
                .call(svgTagClasses())
                .merge(lines)
                .sort(waystack)
                .attr('d', getPath)
                .call(svgTagClasses().tags(svgRelationMemberTags(graph)));

            return selection;
        }


        function getPathData(isSelected) {
            return function() {
                var layer = this.parentNode.__data__;
                var data = pathdata[layer] || [];
                return data.filter(function(d) {
                    if (isSelected)
                        return context.selectedIDs().indexOf(d.id) !== -1;
                    else
                        return context.selectedIDs().indexOf(d.id) === -1;
                });
            };
        }


        var getPath = svgPath(projection, graph),
            ways = [],
            pathdata = {},
            onewaydata = {},
            oldMultiPolygonOuters = {};

        for (var i = 0; i < entities.length; i++) {
            var entity = entities[i],
                outer = osmSimpleMultipolygonOuterMember(entity, graph);
            if (outer) {
                ways.push(entity.mergeTags(outer.tags));
                oldMultiPolygonOuters[outer.id] = true;
            } else if (entity.geometry(graph) === 'line') {
                ways.push(entity);
            }
        }

        ways = ways.filter(getPath);
        pathdata = _groupBy(ways, function(way) { return way.layer(); });

        _forOwn(pathdata, function(v, k) {
            var arr = _filter(v, function(d) { return d.isOneWay(); });
            onewaydata[k] = _flatten(_map(arr, svgOneWaySegments(projection, graph, 35)));
        });


        var layer = selection.selectAll('.layer-lines');

        var layergroup = layer
            .selectAll('g.layergroup')
            .data(d3_range(-10, 11));

        layergroup = layergroup.enter()
            .append('g')
            .attr('class', function(d) { return 'layergroup layer' + String(d); })
            .merge(layergroup);

        layergroup
            .selectAll('g.linegroup')
            .data(['shadow', 'casing', 'stroke', 'shadow-highlighted', 'casing-highlighted', 'stroke-highlighted'])
            .enter()
            .append('g')
            .attr('class', function(d) { return 'linegroup line-' + d; });


        layergroup.selectAll('g.line-shadow')
            .call(drawLineGroup, 'shadow', false);
        layergroup.selectAll('g.line-casing')
            .call(drawLineGroup, 'casing', false);
        layergroup.selectAll('g.line-stroke')
            .call(drawLineGroup, 'stroke', false);

        layergroup.selectAll('g.line-shadow-highlighted')
            .call(drawLineGroup, 'shadow', true);
        layergroup.selectAll('g.line-casing-highlighted')
            .call(drawLineGroup, 'casing', true);
        layergroup.selectAll('g.line-stroke-highlighted')
            .call(drawLineGroup, 'stroke', true);


        var onewaygroup = layergroup
            .selectAll('g.onewaygroup')
            .data(['oneway']);

        onewaygroup = onewaygroup.enter()
            .append('g')
            .attr('class', 'onewaygroup')
            .merge(onewaygroup);

        var oneways = onewaygroup
            .selectAll('path')
            .filter(filter)
            .data(
                function() { return onewaydata[this.parentNode.__data__] || []; },
                function(d) { return [d.id, d.index]; }
            );

        oneways.exit()
            .remove();

        oneways = oneways.enter()
            .append('path')
            .attr('class', 'oneway')
            .attr('marker-mid', 'url(#oneway-marker)')
            .merge(oneways)
            .attr('d', function(d) { return d.d; });

        if (detected.ie) {
            oneways.each(function() { this.parentNode.insertBefore(this, this); });
        }
    }
コード例 #25
0
ファイル: data.test.js プロジェクト: tidepool-org/viz
  describe('calculateCbgStatsForBin', () => {
    const bin = 900000;
    const binKey = bin.toString();
    const binSize = 1000 * 60 * 30;
    const min = 0;
    const max = 100;
    const data = shuffle(range(min, max + 1));

    const res = utils.calculateCbgStatsForBin(binKey, binSize, data);

    it('should be a function', () => {
      assert.isFunction(utils.calculateCbgStatsForBin);
    });

    it('should produce result full of `undefined`s on empty values array', () => {
      const emptyValsRes = utils.calculateCbgStatsForBin(binKey, binSize, []);
      assert.isObject(emptyValsRes);
      expect(emptyValsRes).to.deep.equal({
        id: binKey,
        min: undefined,
        tenthQuantile: undefined,
        firstQuartile: undefined,
        median: undefined,
        thirdQuartile: undefined,
        ninetiethQuantile: undefined,
        max: undefined,
        msX: bin,
        msFrom: 0,
        msTo: bin * 2,
      });
    });

    it('should add the `binKey` as the `id` on the resulting object', () => {
      assert.isString(res.id);
      expect(res.id).to.equal(binKey);
    });

    it('should add the minimum as the `min` on the resulting object', () => {
      expect(res.min).to.equal(min);
    });

    it('should add the 10th quantile as the `tenthQuantile` on the resulting object', () => {
      expect(res.tenthQuantile).to.equal(10);
    });

    it('should add the first quartile as the `firstQuartile` on the resulting object', () => {
      expect(res.firstQuartile).to.equal(25);
    });

    it('should add the median as `median` on the resulting object', () => {
      expect(res.median).to.equal(50);
    });

    it('should add the third quartile as the `thirdQuartile` on the resulting object', () => {
      expect(res.thirdQuartile).to.equal(75);
    });

    it('should add the 90th quantile as the `ninetiethQuantile` on the resulting object', () => {
      expect(res.ninetiethQuantile).to.equal(90);
    });

    it('should add the maximum as the `max` on the resulting object', () => {
      expect(res.max).to.equal(max);
    });

    it('should add the bin as `msX` on the resulting object', () => {
      expect(res.msX).to.equal(bin);
    });

    it('should add a `msFrom` to the resulting object half a bin earlier', () => {
      expect(res.msFrom).to.equal(0);
    });

    it('should add a `msTo` to the resulting object half a bin later', () => {
      expect(res.msTo).to.equal(1800000);
    });

    describe('when an array of out-of-range annotations is provided', () => {
      const outOfRange = [{
        value: 'low',
        threshold: 25,
      }, {
        value: 'low',
        threshold: 40,
      }, {
        value: 'high',
        threshold: 500,
      }, {
        value: 'high',
        threshold: 400,
      }];
      const resWithOutOfRange = utils.calculateCbgStatsForBin(binKey, binSize, data, outOfRange);

      it('should add `outOfRangeThresholds` to the resulting object', () => {
        expect(resWithOutOfRange.outOfRangeThresholds).to.deep.equal({
          low: 40,
          high: 400,
        });
      });
    });
  });
コード例 #26
0
ファイル: events.js プロジェクト: ebemunk/blog
)

const byStartHour = ([start, end]) => d => {
  const hour = getHours(d.startdate)
  return hour >= start && hour < end
}

export const filterByStartHour = memoizeWith(identity, (start, end) =>
  events.filter(byStartHour([start, end])),
)

import { scaleQuantize } from 'd3-scale'
import { extent, range } from 'd3-array'

export const dateScale = scaleQuantize()
  .range(range(0, 101))
  .domain(extent(events, event => getTime(event.startdate)))

export const byDate = events.reduce((acc, event) => {
  const skey = dateScale(getTime(event.startdate))
  const ekey = dateScale(getTime(event.enddate))
  range(skey, ekey).map(key => acc[key].push(event))
  return acc
}, range(0, 101).map(() => []))

const seasons = [
  'winter',
  'winter',
  'spring',
  'spring',
  'spring',
コード例 #27
0
ファイル: data.test.js プロジェクト: tidepool-org/viz
  describe('calculateSmbgStatsForBin', () => {
    const bin = 5400000;
    const binKey = bin.toString();
    const binSize = 1000 * 60 * 60 * 3;
    const min = 0;
    const max = 100;
    const data = shuffle(range(min, max + 1));

    const res = utils.calculateSmbgStatsForBin(binKey, binSize, data);

    it('should be a function', () => {
      assert.isFunction(utils.calculateSmbgStatsForBin);
    });

    it('should produce result full of `undefined`s on empty values array', () => {
      const emptyValsRes = utils.calculateSmbgStatsForBin(binKey, binSize, []);
      assert.isObject(emptyValsRes);
      expect(emptyValsRes).to.deep.equal({
        id: binKey,
        min: undefined,
        mean: undefined,
        max: undefined,
        msX: bin,
        msFrom: 0,
        msTo: bin * 2,
      });
    });

    it('should add the `binKey` as the `id` on the resulting object', () => {
      assert.isString(res.id);
      expect(res.id).to.equal(binKey);
    });

    it('should add the minimum as the `min` on the resulting object', () => {
      expect(res.min).to.equal(min);
    });

    it('should add the mean as the `mean` on the resulting object', () => {
      expect(res.mean).to.equal(50);
    });

    it('should add the maximum as the `max` on the resulting object', () => {
      expect(res.max).to.equal(max);
    });

    it('should add the bin as `msX` on the resulting object', () => {
      expect(res.msX).to.equal(bin);
    });

    describe('when an array of out-of-range annotations is provided', () => {
      const outOfRange = [{
        value: 'low',
        threshold: 25,
      }, {
        value: 'low',
        threshold: 40,
      }, {
        value: 'high',
        threshold: 500,
      }, {
        value: 'high',
        threshold: 400,
      }];
      const resWithOutOfRange = utils.calculateSmbgStatsForBin(binKey, binSize, data, outOfRange);

      it('should add `outOfRangeThresholds` to the resulting object', () => {
        expect(resWithOutOfRange.outOfRangeThresholds).to.deep.equal({
          low: 40,
          high: 400,
        });
      });
    });
  });
コード例 #28
0
ファイル: Background.js プロジェクト: tidepool-org/viz
  return (
    <g id="background">
      <rect
        className={styles.background}
        x={margins.left}
        y={margins.top}
        width={width}
        height={height}
      />
      {lines}
    </g>
  );
};

Background.defaultProps = {
  data: _.map(range(1, 8), (i) => (i * datetime.THREE_HRS)),
  linesAtThreeHrs: false,
};

Background.propTypes = {
  data: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired,
  linesAtThreeHrs: PropTypes.bool.isRequired,
  margins: PropTypes.shape({
    top: PropTypes.number.isRequired,
    right: PropTypes.number.isRequired,
    bottom: PropTypes.number.isRequired,
    left: PropTypes.number.isRequired,
  }).isRequired,
  svgDimensions: PropTypes.shape({
    width: PropTypes.number.isRequired,
    height: PropTypes.number.isRequired,
コード例 #29
0
  function chord(matrix) {
    var n = matrix.length,
        groupSums = [],
        groupIndex = range(n),
        subgroupIndex = [],
        chords = [],
        groups = chords.groups = new Array(n),
        subgroups = new Array(n * n),
        k,
        x,
        x0,
        dx,
        i,
        j;

    // Compute the sum.
    k = 0, i = -1; while (++i < n) {
      x = 0, j = -1; while (++j < n) {
        x += matrix[i][j];
      }
      groupSums.push(x);
      subgroupIndex.push(range(n));
      k += x;
    }

    // Sort groups…
    if (sortGroups) groupIndex.sort(function(a, b) {
      return sortGroups(groupSums[a], groupSums[b]);
    });

    // Sort subgroups…
    if (sortSubgroups) subgroupIndex.forEach(function(d, i) {
      d.sort(function(a, b) {
        return sortSubgroups(matrix[i][a], matrix[i][b]);
      });
    });

    // Convert the sum to scaling factor for [0, 2pi].
    // TODO Allow start and end angle to be specified?
    // TODO Allow padding to be specified as percentage?
    k = max(0, tau - padAngle * n) / k;
    dx = k ? padAngle : tau / n;

    // Compute the start and end angle for each group and subgroup.
    // Note: Opera has a bug reordering object literal properties!
    x = 0, i = -1; while (++i < n) {
      x0 = x, j = -1; while (++j < n) {
        var di = groupIndex[i],
            dj = subgroupIndex[di][j],
            v = matrix[di][dj],
            a0 = x,
            a1 = x += v * k;
        subgroups[dj * n + di] = {
          index: di,
          subindex: dj,
          startAngle: a0,
          endAngle: a1,
          value: v
        };
      }
      groups[di] = {
        index: di,
        startAngle: x0,
        endAngle: x,
        value: groupSums[di]
      };
      x += dx;
    }

    // Generate chords for each (non-empty) subgroup-subgroup link.
    i = -1; while (++i < n) {
      j = i - 1; while (++j < n) {
        var source = subgroups[j * n + i],
            target = subgroups[i * n + j];
        if (source.value || target.value) {
          chords.push(source.value < target.value
              ? {source: target, target: source}
              : {source: source, target: target});
        }
      }
    }

    return sortChords ? chords.sort(sortChords) : chords;
  }
コード例 #30
0
ファイル: Plot.js プロジェクト: d3plus/d3plus-plot
  /**
      Extends the draw behavior of the abstract Viz class.
      @private
  */
  _draw(callback) {

    if (!this._filteredData.length) return this;

    const stackGroup = (d, i) => this._stacked
      ? `${this._groupBy.length > 1 ? this._ids(d, i).slice(0, -1).join("_") : "group"}`
      : `${this._ids(d, i).join("_")}`;

    let data = this._filteredData.map((d, i) => ({
      __d3plus__: true,
      data: d,
      group: stackGroup(d, i),
      i,
      hci: this._confidence && this._confidence[1] && this._confidence[1](d, i),
      id: this._ids(d, i).slice(0, this._drawDepth + 1).join("_"),
      lci: this._confidence && this._confidence[0] && this._confidence[0](d, i),
      shape: this._shape(d, i),
      x: this._x(d, i),
      x2: this._x2(d, i),
      y: this._y(d, i),
      y2: this._y2(d, i)
    }));

    this._formattedData = data;

    if (this._size) {
      const rExtent = extent(data, d => this._size(d.data));
      this._sizeScaleD3 = () => this._sizeMin;
      this._sizeScaleD3 = scales[`scale${this._sizeScale.charAt(0).toUpperCase()}${this._sizeScale.slice(1)}`]()
        .domain(rExtent)
        .range([rExtent[0] === rExtent[1] ? this._sizeMax : min([this._sizeMax / 2, this._sizeMin]), this._sizeMax]);
    }
    else {
      this._sizeScaleD3 = () => this._sizeMin;
    }

    const x2Exists = data.some(d => d.x2 !== undefined),
          y2Exists = data.some(d => d.y2 !== undefined);

    const height = this._height - this._margin.top - this._margin.bottom,
          opp = this._discrete ? this._discrete === "x" ? "y" : "x" : undefined,
          opp2 = this._discrete ? this._discrete === "x" ? "y2" : "x2" : undefined,
          opps = [opp, opp2],
          parent = this._select,
          transition = this._transition,
          width = this._width - this._margin.left - this._margin.right;

    const x2Time = this._time && data[0].x2 === this._time(data[0].data, data[0].i),
          xTime = this._time && data[0].x === this._time(data[0].data, data[0].i),
          y2Time = this._time && data[0].y2 === this._time(data[0].data, data[0].i),
          yTime = this._time && data[0].y === this._time(data[0].data, data[0].i);

    for (let i = 0; i < data.length; i++) {
      const d = data[i];
      if (xTime) d.x = date(d.x);
      if (x2Time) d.x2 = date(d.x2);
      if (yTime) d.y = date(d.y);
      if (y2Time) d.y2 = date(d.y2);
      d.discrete = d.shape === "Bar" ? `${d[this._discrete]}_${d.group}` : `${d[this._discrete]}`;
    }

    let discreteKeys, domains, stackData, stackKeys;
    if (this._stacked) {

      const groupValues = nest()
        .key(d => d.group)
        .entries(data)
        .reduce((obj, d) => {
          if (!obj[d.key]) obj[d.key] = 0;
          obj[d.key] += sum(d.values, dd => dd[opp]);
          return obj;
        }, {});

      data = data.sort((a, b) => {
        if (this[`_${this._discrete}Sort`]) return this[`_${this._discrete}Sort`](a.data, b.data);
        const a1 = a[this._discrete], b1 = b[this._discrete];
        if (a1 - b1 !== 0) return a1 - b1;
        if (a.group !== b.group) return groupValues[b.group] - groupValues[a.group];
        return b[opp] - a[opp];
      });

      discreteKeys = Array.from(new Set(data.map(d => d.discrete)));
      stackKeys = Array.from(new Set(data.map(d => d.id)));

      stackData = nest()
        .key(d => d.discrete)
        .entries(data)
        .map(d => d.values);

      stackData.forEach(g => {
        const ids = Array.from(new Set(g.map(d => d.id)));
        if (ids.length < stackKeys.length) {
          stackKeys.forEach(k => {
            if (!ids.includes(k)) {
              const d = data.filter(d => d.id === k)[0];
              if (d.shape === "Area") {
                const group = stackGroup(d.data, d.i);
                const fillerPoint = {
                  __d3plus__: true,
                  data: d.data,
                  discrete: d.shape === "Bar" ? `${g[0][this._discrete]}_${group}` : `${g[0][this._discrete]}`,
                  group,
                  id: k,
                  shape: d.shape,
                  [this._discrete]: g[0][this._discrete],
                  [opp]: 0
                };
                data.push(fillerPoint);
              }
            }
          });
        }
      });

      if (this[`_${this._discrete}Sort`]) {
        data.sort((a, b) => this[`_${this._discrete}Sort`](a.data, b.data));
      }
      else {
        data.sort((a, b) => a[this._discrete] - b[this._discrete]);
      }

      const order = this._stackOrder;

      if (order instanceof Array) stackKeys.sort((a, b) => order.indexOf(a) - order.indexOf(b));
      else if (order === d3Shape.stackOrderNone) stackKeys.sort((a, b) => a.localeCompare(b));

      stackData = d3Shape.stack()
        .keys(stackKeys)
        .offset(this._stackOffset)
        .order(order instanceof Array ? d3Shape.stackOrderNone : order)
        .value((group, key) => {
          const d = group.filter(g => g.id === key);
          return d.length ? d[0][opp] : 0;
        })(stackData);

      domains = {
        [this._discrete]: extent(data, d => d[this._discrete]),
        [opp]: [min(stackData.map(g => min(g.map(p => p[0])))), max(stackData.map(g => max(g.map(p => p[1]))))]
      };

    }
    else {
      const xData = this._discrete === "x" ? data.map(d => d.x) : data.map(d => d.x)
        .concat(this._confidence && this._confidence[0] ? data.map(d => d.lci) : [])
        .concat(this._confidence && this._confidence[1] ? data.map(d => d.hci) : []);

      const x2Data = this._discrete === "x" ? data.map(d => d.x2) : data.map(d => d.x2)
        .concat(this._confidence && this._confidence[0] ? data.map(d => d.lci) : [])
        .concat(this._confidence && this._confidence[1] ? data.map(d => d.hci) : []);

      const yData = this._discrete === "y" ? data.map(d => d.y) : data.map(d => d.y)
        .concat(this._confidence && this._confidence[0] ? data.map(d => d.lci) : [])
        .concat(this._confidence && this._confidence[1] ? data.map(d => d.hci) : []);

      const y2Data = this._discrete === "y" ? data.map(d => d.y2) : data.map(d => d.y2)
        .concat(this._confidence && this._confidence[0] ? data.map(d => d.lci) : [])
        .concat(this._confidence && this._confidence[1] ? data.map(d => d.hci) : []);

      if (this[`_${this._discrete}Sort`]) {
        data.sort((a, b) => this[`_${this._discrete}Sort`](a.data, b.data));
      }
      else {
        data.sort((a, b) => a[this._discrete] - b[this._discrete]);
      }

      domains = {
        x: this._xSort ? Array.from(new Set(data.filter(d => d.x).sort((a, b) => this._xSort(a.data, b.data)).map(d => d.x))) : extent(xData, d => d),
        x2: this._x2Sort ? Array.from(new Set(data.filter(d => d.x2).sort((a, b) => this._x2Sort(a.data, b.data)).map(d => d.x2))) : extent(x2Data, d => d),
        y: this._ySort ? Array.from(new Set(data.filter(d => d.y).sort((a, b) => this._ySort(a.data, b.data)).map(d => d.y))) : extent(yData, d => d),
        y2: this._y2Sort ? Array.from(new Set(data.filter(d => d.y2).sort((a, b) => this._y2Sort(a.data, b.data)).map(d => d.y2))) : extent(y2Data, d => d)
      };
    }

    let xDomain = this._xDomain ? this._xDomain.slice() : domains.x,
        xScale = this._xSort ? "Ordinal" : "Linear";

    if (xDomain[0] === void 0) xDomain[0] = domains.x[0];
    if (xDomain[1] === void 0) xDomain[1] = domains.x[1];

    if (xTime) {
      xDomain = xDomain.map(date);
      xScale = "Time";
    }
    else if (this._discrete === "x") {
      xDomain = Array.from(new Set(data.filter(d => d.x).sort((a, b) => this._xSort ? this._xSort(a.data, b.data) : a.x - b.x).map(d => d.x)));
      xScale = "Ordinal";
    }

    let x2Domain = this._x2Domain ? this._x2Domain.slice() : domains.x2,
        x2Scale = this._x2Sort ? "Ordinal" : "Linear";

    if (x2Domain && x2Domain[0] === void 0) x2Domain[0] = domains.x2[0];
    if (x2Domain && x2Domain[1] === void 0) x2Domain[1] = domains.x2[1];

    if (x2Time) {
      x2Domain = x2Domain.map(date);
      x2Scale = "Time";
    }
    else if (this._discrete === "x") {
      x2Domain = Array.from(new Set(data.filter(d => d.x2).sort((a, b) => this._x2Sort ? this._x2Sort(a.data, b.data) : a.x2 - b.x2).map(d => d.x2)));
      x2Scale = "Ordinal";
    }

    let yDomain = this._yDomain ? this._yDomain.slice() : domains.y,
        yScale = this._ySort ? "Ordinal" : "Linear";

    if (yDomain[0] === void 0) yDomain[0] = domains.y[0];
    if (yDomain[1] === void 0) yDomain[1] = domains.y[1];

    let y2Domain = this._y2Domain ? this._y2Domain.slice() : domains.y2,
        y2Scale = this._y2Sort ? "Ordinal" : "Linear";

    if (y2Domain && y2Domain[0] === void 0) y2Domain[0] = domains.y2[0];
    if (y2Domain && y2Domain[1] === void 0) y2Domain[1] = domains.y2[1];

    if (yTime) {
      yDomain = yDomain.map(date);
      yScale = "Time";
    }
    else if (this._discrete === "y") {
      yDomain = Array.from(new Set(data.sort((a, b) => this._ySort ? this._ySort(a.data, b.data) : a.y - b.y).map(d => d.y)));
      yScale = "Ordinal";

      y2Domain = Array.from(new Set(data.sort((a, b) => this._y2Sort ? this._y2Sort(a.data, b.data) : a.y2 - b.y2).map(d => d.y2)));
      y2Scale = "Ordinal";
    }

    if (y2Time) {
      y2Domain = y2Domain.map(date);
      y2Scale = "Time";
    }

    domains = {x: xDomain, x2: x2Domain || xDomain, y: yDomain, y2: y2Domain || yDomain};

    opps.forEach(opp => {
      if (opp && this._baseline !== void 0) {
        const b = this._baseline;
        if (domains[opp] && domains[opp][0] > b) domains[opp][0] = b;
        else if (domains[opp] && domains[opp][1] < b) domains[opp][1] = b;
      }
    });

    let x = scales[`scale${xScale}`]().domain(domains.x).range(range(0, width + 1, width / (domains.x.length - 1))),
        x2 = scales[`scale${x2Scale}`]().domain(domains.x2).range(range(0, width + 1, width / (domains.x2.length - 1))),
        y = scales[`scale${yScale}`]().domain(domains.y.reverse()).range(range(0, height + 1, height / (domains.y.length - 1))),
        y2 = scales[`scale${y2Scale}`]().domain(domains.y2.reverse()).range(range(0, height + 1, height / (domains.y2.length - 1)));

    const shapeData = nest()
      .key(d => d.shape)
      .entries(data)
      .sort((a, b) => this._shapeSort(a.key, b.key));

    const oppScale = this._discrete === "x" ? yScale : xScale;
    if (this._xConfig.scale !== "log" && this._yConfig.scale !== "log" && oppScale !== "Ordinal") {
      shapeData.forEach(d => {
        if (this._buffer[d.key]) {
          const res = this._buffer[d.key].bind(this)({data: d.values, x, y, config: this._shapeConfig[d.key]});
          if (this._xConfig.scale !== "log") x = res[0];
          if (this._yConfig.scale !== "log") y = res[1];
          const res2 = this._buffer[d.key].bind(this)({data: d.values, x: x2, y: y2, x2: true, y2: true, config: this._shapeConfig[d.key]});
          if (this._x2Config.scale !== "log") x2 = res2[0];
          if (this._y2Config.scale !== "log") y2 = res2[1];
        }
      });
    }
    xDomain = x.domain();
    x2Domain = x2.domain();
    yDomain = y.domain();
    y2Domain = y2.domain();

    const testGroup = elem("g.d3plus-plot-test", {enter: {opacity: 0}, parent: this._select}),
          x2Ticks = this._discrete === "x" && !x2Time ? domains.x2 : undefined,
          xTicks = this._discrete === "x" && !xTime ? domains.x : undefined,
          y2Ticks = this._discrete === "y" && !y2Time ? domains.y2 : undefined,
          yTicks = this._discrete === "y" && !yTime ? domains.y : undefined;

    const yC = {
      gridConfig: {stroke: !this._discrete || this._discrete === "x" ? this._yTest.gridConfig().stroke : "transparent"}
    };

    const defaultConfig = {
      barConfig: {"stroke-width": 0},
      gridSize: 0,
      labels: [],
      title: false,
      tickSize: 0
    };

    const defaultX2Config = x2Exists ? {} : defaultConfig;
    const defaultY2Config = y2Exists ? {} : defaultConfig;

    this._yTest
      .domain(yDomain)
      .height(height)
      .maxSize(width / 2)
      .range([undefined, undefined])
      .scale(yScale.toLowerCase())
      .select(testGroup.node())
      .ticks(yTicks)
      .width(width)
      .config(yC)
      .config(this._yConfig)
      .render();

    let yBounds = this._yTest.outerBounds();
    let yWidth = yBounds.width ? yBounds.width + this._yTest.padding() : undefined;

    if (y2Exists) {
      this._y2Test
        .domain(y2Domain)
        .height(height)
        .range([undefined, undefined])
        .scale(y2Scale.toLowerCase())
        .select(testGroup.node())
        .ticks(y2Ticks)
        .width(width)
        .config(yC)
        .config(defaultY2Config)
        .config(this._y2Config)
        .render();
    }

    let y2Bounds = this._y2Test.outerBounds();
    let y2Width = y2Bounds.width ? y2Bounds.width + this._y2Test.padding() : undefined;

    const xC = {
      gridConfig: {stroke: !this._discrete || this._discrete === "y" ? this._xTest.gridConfig().stroke : "transparent"}
    };

    this._xTest
      .domain(xDomain)
      .height(height)
      .maxSize(height / 2)
      .range([undefined, undefined])
      .scale(xScale.toLowerCase())
      .select(testGroup.node())
      .ticks(xTicks)
      .width(width)
      .config(xC)
      .config(this._xConfig)
      .render();

    if (x2Exists) {
      this._x2Test
        .domain(x2Domain)
        .height(height)
        .range([undefined, undefined])
        .scale(x2Scale.toLowerCase())
        .select(testGroup.node())
        .ticks(x2Ticks)
        .width(width)
        .config(xC)
        .tickSize(0)
        .config(defaultX2Config)
        .config(this._x2Config)
        .render();
    }

    const xTestRange = this._xTest._getRange();
    const x2TestRange = this._x2Test._getRange();

    const x2Bounds = this._x2Test.outerBounds();
    const x2Height = x2Bounds.height + this._x2Test.padding();

    let xOffsetLeft = max([yWidth, xTestRange[0], x2TestRange[0]]);

    this._xTest
      .range([xOffsetLeft, undefined])
      .render();

    const topOffset = this._yTest.shapeConfig().labelConfig.fontSize() / 2;

    let xOffsetRight = max([y2Width, width - xTestRange[1], width - x2TestRange[1]]);
    const xBounds = this._xTest.outerBounds();
    const xHeight = xBounds.height + this._xTest.padding();

    this._padding.left += xOffsetLeft;
    this._padding.right += xOffsetRight;
    this._padding.bottom += xHeight;
    this._padding.top += x2Height + topOffset;

    super._draw(callback);

    const horizontalMargin = this._margin.left + this._margin.right;
    const verticalMargin = this._margin.top + this._margin.bottom;
    this._yTest
      .domain(yDomain)
      .height(height)
      .maxSize(width / 2)
      .range([x2Height, height - (xHeight + topOffset + verticalMargin)])
      .scale(yScale.toLowerCase())
      .select(testGroup.node())
      .ticks(yTicks)
      .width(width)
      .config(yC)
      .config(this._yConfig)
      .render();

    yBounds = this._yTest.outerBounds();
    yWidth = yBounds.width ? yBounds.width + this._yTest.padding() : undefined;
    xOffsetLeft = max([yWidth, xTestRange[0], x2TestRange[0]]);

    if (y2Exists) {
      this._y2Test
        .config(yC)
        .domain(y2Domain)
        .gridSize(0)
        .height(height)
        .range([x2Height, height - (xHeight + topOffset + verticalMargin)])
        .scale(y2Scale.toLowerCase())
        .select(testGroup.node())
        .width(width - max([0, xOffsetRight - y2Width]))
        .title(false)
        .config(this._y2Config)
        .config(defaultY2Config)
        .render();
    }

    y2Bounds = this._y2Test.outerBounds();
    y2Width = y2Bounds.width ? y2Bounds.width + this._y2Test.padding() : undefined;
    xOffsetRight = max([y2Width, width - xTestRange[1], width - x2TestRange[1]]);

    const transform = `translate(${this._margin.left}, ${this._margin.top + x2Height + topOffset})`;
    const x2Transform = `translate(${this._margin.left}, ${this._margin.top + topOffset})`;

    const xGroup = elem("g.d3plus-plot-x-axis", {parent, transition, enter: {transform}, update: {transform}});
    const x2Group = elem("g.d3plus-plot-x2-axis", {parent, transition, enter: {transform: x2Transform}, update: {transform: x2Transform}});

    const xTrans = xOffsetLeft > yWidth ? xOffsetLeft - yWidth : 0;
    const yTransform = `translate(${this._margin.left + xTrans}, ${this._margin.top + topOffset})`;
    const yGroup = elem("g.d3plus-plot-y-axis", {parent, transition, enter: {transform: yTransform}, update: {transform: yTransform}});

    const y2Transform = `translate(-${this._margin.right}, ${this._margin.top + topOffset})`;
    const y2Group = elem("g.d3plus-plot-y2-axis", {parent, transition, enter: {transform: y2Transform}, update: {transform: y2Transform}});

    this._xAxis
      .domain(xDomain)
      .height(height - (x2Height + topOffset + verticalMargin))
      .maxSize(height / 2)
      .range([xOffsetLeft, width - (xOffsetRight + horizontalMargin)])
      .scale(xScale.toLowerCase())
      .select(xGroup.node())
      .ticks(xTicks)
      .width(width)
      .config(xC)
      .config(this._xConfig)
      .render();

    if (x2Exists) {
      this._x2Axis
        .domain(x2Domain)
        .height(height - (xHeight + topOffset + verticalMargin))
        .range([xOffsetLeft, width - (xOffsetRight + horizontalMargin)])
        .scale(x2Scale.toLowerCase())
        .select(x2Group.node())
        .ticks(x2Ticks)
        .width(width)
        .config(xC)
        .config(defaultX2Config)
        .config(this._x2Config)
        .render();
    }

    x = (d, x) => {
      if (x === "x2") {
        if (this._x2Config.scale === "log" && d === 0) d = x2Domain[0] < 0 ? -1 : 1;
        return this._x2Axis._getPosition.bind(this._x2Axis)(d);
      }
      else {
        if (this._xConfig.scale === "log" && d === 0) d = xDomain[0] < 0 ? -1 : 1;
        return this._xAxis._getPosition.bind(this._xAxis)(d);
      }
    };
    const xRange = this._xAxis._getRange();

    this._yAxis
      .domain(yDomain)
      .height(height)
      .maxSize(width / 2)
      .range([this._xAxis.outerBounds().y + x2Height, height - (xHeight + topOffset + verticalMargin)])
      .scale(yScale.toLowerCase())
      .select(yGroup.node())
      .ticks(yTicks)
      .width(xRange[xRange.length - 1])
      .config(yC)
      .config(this._yConfig)
      .render();

    if (y2Exists) {
      this._y2Axis
        .config(yC)
        .domain(y2Exists ? y2Domain : yDomain)
        .gridSize(0)
        .height(height)
        .range([this._xAxis.outerBounds().y + x2Height, height - (xHeight + topOffset + verticalMargin)])
        .scale(y2Exists ? y2Scale.toLowerCase() : yScale.toLowerCase())
        .select(y2Group.node())
        .width(width - max([0, xOffsetRight - y2Width]))
        .title(false)
        .config(this._y2Config)
        .config(defaultY2Config)
        .render();
    }

    y = (d, y) => {
      if (y === "y2") {
        if (this._y2Config.scale === "log" && d === 0) d = y2Domain[0] < 0 ? -1 : 1;
        return this._y2Axis._getPosition.bind(this._y2Axis)(d) - x2Height;
      }
      else {
        if (this._yConfig.scale === "log" && d === 0) d = yDomain[0] < 0 ? -1 : 1;
        return this._yAxis._getPosition.bind(this._yAxis)(d) - x2Height;
      }
    };
    const yRange = this._yAxis._getRange();

    const annotationGroup = elem("g.d3plus-plot-annotations", {parent, transition, enter: {transform}, update: {transform}}).node();
    this._annotations.forEach(annotation => {
      new shapes[annotation.shape]()
        .config(annotation)
        .config({
          x: d => d.x2 ? x(d.x2, "x2") : x(d.x),
          x0: this._discrete === "x" ? d => d.x2 ? x(d.x2, "x2") : x(d.x) : x(0),
          x1: this._discrete === "x" ? null : d => d.x2 ? x(d.x2, "x2") : x(d.x),
          y: d => d.y2 ? y(d.y2, "y2") : y(d.y),
          y0: this._discrete === "y" ? d => d.y2 ? y(d.y2, "y2") : y(d.y) : y(0) - yOffset,
          y1: this._discrete === "y" ? null : d => d.y2 ? y(d.y2, "y2") : y(d.y) - yOffset
        })
        .select(annotationGroup)
        .render();
    });

    let yOffset = this._xAxis.barConfig()["stroke-width"];
    if (yOffset) yOffset /= 2;

    const shapeConfig = {
      duration: this._duration,
      label: d => this._drawLabel(d.data, d.i),
      select: elem("g.d3plus-plot-shapes", {parent, transition, enter: {transform}, update: {transform}}).node(),
      x: d => d.x2 ? x(d.x2, "x2") : x(d.x),
      x0: this._discrete === "x" ? d => d.x2 ? x(d.x2, "x2") : x(d.x) : x(0),
      x1: this._discrete === "x" ? null : d => d.x2 ? x(d.x2, "x2") : x(d.x),
      y: d => d.y2 ? y(d.y2, "y2") : y(d.y),
      y0: this._discrete === "y" ? d => d.y2 ? y(d.y2, "y2") : y(d.y) : y(0) - yOffset,
      y1: this._discrete === "y" ? null : d => d.y2 ? y(d.y2, "y2") : y(d.y) - yOffset
    };

    if (this._stacked) {
      const scale = opp === "x" ? x : y;
      shapeConfig[`${opp}`] = shapeConfig[`${opp}0`] = d => {
        const dataIndex = stackKeys.indexOf(d.id),
              discreteIndex = discreteKeys.indexOf(d.discrete);
        return dataIndex >= 0 ? scale(stackData[dataIndex][discreteIndex][0]) : scale(0);
      };
      shapeConfig[`${opp}1`] = d => {
        const dataIndex = stackKeys.indexOf(d.id),
              discreteIndex = discreteKeys.indexOf(d.discrete);
        return dataIndex >= 0 ? scale(stackData[dataIndex][discreteIndex][1]) : scale(0);
      };
    }

    const events = Object.keys(this._on);
    shapeData.forEach(d => {

      const s = new shapes[d.key]().config(shapeConfig).data(d.values);

      if (d.key === "Bar") {

        let space;
        const scale = this._discrete === "x" ? x : y;
        const vals = (this._discrete === "x" ? xDomain : yDomain).filter(d => typeof d !== "string" || d.indexOf("d3plus-buffer-") < 0);
        const range = this._discrete === "x" ? xRange : yRange;
        if (vals.length > 1) space = scale(vals[1]) - scale(vals[0]);
        else space = range[range.length - 1] - range[0];
        space -= this._groupPadding;

        let barSize = space;

        const groups = nest()
          .key(d => d[this._discrete])
          .key(d => d.group)
          .entries(d.values);

        const ids = merge(groups.map(d => d.values.map(v => v.key)));
        const uniqueIds = Array.from(new Set(ids));

        if (max(groups.map(d => d.values.length)) === 1) {
          s[this._discrete]((d, i) => shapeConfig[this._discrete](d, i));
        }
        else {

          barSize = (barSize - this._barPadding * uniqueIds.length - 1) / uniqueIds.length;

          const offset = space / 2 - barSize / 2;

          const xMod = scales.scaleLinear()
            .domain([0, uniqueIds.length - 1])
            .range([-offset, offset]);

          s[this._discrete]((d, i) => shapeConfig[this._discrete](d, i) + xMod(uniqueIds.indexOf(d.group)));

        }

        s.width(barSize);
        s.height(barSize);

      }
      else if (d.key === "Line" && this._confidence) {

        const areaConfig = Object.assign({}, shapeConfig);
        const key = this._discrete === "x" ? "y" : "x";
        const scaleFunction = this._discrete === "x" ? y : x;
        areaConfig[`${key}0`] = d => scaleFunction(this._confidence[0] ? d.lci : d[key]);
        areaConfig[`${key}1`] = d => scaleFunction(this._confidence[1] ? d.hci : d[key]);

        const area = new shapes.Area().config(areaConfig).data(d.values);
        const confidenceConfig = Object.assign(this._shapeConfig, this._confidenceConfig);
        area.config(configPrep.bind(this)(confidenceConfig, "shape", "Area")).render();
        this._shapes.push(area);
      }

      const classEvents = events.filter(e => e.includes(`.${d.key}`)),
            globalEvents = events.filter(e => !e.includes(".")),
            shapeEvents = events.filter(e => e.includes(".shape"));
      for (let e = 0; e < globalEvents.length; e++) s.on(globalEvents[e], d => this._on[globalEvents[e]](d.data, d.i));
      for (let e = 0; e < shapeEvents.length; e++) s.on(shapeEvents[e], d => this._on[shapeEvents[e]](d.data, d.i));
      for (let e = 0; e < classEvents.length; e++) s.on(classEvents[e], d => this._on[classEvents[e]](d.data, d.i));

      s.config(configPrep.bind(this)(this._shapeConfig, "shape", d.key)).render();
      this._shapes.push(s);

    });

    return this;

  }