function init({ chartData }) {
      const $el = $('.ml-explorer-chart');

      // Clear any existing elements from the visualization,
      // then build the svg elements for the chart.
      const chartElement = d3.select(element).select('.content-wrapper');
      chartElement.select('svg').remove();

      const svgWidth = $el.width();
      const svgHeight = chartHeight + margin.top + margin.bottom;

      const svg = chartElement.append('svg')
        .classed('ml-explorer-chart-svg', true)
        .attr('width', svgWidth)
        .attr('height', svgHeight);

      const categoryLimit = 30;
      const scaleCategories = d3.nest()
        .key(d => d.entity)
        .entries(chartData)
        .sort((a, b) => {
          return b.values.length - a.values.length;
        })
        .filter((d, i) => {
          // only filter for rare charts
          if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) {
            return (i < categoryLimit || d.key === highlight);
          }
          return true;
        })
        .map(d => d.key);

      chartData = chartData.filter((d) => {
        return (scaleCategories.includes(d.entity));
      });

      if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) {
        const focusData = chartData.filter((d) => {
          return d.entity === highlight;
        }).map(d => d.value);
        const focusExtent = d3.extent(focusData);

        // now again filter chartData to include only the data points within the domain
        chartData = chartData.filter((d) => {
          return (d.value <= focusExtent[1]);
        });

        lineChartYScale = d3.scale.linear()
          .range([chartHeight, 0])
          .domain([0, focusExtent[1]])
          .nice();
      } else if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) {
        // avoid overflowing the border of the highlighted area
        const rowMargin = 5;
        lineChartYScale = d3.scale.ordinal()
          .rangePoints([rowMargin, chartHeight - rowMargin])
          .domain(scaleCategories);
      } else {
        throw `chartType '${chartType}' not supported`;
      }

      const yAxis = d3.svg.axis().scale(lineChartYScale)
        .orient('left')
        .innerTickSize(0)
        .outerTickSize(0)
        .tickPadding(10);

      let maxYAxisLabelWidth = 0;
      const tempLabelText = svg.append('g')
        .attr('class', 'temp-axis-label tick');
      const tempLabelTextData = (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) ? lineChartYScale.ticks() : scaleCategories;
      tempLabelText.selectAll('text.temp.axis').data(tempLabelTextData)
        .enter()
        .append('text')
        .text((d) => {
          if (fieldFormat !== undefined) {
            return fieldFormat.convert(d, 'text');
          } else {
            if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) {
              return lineChartYScale.tickFormat()(d);
            }
            return d;
          }
        })
        // Don't use an arrow function since we need access to `this`.
        .each(function () {
          maxYAxisLabelWidth = Math.max(this.getBBox().width + yAxis.tickPadding(), maxYAxisLabelWidth);
        })
        .remove();
      d3.select('.temp-axis-label').remove();

      // Set the size of the left margin according to the width of the largest y axis tick label
      // if the chart is either a population chart or a rare chart below the cardinality threshold.
      if (
        chartType === CHART_TYPE.POPULATION_DISTRIBUTION
        || (
          chartType === CHART_TYPE.EVENT_DISTRIBUTION
          && scaleCategories.length <= Y_AXIS_LABEL_THRESHOLD
        )
      ) {
        margin.left = (Math.max(maxYAxisLabelWidth, 40));
      }
      vizWidth = svgWidth - margin.left - margin.right;

      // Set the x axis domain to match the request plot range.
      // This ensures ranges on different charts will match, even when there aren't
      // data points across the full range, and the selected anomalous region is centred.
      lineChartXScale = d3.time.scale()
        .range([0, vizWidth])
        .domain([config.plotEarliest, config.plotLatest]);

      lineChartValuesLine = d3.svg.line()
        .x(d => lineChartXScale(d.date))
        .y(d => lineChartYScale(d[CHART_Y_ATTRIBUTE]))
        .defined(d => d.value !== null);

      lineChartGroup = svg.append('g')
        .attr('class', 'line-chart')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      return chartData;
    }
示例#2
0
function genplot(plotsvg,cf,cellid){
    var all = cf.groupAll()
    var dimensions = {}

    var vars = ['ts','vmt','road_class']
    vars.forEach(function(k){
        dimensions[k] = cf.dimension(function(d){
                            return d[k]
                        })
        return null
    })
    // and another one too
    dimensions['ts2'] = cf.dimension(function(d){
                            return d['ts']
                        })
    var dates = dimensions.ts.group(d3.time.day)

    var group_road_class = dimensions.road_class.group()
    //var filter_roadclass = dimensions.road_class.filter(function(d){
    //                           // return d !== 'totals'
    //                            return d !== 'detector_based'
    //                       })
    var vmtsums = dimensions.vmt
                  .groupAll()
                  .reduceSum(function(d) { return d.vmt; });

    var tsvmt_group = dimensions.ts2
                      .group(function(d){ return d})
                      .reduce(
                          function (p, v) {
                              return p + +(v.vmt)
                          }
                        ,function (p, v) {
                             return p - +v.vmt
                         }
                        ,function (){ return 0 }
                      )

    var mycheck =
        cb.checkBox()
        .dimension(dimensions.road_class)
        .group(group_road_class)
        .renderAll(renderAll(plotsvg,all))

    // svg.selectAll(".limit")
    // .data([mycheck])
    // .enter()
    // .append('div').classed('limit',true)
    // .each(function(limit) {
    //     d3.select(this).call(limit)
    // });


    var mychart =
        xyChart()
        .xdimension(dimensions.ts2)
        .xdim('ts')
        .ydim('vmt')
        .ylabel('VMT grid '+cellid)
        .xlabel('timestamp (hour)')
        .group(tsvmt_group)
        .sumgroup(vmtsums)
        .x(d3.time.scale()
           .rangeRound([0, 10 * 85]));

    var timescale =
        barChart()
        .dimension(dimensions.ts)
        .group(dates)
        .round(d3.time.day.round)
        //.ylabel('Count')
        //.xlabel('timestamp (day)')
        .x(d3.time.scale()
           .rangeRound([0, 10 * 85]))
    barcharts.push(timescale)

    var g = plotsvg.selectAll("g.gridplot")
            .data([mychart,timescale])
    var ch = plotsvg.selectAll("g.checkboxes")
            .data([mycheck])

    // if g is there??
    // g.remove() ??
    g.enter().append('g').attr("class", 'gridplot')
    .each(function(chart) {
        if(chart.on !== undefined ){
            chart
            .on("brush", renderAll(plotsvg,all))
            .on("brushend", renderAll(plotsvg,all));
        }
    })
    .each(render);
    ch.enter().append('g').attr("class", 'checkboxes')
    .each(render);
    return null
}
示例#3
0
NetworkGraph.prototype.make = function() {
    var me = this;
    var graphDom = me.element,
        data = me.lower.data,
        rxData = me.upper.data,
        txData = me.lower.data;

    var getX = Utils.get("x");
    var getY = Utils.get("y");

    var yAxisWidth = 60,
        margin = {
            top: 10,
            right: 20,
            bottom: 5,
            left: yAxisWidth
        },
        width = this.width - margin.left - margin.right,
        height = this.height - margin.top - margin.bottom;

    var yMax = d3.max([d3.max(rxData, getY)
            , d3.max(txData, getY)
            , 1.2 * 1024]) || 0;
    var yMeanUpper = d3.max([d3.mean(rxData, getY)
            , d3.mean(rxData, getY)]) || 0;
    var yMeanLower = d3.max([d3.mean(txData, getY)
            , d3.mean(txData, getY)]) || 0;


    var xMax = d3.max(data, getX);
    var xMin = d3.min(data, getX);

    var x = d3.time.scale()
        .range([0, width])
        .domain(d3.extent(data, getX));

    var y = d3.scale.linear()
        .range([height, 0])
        .domain([-1.2 * yMax, 1.2 * yMax]);

    var line = d3.svg.line()
        .x(function(d) {
            return x(d.x);
        })
        .y(function(d) {
            return y(d.y);
        });

    var area = d3.svg.area()
        .x(function(d) {
            return x(d.x);
        })
        .y0(height / 2)
        .y1(function(d) {
            return y(d.y);
        });

    var lineReflect = d3.svg.line()
        .x(function(d) {
            return x(d.x);
        })
        .y(function(d) {
            return -y(d.y);
        });

    var areaReflect = d3.svg.area()
        .x(function(d) {
            return x(d.x);
        })
        .y0(height / 2)
        .y1(function(d) {
            return -y(d.y) + height;
        });

    var svg = d3.select(graphDom).append("svg")
        .attr("width", me.width)
        .attr("height", me.height)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


    var delta = 0.2;
    var showMeanUpper = yMeanUpper < yMax - delta * yMax &&
        yMeanUpper > delta * yMax;

    var showMeanLower = yMeanLower < yMax - delta * yMax &&
        yMeanLower > delta * yMax;

    if (showMeanUpper && showMeanLower) {
        svg.append("path")
            .datum([
                {
                    x: xMin,
                    y: yMeanUpper
                },
                {
                    x: xMax,
                    y: yMeanUpper
                }
            ])
            .style("stroke-dasharray", ("3, 3"))
            .attr("class", "metrics mean line")
            .attr("d", line);

        svg.append("path")
            .datum([
                {
                    x: xMin,
                    y: -yMeanLower
                },
                {
                    x: xMax,
                    y: -yMeanLower
                }
            ])
            .style("stroke-dasharray", ("3, 3"))
            .attr("class", "metrics mean line")
            .attr("d", line);
    }

    svg.append("path")
        .datum(rxData)
        .attr("class", "metrics rx area")
        .attr("d", area);

    svg.append("path")
        .datum(txData)
        .attr("class", "metrics tx area")
        .attr("d", areaReflect);

    svg.append("path")
        .datum(txData)
        .attr("class", "metrics tx line")
        .attr("d", lineReflect)
        .attr("transform", "translate(0," + height + ")");

    svg.append("path")
        .datum(rxData)
        .attr("class", "metrics rx line")
        .attr("d", line);

    var yTick = Math.max(1024, yMax);
    var yAxis = d3.svg.axis()
        .tickFormat(function(d) {
            return Utils.bytesToString(Math.abs(d));
        })
        .tickValues([-yTick, yTick, 0 /*yMeanUpper, -yMeanLower*/ ])
        .scale(y)
        .orient("left");

    svg.append("g")
        .attr("class", "metrics y axis")
        .call(yAxis);

    svg.append("text")
        .attr("class", "metrics x axis")
        .attr("style", "text-anchor:end")
        .attr("x", width)
        .attr("y", 0)
        .attr("dy", ".32em")
        .text("data in");

    svg.append("text")
        .attr("class", "metrics x axis")
        .attr("style", "text-anchor:end")
        .attr("x", width)
        .attr("y", height)
        .attr("dy", ".32em")
        .text("data out");
};
示例#4
0
import d3 from 'd3';

export const dateFormat = d3.time.format('%b %d, %Y');
export const timeFormat = d3.time.format('%H:%M%p');
export default function() {

    var xScale = d3.time.scale(),
        yScale = d3.scale.linear(),
        upperValue = 70,
        lowerValue = 30,
        multiSeries = _multi(),
        decorate = noop;

    var annotations = annotationLine();
    var rsiLine = seriesLine()
        .xValue(function(d, i) { return d.date; })
        .yValue(function(d, i) { return d.rsi; });

    var rsi = function(selection) {

        multiSeries.xScale(xScale)
            .yScale(yScale)
            .series([annotations, rsiLine])
            .mapping(function(series) {
                if (series === annotations) {
                    return [
                        upperValue,
                        50,
                        lowerValue
                    ];
                }
                return this;
            })
            .decorate(function(g, data, index) {
                g.enter()
                    .attr('class', function(d, i) {
                        return 'multi rsi ' + ['annotations', 'indicator'][i];
                    });
                decorate(g, data, index);
            });

        selection.call(multiSeries);
    };

    rsi.xScale = function(x) {
        if (!arguments.length) {
            return xScale;
        }
        xScale = x;
        return rsi;
    };
    rsi.yScale = function(x) {
        if (!arguments.length) {
            return yScale;
        }
        yScale = x;
        return rsi;
    };
    rsi.upperValue = function(x) {
        if (!arguments.length) {
            return upperValue;
        }
        upperValue = x;
        return rsi;
    };
    rsi.lowerValue = function(x) {
        if (!arguments.length) {
            return lowerValue;
        }
        lowerValue = x;
        return rsi;
    };
    rsi.decorate = function(x) {
        if (!arguments.length) {
            return decorate;
        }
        decorate = x;
        return rsi;
    };

    d3.rebind(rsi, rsiLine, 'yValue', 'xValue');

    return rsi;
}
    function draw(measurements, history) {
      if (!history) return

      var w       = measurements.w
      var h       = measurements.h
      var padding = measurements.padding

      var totalH = h + padding.top + padding.bottom
      // var totalW = w + padding.left + padding.right

      var x = d3.time.scale()
                .range([padding.left + 5, w + padding.left - 5])
                .domain(d3.extent(history, function(d) { return d.ratingDate }))

      var y = d3.scale.linear()
                .range([h + padding.top - 5, padding.top + 5])
                .domain(d3.extent(history, function(d) { return d.newRating }))


      function yAxis(ticks) {
        return d3.svg.axis()
                 .scale(y)
                 .ticks(ticks || 10)
                 .orient('left')
      }

      function xAxis(ticks) {
        return d3.svg.axis()
                 .scale(x)
                 .ticks(ticks || 10)
                 .orient('bottom')
      }

      var line = d3.svg.line()
                   //.interpolate('cardinal')
                   .x(function(d) { return x(d.ratingDate) })
                   .y(function(d) { return y(d.newRating) })


      var svg = d3.select('.history-graph').append('svg')
          .attr('width', w + padding.left + padding.right)
          .attr('height', h + padding.top + padding.bottom)

      svg.append('rect')
         .attr('x', padding.left)
         .attr('y', padding.top)
         .attr('width', w)
         .attr('height', h)



      svg.append('g')
         .attr('class', 'x axis')
         .attr('transform', 'translate(0,' + (h + padding.top) +')')
         .call(xAxis().tickFormat(function(d) {
           var m = moment(d)
           if (m.format('MM') == '01') return m.format('YYYY')
           else return m.format('MMM').toUpperCase()
         }))

      svg.selectAll('g.x.axis .tick text')
         .attr('font-weight', function(d) {
           return moment(d).format('MM') == '01' ? 'bold' : 'normal'
         })
         .attr('fill', function(d) {
           return moment(d).format('MM') == '01' ? 'black' : '#a3a3ae'
         })
         .attr('font-size', function(d) {
           return 11
         })


      svg.append('g')
          .attr('class', 'y axis')
          .attr('transform', 'translate(' + (padding.left - 25) + ')')
          .call(yAxis().tickFormat(function(d) { return parseInt(d) + '' })
           )

      svg.append('g')
         .attr('class', 'grid x')
         .attr('transform', 'translate(0, ' + (h + padding.top) + ')')
         .call(
           xAxis(/*Math.round(w / 35)*/).tickSize(-h, 0, 0)
                   .tickFormat('')
         )

      svg.append('g')
         .attr('class', 'grid y')
         .attr('transform', 'translate(' + padding.left + ',0)')
         .call(
           yAxis(/*Math.round(h / 30)*/).tickSize(-w, 0, 0)
                  .tickFormat('')
         )


      svg.append('path')
         .datum(history)
         .attr('class', 'line')
         .attr('d', line)


      // FIXME !!!
      svg.append('g')
         .selectAll('line')
         .data($scope.colors)
         .enter()
         .append('line')
         .attr('x1', padding.left - 18)
         .attr('x2', padding.left - 18)
         .attr('y1', function(d) {
           return processRatingStripePoint(y(d.start))
         })
         .attr('y2', function(d) {
           return processRatingStripePoint(y(d.end))
         })
         .attr('stroke', function(d) {
           return d.color
         })
         .attr('stroke-width', 3)

      function processRatingStripePoint(y) {
        if (y < padding.top || isNaN(y)) {
          return padding.top
        } else if (y > totalH - padding.bottom) {
          return totalH - padding.bottom
        } else {
          return y
        }
      }
      
      /* render react tooltip component */
      ReactDOM.unmountComponentAtNode(document.getElementById('chart-tooltip'))
      ReactDOM.render(<Tooltip popMethod='click'>
          <div className='tooltip-target'></div>
          <div className='tooltip-body'>
          <div className='tooltip-rating'></div>
          <div className='tooltip-challenge'>
            <div className='challenge-name'></div>
            <div className='challenge-date'></div>
          </div>
          </div>
        </Tooltip>
        , document.getElementById('chart-tooltip'))
    
      svg.selectAll('circle')
         .data(history)
         .enter()
         .append('circle')
         .attr('cx', function(d) {
           return x(d.ratingDate)
         })
         .attr('cy', function(d) {
           return y(d.newRating)
         })
         .attr('fill', function(d) {
           return ratingToColor($scope.colors, d.newRating)
         })
         .on('mouseover', function(d) {
           $scope.historyRating = d.newRating
           $scope.historyDate = moment(d.ratingDate).format('YYYY-MM-DD')
           $scope.historyChallenge = d.challengeName
           $('#chart-tooltip .tooltip-container').on('click', function(){
             if($state.params && ($state.params.subTrack === 'SRM' || $state.params.subTrack === 'MARATHON_MATCH'))
               location.href = $filter('challengeLinks')({'rounds': [{id: d.challengeId, forumId: null}], 'track': $state.params.track, 'subTrack': $state.params.subTrack}, 'detail')
             else
               location.href = $filter('challengeLinks')({id: d.challengeId, 'track': $state.params.track, 'subTrack': $state.params.subTrack}, 'detail')
           })
           
           /* update tooltip location on mouseover, feature currently not inbuilt in react tooltip component */
           d3.select('#chart-tooltip')
              .style('left', (d3.event.pageX-5) + 'px')    
              .style('top', (d3.event.pageY-5) + 'px')
           d3.select('#chart-tooltip .tooltip-container')
              .style('left', '20px !important')    
              .style('top', '-20px !important')
           d3.select('#chart-tooltip .tooltip-container .tooltip-pointer')
              .style('left', '-5.5px !important')    
              .style('bottom', '25px !important')
          
           d3.select('#chart-tooltip .challenge-name').text($scope.historyChallenge)
           d3.select('#chart-tooltip .challenge-date').text(moment(d.ratingDate).format('MMM DD, YYYY'))
           d3.select('#chart-tooltip .tooltip-rating').text($scope.historyRating)
           d3.select('#chart-tooltip .tooltip-rating').style('background', ratingToColor($scope.colors, $scope.historyRating))
           $('#chart-tooltip').removeClass('distribution')
           $scope.$digest()
         })
         .on('mouseout', function(d) {
           $scope.historyRating = undefined
           $scope.$digest()
         })

      /* hide tooltip when clicked anywhere outside */           
      d3.select('body').on('click', function(){
        if((d3.event.target.classList[0] != 'tooltip-target') && !$('#chart-tooltip .tooltip-container').hasClass('tooltip-hide') &&
          (d3.event.target.tagName.toLowerCase()!='circle') && !(d3.event.target.tagName.toLowerCase()=='rect' && d3.event.target.classList[0] == 'hover')) {
          $('#chart-tooltip .tooltip-target').trigger('click')
          $('#chart-tooltip .tooltip-container').off('click')
        }
      })

    }
"use strict";

import React from "react";
import ReactDOM from "react-dom";
import d3 from "d3";

import ReStock from "react-stockcharts";

var parseDate = d3.time.format("%Y-%m-%d").parse
var parseDateTime = d3.time.format("%Y-%m-%d %H:%M:%S").parse

import "stylesheets/re-stock";


import Nav from "lib/navbar";
import Sidebar from "lib/sidebar";
import MainContainer from "lib/main-container";
import MenuGroup from "lib/menu-group";
import MenuItem from "lib/MenuItem";

var DOCUMENTATION = {
	head: "Documentation",
	pages: [
		// require("lib/page/GettingStartedPage").default,
		// require("lib/page/QuickStartExamplesPage").default,
		require("lib/page/OverviewPage").default,
		require("lib/page/SvgVsCanvasPage").default,
		require("lib/page/LotsOfDataPage").default,
		require("lib/page/ChangeLogPage").default,
		require("lib/page/ComingSoonPage").default,
	]
    function drawSwimlane(swlGroup, swlWidth, swlHeight) {
      const data = scope.swimlaneData;

      // Calculate the x axis domain.
      // Elasticsearch aggregation returns points at start of bucket, so set the
      // x-axis min to the start of the aggregation interval.
      // Need to use the min(earliest) and max(earliest) of the context chart
      // aggregation to align the axes of the chart and swimlane elements.
      const xAxisDomain = calculateContextXAxisDomain();
      const x = d3.time.scale().range([0, swlWidth])
        .domain(xAxisDomain);

      const y = d3.scale.linear().range([swlHeight, 0])
        .domain([0, swlHeight]);

      const xAxis = d3.svg.axis()
        .scale(x)
        .orient('bottom')
        .innerTickSize(-swlHeight)
        .outerTickSize(0);

      const yAxis = d3.svg.axis()
        .scale(y)
        .orient('left')
        .tickValues(y.domain())
        .innerTickSize(-swlWidth)
        .outerTickSize(0);

      const axes = swlGroup.append('g');

      axes.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0,' + (swlHeight) + ')')
        .call(xAxis);

      axes.append('g')
        .attr('class', 'y axis')
        .call(yAxis);

      const earliest = xAxisDomain[0].getTime();
      const latest = xAxisDomain[1].getTime();
      const swimlaneAggMs = scope.contextAggregationInterval.asMilliseconds();
      let cellWidth = swlWidth / ((latest - earliest) / swimlaneAggMs);
      if (cellWidth < 1) {
        cellWidth = 1;
      }

      const cells = swlGroup.append('g')
        .attr('class', 'swimlane-cells')
        .selectAll('cells')
        .data(data);

      cells.enter().append('rect')
        .attr('x', (d) => { return x(d.date); })
        .attr('y', 0)
        .attr('rx', 0)
        .attr('ry', 0)
        .attr('class', (d) => { return d.score > 0 ? 'swimlane-cell' : 'swimlane-cell-hidden';})
        .attr('width', cellWidth)
        .attr('height', swlHeight)
        .style('fill', (d) => { return anomalyColorScale(d.score);});

    }
	    var tickFormatter = function (val) {
	    	return d3.time.format(tickFormat)(val);
	    }
  function link(scope, element) {

    // Key dimensions for the viz and constituent charts.
    let svgWidth = angular.element('.results-container').width();
    const focusZoomPanelHeight = 25;
    const focusChartHeight = 310;
    const focusHeight = focusZoomPanelHeight + focusChartHeight;
    const contextChartHeight = 60;
    const contextChartLineTopMargin = 3;
    const chartSpacing = 25;
    const swimlaneHeight = 30;
    const margin = { top: 20, right: 10, bottom: 15, left: 40 };
    const svgHeight = focusHeight + contextChartHeight + swimlaneHeight + chartSpacing + margin.top + margin.bottom;
    let vizWidth  = svgWidth  - margin.left - margin.right;

    const ZOOM_INTERVAL_OPTIONS = [
      { duration: moment.duration(1, 'h'), label: '1h' },
      { duration: moment.duration(12, 'h'), label: '12h' },
      { duration: moment.duration(1, 'd'), label: '1d' },
      { duration: moment.duration(1, 'w'), label: '1w' },
      { duration: moment.duration(2, 'w'), label: '2w' },
      { duration: moment.duration(1, 'M'), label: '1M' }];

    // Set up the color scale to use for indicating score.
    const anomalyColorScale = d3.scale.threshold()
      .domain([3, 25, 50, 75, 100])
      .range(['#d2e9f7', '#8bc8fb', '#ffdd00', '#ff7e00', '#fe5050']);

    // Create a gray-toned version of the color scale to use under the context chart mask.
    const anomalyGrayScale = d3.scale.threshold()
      .domain([3, 25, 50, 75, 100])
      .range(['#dce7ed', '#b0c5d6', '#b1a34e', '#b17f4e', '#c88686']);

    const focusXScale = d3.time.scale().range([0, vizWidth]);
    const focusYScale = d3.scale.linear().range([focusHeight, focusZoomPanelHeight]);

    const focusXAxis = d3.svg.axis().scale(focusXScale).orient('bottom')
      .innerTickSize(-focusChartHeight).outerTickSize(0).tickPadding(10);
    const focusYAxis = d3.svg.axis().scale(focusYScale).orient('left')
      .innerTickSize(-vizWidth).outerTickSize(0).tickPadding(10);

    const focusValuesLine = d3.svg.line()
      .x(function (d) { return focusXScale(d.date); })
      .y(function (d) { return focusYScale(d.value); })
      .defined(d => d.value !== null);
    const focusBoundedArea = d3.svg.area()
      .x (function (d) { return focusXScale(d.date) || 1; })
      .y0(function (d) { return focusYScale(d.upper); })
      .y1(function (d) { return focusYScale(d.lower); })
      .defined(d => (d.lower !== null && d.upper !== null));

    let contextXScale = d3.time.scale().range([0, vizWidth]);
    let contextYScale = d3.scale.linear().range([contextChartHeight, contextChartLineTopMargin]);

    let fieldFormat = undefined;

    const brush = d3.svg.brush();
    let mask;

    scope.$on('render', () => {
      fieldFormat = mlFieldFormatService.getFieldFormat(scope.selectedJob.job_id, scope.detectorIndex);
      render();
      drawContextChartSelection();
    });

    scope.$watchCollection('focusForecastData', renderFocusChart);
    scope.$watchCollection('focusChartData', renderFocusChart);
    scope.$watchGroup(['showModelBounds', 'showForecast'], renderFocusChart);

    // Redraw the charts when the container is resize.
    const resizeChecker = new ResizeChecker(angular.element('.ml-timeseries-chart'));
    resizeChecker.on('resize', () => {
      scope.$evalAsync(() => {
        // Wait a digest cycle before rendering to prevent
        // the underlying ResizeObserver going into an infinite loop.
        render();
        drawContextChartSelection();
        renderFocusChart();
      });
    });

    // Listeners for mouseenter/leave events for rows in the table
    // to highlight the corresponding anomaly mark in the focus chart.
    const tableRecordMousenterListener = function (record) {
      highlightFocusChartAnomaly(record);
    };

    const tableRecordMouseleaveListener = function (record) {
      unhighlightFocusChartAnomaly(record);
    };

    mlAnomaliesTableService.anomalyRecordMouseenter.watch(tableRecordMousenterListener);
    mlAnomaliesTableService.anomalyRecordMouseleave.watch(tableRecordMouseleaveListener);

    element.on('$destroy', () => {
      mlAnomaliesTableService.anomalyRecordMouseenter.unwatch(tableRecordMousenterListener);
      mlAnomaliesTableService.anomalyRecordMouseleave.unwatch(tableRecordMouseleaveListener);
      resizeChecker.destroy();
      scope.$destroy();
    });

    function render() {
      // Clear any existing elements from the visualization,
      // then build the svg elements for the bubble chart.
      const chartElement = d3.select(element.get(0));
      chartElement.selectAll('*').remove();

      if (scope.contextChartData === undefined) {
        return;
      }

      // Set the size of the components according to the width of the parent container at render time.
      svgWidth = Math.max(angular.element('.results-container').width(), 0);

      const svg = chartElement.append('svg')
        .attr('width',  svgWidth)
        .attr('height', svgHeight);

      let contextDataMin;
      let contextDataMax;
      if (scope.modelPlotEnabled === true ||
        (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) {
        const combinedData = scope.contextForecastData === undefined ?
          scope.contextChartData : scope.contextChartData.concat(scope.contextForecastData);

        contextDataMin = d3.min(combinedData, d => Math.min(d.value, d.lower));
        contextDataMax = d3.max(combinedData, d => Math.max(d.value, d.upper));

      } else {
        contextDataMin = d3.min(scope.contextChartData, d => d.value);
        contextDataMax = d3.max(scope.contextChartData, d => d.value);
      }


      // Set the size of the left margin according to the width of the largest y axis tick label.
      // The min / max of the aggregated context chart data may be less than the min / max of the
      // data which is displayed in the focus chart which is likely to be plotted at a lower
      // aggregation interval. Therefore ceil the min / max with the higher absolute value to allow
      // for extra space for chart labels which may have higher values than the context data
      // e.g. aggregated max may be 9500, whereas focus plot max may be 11234.
      const ceiledMax = contextDataMax > 0 ?
        Math.pow(10, Math.ceil(Math.log10(Math.abs(contextDataMax)))) : contextDataMax;

      const flooredMin = contextDataMin >= 0 ?
        contextDataMin : -1 * Math.pow(10, Math.ceil(Math.log10(Math.abs(contextDataMin))));

      // Temporarily set the domain of the focus y axis to the min / max of the full context chart
      // data range so that we can measure the maximum tick label width on temporary text elements.
      focusYScale.domain([flooredMin, ceiledMax]);

      let maxYAxisLabelWidth = 0;
      const tempLabelText = svg.append('g')
        .attr('class', 'temp-axis-label tick');
      tempLabelText.selectAll('text.temp.axis').data(focusYScale.ticks())
        .enter()
        .append('text')
        .text((d) => {
          if (fieldFormat !== undefined) {
            return fieldFormat.convert(d, 'text');
          } else {
            return focusYScale.tickFormat()(d);
          }
        })
        .each(function () {
          maxYAxisLabelWidth = Math.max(this.getBBox().width + focusYAxis.tickPadding(), maxYAxisLabelWidth);
        })
        .remove();
      d3.select('.temp-axis-label').remove();

      margin.left = (Math.max(maxYAxisLabelWidth, 40));
      vizWidth  = Math.max(svgWidth  - margin.left - margin.right, 0);
      focusXScale.range([0, vizWidth]);
      focusYAxis.innerTickSize(-vizWidth);

      const focus = svg.append('g')
        .attr('class', 'focus-chart')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      const context = svg.append('g')
        .attr('class', 'context-chart')
        .attr('transform', 'translate(' + margin.left + ',' + (focusHeight + margin.top + chartSpacing) + ')');

      // Draw each of the component elements.
      createFocusChart(focus, vizWidth, focusHeight);
      drawContextElements(context, vizWidth, contextChartHeight, swimlaneHeight);
    }

    function drawContextChartSelection() {
      if (scope.contextChartData === undefined) {
        return;
      }

      // Make appropriate selection in the context chart to trigger loading of the focus chart.
      let focusLoadFrom;
      let focusLoadTo;
      const contextXMin = contextXScale.domain()[0].getTime();
      const contextXMax = contextXScale.domain()[1].getTime();

      let combinedData = scope.contextChartData;
      if (scope.contextForecastData !== undefined) {
        combinedData = combinedData.concat(scope.contextForecastData);
      }

      if (scope.zoomFrom) {
        focusLoadFrom = scope.zoomFrom.getTime();
      } else {
        focusLoadFrom = _.reduce(combinedData, (memo, point) =>
          Math.min(memo, point.date.getTime()), new Date(2099, 12, 31).getTime());
      }
      focusLoadFrom = Math.max(focusLoadFrom, contextXMin);

      if (scope.zoomTo) {
        focusLoadTo = scope.zoomTo.getTime();
      } else {
        focusLoadTo = _.reduce(combinedData, (memo, point) => Math.max(memo, point.date.getTime()), 0);
      }
      focusLoadTo = Math.min(focusLoadTo, contextXMax);

      if ((focusLoadFrom !== contextXMin) || (focusLoadTo !== contextXMax)) {
        setContextBrushExtent(new Date(focusLoadFrom), new Date(focusLoadTo), true);
      } else {
        // Don't set the brush if the selection is the full context chart domain.
        setBrushVisibility(false);
        const selectedBounds = contextXScale.domain();
        scope.selectedBounds = { min: moment(new Date(selectedBounds[0])), max: moment(selectedBounds[1]) };
        scope.$root.$broadcast('contextChartSelected', { from: selectedBounds[0], to: selectedBounds[1] });
      }
    }

    function createFocusChart(fcsGroup, fcsWidth, fcsHeight) {
      // Split out creation of the focus chart from the rendering,
      // as we want to re-render the paths and points when the zoom area changes.

      // Add a group at the top to display info on the chart aggregation interval
      // and links to set the brush span to 1h, 1d, 1w etc.
      const zoomGroup = fcsGroup.append('g')
        .attr('class', 'focus-zoom');
      zoomGroup.append('rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', fcsWidth)
        .attr('height', focusZoomPanelHeight)
        .attr('class', 'chart-border');
      createZoomInfoElements(zoomGroup, fcsWidth);

      // Add border round plot area.
      fcsGroup.append('rect')
        .attr('x', 0)
        .attr('y', focusZoomPanelHeight)
        .attr('width', fcsWidth)
        .attr('height', focusChartHeight)
        .attr('class', 'chart-border');

      // Add background for x axis.
      const xAxisBg = fcsGroup.append('g')
        .attr('class', 'x-axis-background');
      xAxisBg.append('rect')
        .attr('x', 0)
        .attr('y', fcsHeight)
        .attr('width', fcsWidth)
        .attr('height', chartSpacing);
      xAxisBg.append('line')
        .attr('x1', 0)
        .attr('y1', fcsHeight)
        .attr('x2', 0)
        .attr('y2', fcsHeight + chartSpacing);
      xAxisBg.append('line')
        .attr('x1', fcsWidth)
        .attr('y1', fcsHeight)
        .attr('x2', fcsWidth)
        .attr('y2', fcsHeight + chartSpacing);
      xAxisBg.append('line')
        .attr('x1', 0)
        .attr('y1', fcsHeight + chartSpacing)
        .attr('x2', fcsWidth)
        .attr('y2', fcsHeight + chartSpacing);


      const axes = fcsGroup.append('g');
      axes.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0,' + fcsHeight + ')');
      axes.append('g')
        .attr('class', 'y axis');

      // Create the elements for the metric value line and model bounds area.
      fcsGroup.append('path')
        .attr('class', 'area bounds');
      fcsGroup.append('path')
        .attr('class', 'values-line');
      fcsGroup.append('g')
        .attr('class', 'focus-chart-markers');


      // Create the path elements for the forecast value line and bounds area.
      if (scope.contextForecastData) {
        fcsGroup.append('path')
          .attr('class', 'area forecast');
        fcsGroup.append('path')
          .attr('class', 'values-line forecast');
        fcsGroup.append('g')
          .attr('class', 'focus-chart-markers forecast');
      }


      fcsGroup.append('rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', fcsWidth)
        .attr('height', fcsHeight + 24)
        .attr('class', 'chart-border chart-border-highlight');
    }

    function renderFocusChart() {
      if (scope.focusChartData === undefined) {
        return;
      }

      const data = scope.focusChartData;

      const focusChart = d3.select('.focus-chart');

      // Update the plot interval labels.
      const focusAggInt = scope.focusAggregationInterval.expression;
      const bucketSpan = scope.selectedJob.analysis_config.bucket_span;
      angular.element('.zoom-aggregation-interval').text(
        `(aggregation interval: ${focusAggInt}, bucket span: ${bucketSpan})`);

      // Render the axes.

      // Calculate the x axis domain.
      // Elasticsearch aggregation returns points at start of bucket,
      // so set the x-axis min to the start of the first aggregation interval,
      // and the x-axis max to the end of the last aggregation interval.
      const bounds = scope.selectedBounds;
      const aggMs = scope.focusAggregationInterval.asMilliseconds();
      const earliest = moment(Math.floor((bounds.min.valueOf()) / aggMs) * aggMs);
      const latest = moment(Math.ceil((bounds.max.valueOf()) / aggMs) * aggMs);
      focusXScale.domain([earliest.toDate(), latest.toDate()]);

      // Calculate the y-axis domain.
      if (scope.focusChartData.length > 0 ||
        (scope.focusForecastData !== undefined && scope.focusForecastData.length > 0)) {
        if (fieldFormat !== undefined) {
          focusYAxis.tickFormat(d => fieldFormat.convert(d, 'text'));
        } else {
          // Use default tick formatter.
          focusYAxis.tickFormat(null);
        }

        // Calculate the min/max of the metric data and the forecast data.
        let yMin = 0;
        let yMax = 0;

        let combinedData = data;
        if (scope.focusForecastData !== undefined && scope.focusForecastData.length > 0) {
          combinedData = data.concat(scope.focusForecastData);
        }

        yMin = d3.min(combinedData, (d) => {
          return d.lower !== undefined ? Math.min(d.value, d.lower) : d.value;
        });
        yMax = d3.max(combinedData, (d) => {
          return d.upper !== undefined ? Math.max(d.value, d.upper) : d.value;
        });

        if (yMax === yMin) {
          if (
            contextYScale.domain()[0] !== contextYScale.domain()[1] &&
            yMin >= contextYScale.domain()[0] && yMax <= contextYScale.domain()[1]
          ) {
            // Set the focus chart limits to be the same as the context chart.
            yMin = contextYScale.domain()[0];
            yMax = contextYScale.domain()[1];
          } else {
            yMin -= (yMin * 0.05);
            yMax += (yMax * 0.05);
          }
        }

        focusYScale.domain([yMin, yMax]);

      } else {
        // Display 10 unlabelled ticks.
        focusYScale.domain([0, 10]);
        focusYAxis.tickFormat('');
      }

      // Get the scaled date format to use for x axis tick labels.
      const timeBuckets = new TimeBuckets();
      timeBuckets.setInterval('auto');
      timeBuckets.setBounds(bounds);
      const xAxisTickFormat = timeBuckets.getScaledDateFormat();
      focusChart.select('.x.axis')
        .call(focusXAxis.ticks(numTicksForDateFormat(vizWidth), xAxisTickFormat)
          .tickFormat((d) => {
            return moment(d).format(xAxisTickFormat);
          }));
      focusChart.select('.y.axis')
        .call(focusYAxis);

      filterAxisLabels(focusChart.select('.x.axis'), vizWidth);

      // Render the bounds area and values line.
      if (scope.modelPlotEnabled === true) {
        focusChart.select('.area.bounds')
          .attr('d', focusBoundedArea(data))
          .classed('hidden', !scope.showModelBounds);
      }

      focusChart.select('.values-line')
        .attr('d', focusValuesLine(data));
      drawLineChartDots(data, focusChart, focusValuesLine);

      // Render circle markers for the points.
      // These are used for displaying tooltips on mouseover.
      // Don't render dots where value=null (data gaps) or for multi-bucket anomalies.
      const dots = d3.select('.focus-chart-markers').selectAll('.metric-value')
        .data(data.filter(d => (d.value !== null && !showMultiBucketAnomalyMarker(d))));

      // Remove dots that are no longer needed i.e. if number of chart points has decreased.
      dots.exit().remove();
      // Create any new dots that are needed i.e. if number of chart points has increased.
      dots.enter().append('circle')
        .attr('r', LINE_CHART_ANOMALY_RADIUS)
        .on('mouseover', function (d) {
          showFocusChartTooltip(d, this);
        })
        .on('mouseout', () => mlChartTooltipService.hide());

      // Update all dots to new positions.
      dots.attr('cx', (d) => { return focusXScale(d.date); })
        .attr('cy', (d) => { return focusYScale(d.value); })
        .attr('class', (d) => {
          let markerClass = 'metric-value';
          if (_.has(d, 'anomalyScore')) {
            markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore)}`;
          }
          return markerClass;
        });


      // Render cross symbols for any multi-bucket anomalies.
      const multiBucketMarkers = d3.select('.focus-chart-markers').selectAll('.multi-bucket')
        .data(data.filter(d => (d.anomalyScore !== null && showMultiBucketAnomalyMarker(d) === true)));

      // Remove multi-bucket markers that are no longer needed.
      multiBucketMarkers.exit().remove();

      // Add any new markers that are needed i.e. if number of multi-bucket points has increased.
      multiBucketMarkers.enter().append('path')
        .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross'))
        .on('mouseover', function (d) {
          showFocusChartTooltip(d, this);
        })
        .on('mouseout', () => mlChartTooltipService.hide());

      // Update all markers to new positions.
      multiBucketMarkers.attr('transform', d => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`)
        .attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)}`);


      // Add rectangular markers for any scheduled events.
      const scheduledEventMarkers = d3.select('.focus-chart-markers').selectAll('.scheduled-event-marker')
        .data(data.filter(d => d.scheduledEvents !== undefined));

      // Remove markers that are no longer needed i.e. if number of chart points has decreased.
      scheduledEventMarkers.exit().remove();

      // Create any new markers that are needed i.e. if number of chart points has increased.
      scheduledEventMarkers.enter().append('rect')
        .attr('width', LINE_CHART_ANOMALY_RADIUS * 2)
        .attr('height', SCHEDULED_EVENT_SYMBOL_HEIGHT)
        .attr('class', 'scheduled-event-marker')
        .attr('rx', 1)
        .attr('ry', 1);

      // Update all markers to new positions.
      scheduledEventMarkers.attr('x', (d) => focusXScale(d.date) - LINE_CHART_ANOMALY_RADIUS)
        .attr('y', (d) => focusYScale(d.value) - 3);

      // Plot any forecast data in scope.
      if (scope.focusForecastData !== undefined) {
        focusChart.select('.area.forecast')
          .attr('d', focusBoundedArea(scope.focusForecastData))
          .classed('hidden', !scope.showForecast);
        focusChart.select('.values-line.forecast')
          .attr('d', focusValuesLine(scope.focusForecastData))
          .classed('hidden', !scope.showForecast);

        const forecastDots = d3.select('.focus-chart-markers.forecast').selectAll('.metric-value')
          .data(scope.focusForecastData);

        // Remove dots that are no longer needed i.e. if number of forecast points has decreased.
        forecastDots.exit().remove();
        // Create any new dots that are needed i.e. if number of forecast points has increased.
        forecastDots.enter().append('circle')
          .attr('r', LINE_CHART_ANOMALY_RADIUS)
          .on('mouseover', function (d) {
            showFocusChartTooltip(d, this);
          })
          .on('mouseout', () => mlChartTooltipService.hide());

        // Update all dots to new positions.
        forecastDots.attr('cx', (d) => { return focusXScale(d.date); })
          .attr('cy', (d) => { return focusYScale(d.value); })
          .attr('class', 'metric-value')
          .classed('hidden', !scope.showForecast);
      }

    }

    function createZoomInfoElements(zoomGroup, fcsWidth) {
      // Create zoom duration links applicable for the current time span.
      // Don't add links for any durations which would give a brush extent less than 10px.
      const bounds = timefilter.getActiveBounds();
      const boundsSecs = bounds.max.unix() - bounds.min.unix();
      const minSecs = (10 / vizWidth) * boundsSecs;

      let xPos = 10;
      const zoomLabel = zoomGroup.append('text')
        .attr('x', xPos)
        .attr('y', 17)
        .attr('class', 'zoom-info-text')
        .text('Zoom:');

      const zoomOptions = [{ durationMs: scope.autoZoomDuration, label: 'auto' }];
      _.each(ZOOM_INTERVAL_OPTIONS, (option) => {
        if (option.duration.asSeconds() > minSecs &&
            option.duration.asSeconds() < boundsSecs) {
          zoomOptions.push({ durationMs: option.duration.asMilliseconds(), label: option.label });
        }
      });
      xPos += (zoomLabel.node().getBBox().width + 4);

      _.each(zoomOptions, (option) => {
        const text = zoomGroup.append('a')
          .attr('data-ms', option.durationMs)
          .attr('href', '')
          .append('text')
          .attr('x', xPos)
          .attr('y', 17)
          .attr('class', 'zoom-info-text')
          .text(option.label);

        xPos += (text.node().getBBox().width + 4);
      });

      zoomGroup.append('text')
        .attr('x', (xPos + 6))
        .attr('y', 17)
        .attr('class', 'zoom-info-text zoom-aggregation-interval')
        .text('(aggregation interval: , bucket span: )');

      if (scope.modelPlotEnabled === false) {
        const modelPlotLabel = zoomGroup.append('text')
          .attr('x', 300)
          .attr('y', 17)
          .attr('class', 'zoom-info-text')
          .text('Model bounds are not available');

        modelPlotLabel.attr('x', (fcsWidth - (modelPlotLabel.node().getBBox().width + 10)));
      }

      $('.focus-zoom a').click(function (e) {
        e.preventDefault();
        setZoomInterval($(this).attr('data-ms'));
      });
    }

    function drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) {
      const data = scope.contextChartData;

      contextXScale = d3.time.scale().range([0, cxtWidth])
        .domain(calculateContextXAxisDomain());

      const combinedData = scope.contextForecastData === undefined ? data : data.concat(scope.contextForecastData);
      const valuesRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE };
      _.each(combinedData, (item) => {
        valuesRange.min = Math.min(item.value, valuesRange.min);
        valuesRange.max = Math.max(item.value, valuesRange.max);
      });
      let dataMin = valuesRange.min;
      let dataMax = valuesRange.max;
      const chartLimits = { min: dataMin, max: dataMax };

      if (scope.modelPlotEnabled === true ||
        (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) {
        const boundsRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE };
        _.each(combinedData, (item) => {
          boundsRange.min = Math.min(item.lower, boundsRange.min);
          boundsRange.max = Math.max(item.upper, boundsRange.max);
        });
        dataMin = Math.min(dataMin, boundsRange.min);
        dataMax = Math.max(dataMax, boundsRange.max);

        // Set the y axis domain so that the range of actual values takes up at least 50% of the full range.
        if ((valuesRange.max - valuesRange.min) < 0.5 * (dataMax - dataMin)) {
          if (valuesRange.min > dataMin) {
            chartLimits.min = valuesRange.min - (0.5 * (valuesRange.max - valuesRange.min));
          }

          if (valuesRange.max < dataMax) {
            chartLimits.max = valuesRange.max + (0.5 * (valuesRange.max - valuesRange.min));
          }
        }
      }

      contextYScale = d3.scale.linear().range([cxtChartHeight, contextChartLineTopMargin])
        .domain([chartLimits.min, chartLimits.max]);

      const borders = cxtGroup.append('g')
        .attr('class', 'axis');

      // Add borders left and right.
      borders.append('line')
        .attr('x1', 0)
        .attr('y1', 0)
        .attr('x2', 0)
        .attr('y2', cxtChartHeight + swlHeight);
      borders.append('line')
        .attr('x1', cxtWidth)
        .attr('y1', 0)
        .attr('x2', cxtWidth)
        .attr('y2', cxtChartHeight + swlHeight);

      // Add x axis.
      const bounds = timefilter.getActiveBounds();
      const timeBuckets = new TimeBuckets();
      timeBuckets.setInterval('auto');
      timeBuckets.setBounds(bounds);
      const xAxisTickFormat = timeBuckets.getScaledDateFormat();
      const xAxis = d3.svg.axis().scale(contextXScale)
        .orient('top')
        .innerTickSize(-cxtChartHeight)
        .outerTickSize(0)
        .tickPadding(0)
        .ticks(numTicksForDateFormat(cxtWidth, xAxisTickFormat))
        .tickFormat((d) => {
          return moment(d).format(xAxisTickFormat);
        });

      cxtGroup.datum(data);

      const contextBoundsArea = d3.svg.area()
        .x((d) => { return contextXScale(d.date); })
        .y0((d) => { return contextYScale(Math.min(chartLimits.max, Math.max(d.lower, chartLimits.min))); })
        .y1((d) => { return contextYScale(Math.max(chartLimits.min, Math.min(d.upper, chartLimits.max))); })
        .defined(d => (d.lower !== null && d.upper !== null));

      if (scope.modelPlotEnabled === true) {
        cxtGroup.append('path')
          .datum(data)
          .attr('class', 'area context')
          .attr('d', contextBoundsArea);
      }

      const contextValuesLine = d3.svg.line()
        .x((d) => { return contextXScale(d.date); })
        .y((d) => { return contextYScale(d.value); })
        .defined(d => d.value !== null);

      cxtGroup.append('path')
        .datum(data)
        .attr('class', 'values-line')
        .attr('d', contextValuesLine);
      drawLineChartDots(data, cxtGroup, contextValuesLine, 1);

      // Create the path elements for the forecast value line and bounds area.
      if (scope.contextForecastData !== undefined) {
        cxtGroup.append('path')
          .datum(scope.contextForecastData)
          .attr('class', 'area forecast')
          .attr('d', contextBoundsArea);
        cxtGroup.append('path')
          .datum(scope.contextForecastData)
          .attr('class', 'values-line forecast')
          .attr('d', contextValuesLine);
      }

      // Create and draw the anomaly swimlane.
      const swimlane = cxtGroup.append('g')
        .attr('class', 'swimlane')
        .attr('transform', 'translate(0,' + cxtChartHeight + ')');

      drawSwimlane(swimlane, cxtWidth, swlHeight);

      // Draw a mask over the sections of the context chart and swimlane
      // which fall outside of the zoom brush selection area.
      mask = new ContextChartMask(cxtGroup, scope.contextChartData, scope.modelPlotEnabled, swlHeight)
        .x(contextXScale)
        .y(contextYScale);

      // Draw the x axis on top of the mask so that the labels are visible.
      cxtGroup.append('g')
        .attr('class', 'x axis context-chart-axis')
        .call(xAxis);

      // Move the x axis labels up so that they are inside the contact chart area.
      cxtGroup.selectAll('.x.context-chart-axis text')
        .attr('dy', (cxtChartHeight - 5));

      filterAxisLabels(cxtGroup.selectAll('.x.context-chart-axis'), cxtWidth);

      drawContextBrush(cxtGroup);
    }

    function drawContextBrush(contextGroup) {
      // Create the brush for zooming in to the focus area of interest.
      brush.x(contextXScale)
        .on('brush', brushing)
        .on('brushend', brushed);

      contextGroup.append('g')
        .attr('class', 'x brush')
        .call(brush)
        .selectAll('rect')
        .attr('y', -1)
        .attr('height', contextChartHeight + swimlaneHeight + 1);

      // move the left and right resize areas over to
      // be under the handles
      contextGroup.selectAll('.w rect')
        .attr('x', -10)
        .attr('width', 10);

      contextGroup.selectAll('.e rect')
        .attr('x', 0)
        .attr('width', 10);

      const topBorder = contextGroup.append('rect')
        .attr('class', 'top-border')
        .attr('y', -2)
        .attr('height', contextChartLineTopMargin);

      // Draw the brush handles using SVG foreignObject elements.
      // Note these are not supported on IE11 and below, so will not appear in IE.
      const leftHandle = contextGroup.append('foreignObject')
        .attr('width', 10)
        .attr('height', 90)
        .attr('class', 'brush-handle')
        .html('<div class="brush-handle-inner brush-handle-inner-left"><i class="fa fa-caret-left"></i></div>');
      const rightHandle = contextGroup.append('foreignObject')
        .attr('width', 10)
        .attr('height', 90)
        .attr('class', 'brush-handle')
        .html('<div class="brush-handle-inner brush-handle-inner-right"><i class="fa fa-caret-right"></i></div>');

      setBrushVisibility(!brush.empty());

      function showBrush(show) {
        if (show === true) {
          const brushExtent = brush.extent();
          mask.reveal(brushExtent);
          leftHandle.attr('x', contextXScale(brushExtent[0]) - 10);
          rightHandle.attr('x', contextXScale(brushExtent[1]) + 0);

          topBorder.attr('x', contextXScale(brushExtent[0]) + 1);
          topBorder.attr('width', contextXScale(brushExtent[1]) - contextXScale(brushExtent[0]) - 2);
        }

        setBrushVisibility(show);
      }

      function brushing() {
        const isEmpty = brush.empty();
        showBrush(!isEmpty);
      }

      function brushed() {
        const isEmpty = brush.empty();
        showBrush(!isEmpty);

        const selectedBounds = isEmpty ? contextXScale.domain() : brush.extent();
        const selectionMin = selectedBounds[0].getTime();
        const selectionMax = selectedBounds[1].getTime();

        // Set the color of the swimlane cells according to whether they are inside the selection.
        contextGroup.selectAll('.swimlane-cell')
          .style('fill', (d) => {
            const cellMs = d.date.getTime();
            if (cellMs < selectionMin || cellMs > selectionMax) {
              return anomalyGrayScale(d.score);
            } else {
              return anomalyColorScale(d.score);
            }
          });

        scope.selectedBounds = { min: moment(selectionMin), max: moment(selectionMax) };
        scope.$root.$broadcast('contextChartSelected', { from: selectedBounds[0], to: selectedBounds[1] });
      }
    }

    function setBrushVisibility(show) {
      if (mask !== undefined) {
        const visibility = show ? 'visible' : 'hidden';
        mask.style('visibility', visibility);

        d3.selectAll('.brush').style('visibility', visibility);

        const brushHandles = d3.selectAll('.brush-handle-inner');
        brushHandles.style('visibility', visibility);

        const topBorder = d3.selectAll('.top-border');
        topBorder.style('visibility', visibility);

        const border = d3.selectAll('.chart-border-highlight');
        border.style('visibility', visibility);
      }
    }

    function drawSwimlane(swlGroup, swlWidth, swlHeight) {
      const data = scope.swimlaneData;

      // Calculate the x axis domain.
      // Elasticsearch aggregation returns points at start of bucket, so set the
      // x-axis min to the start of the aggregation interval.
      // Need to use the min(earliest) and max(earliest) of the context chart
      // aggregation to align the axes of the chart and swimlane elements.
      const xAxisDomain = calculateContextXAxisDomain();
      const x = d3.time.scale().range([0, swlWidth])
        .domain(xAxisDomain);

      const y = d3.scale.linear().range([swlHeight, 0])
        .domain([0, swlHeight]);

      const xAxis = d3.svg.axis()
        .scale(x)
        .orient('bottom')
        .innerTickSize(-swlHeight)
        .outerTickSize(0);

      const yAxis = d3.svg.axis()
        .scale(y)
        .orient('left')
        .tickValues(y.domain())
        .innerTickSize(-swlWidth)
        .outerTickSize(0);

      const axes = swlGroup.append('g');

      axes.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0,' + (swlHeight) + ')')
        .call(xAxis);

      axes.append('g')
        .attr('class', 'y axis')
        .call(yAxis);

      const earliest = xAxisDomain[0].getTime();
      const latest = xAxisDomain[1].getTime();
      const swimlaneAggMs = scope.contextAggregationInterval.asMilliseconds();
      let cellWidth = swlWidth / ((latest - earliest) / swimlaneAggMs);
      if (cellWidth < 1) {
        cellWidth = 1;
      }

      const cells = swlGroup.append('g')
        .attr('class', 'swimlane-cells')
        .selectAll('cells')
        .data(data);

      cells.enter().append('rect')
        .attr('x', (d) => { return x(d.date); })
        .attr('y', 0)
        .attr('rx', 0)
        .attr('ry', 0)
        .attr('class', (d) => { return d.score > 0 ? 'swimlane-cell' : 'swimlane-cell-hidden';})
        .attr('width', cellWidth)
        .attr('height', swlHeight)
        .style('fill', (d) => { return anomalyColorScale(d.score);});

    }

    function calculateContextXAxisDomain() {
      // Calculates the x axis domain for the context elements.
      // Elasticsearch aggregation returns points at start of bucket,
      // so set the x-axis min to the start of the first aggregation interval,
      // and the x-axis max to the end of the last aggregation interval.
      // Context chart and swimlane use the same aggregation interval.
      const bounds = timefilter.getActiveBounds();
      let earliest = bounds.min.valueOf();

      if (scope.swimlaneData !== undefined && scope.swimlaneData.length > 0) {
        // Adjust the earliest back to the time of the first swimlane point
        // if this is before the time filter minimum.
        earliest = Math.min(_.first(scope.swimlaneData).date.getTime(), bounds.min.valueOf());
      }

      const contextAggMs = scope.contextAggregationInterval.asMilliseconds();
      const earliestMs = Math.floor(earliest / contextAggMs) * contextAggMs;
      const latestMs = Math.ceil((bounds.max.valueOf()) / contextAggMs) * contextAggMs;

      return [new Date(earliestMs), new Date(latestMs)];
    }

    // Sets the extent of the brush on the context chart to the
    // supplied from and to Date objects.
    function setContextBrushExtent(from, to, fireEvent) {
      brush.extent([from, to]);
      brush(d3.select('.brush'));
      if (fireEvent) {
        brush.event(d3.select('.brush'));
      }
    }

    function setZoomInterval(ms) {
      const bounds = timefilter.getActiveBounds();
      const minBoundsMs = bounds.min.valueOf();
      const maxBoundsMs = bounds.max.valueOf();

      // Attempt to retain the same zoom end time.
      // If not, go back to the bounds start and add on the required millis.
      const millis = +ms;
      let to = scope.zoomTo.getTime();
      let from = to - millis;
      if (from < minBoundsMs) {
        from = minBoundsMs;
        to = Math.min(minBoundsMs + millis, maxBoundsMs);
      }

      setContextBrushExtent(new Date(from), new Date(to), true);
    }

    function showFocusChartTooltip(marker, circle) {
      // Show the time and metric values in the tooltip.
      // Uses date, value, upper, lower and anomalyScore (optional) marker properties.
      const formattedDate = moment(marker.date).format('MMMM Do YYYY, HH:mm');
      let contents = formattedDate + '<br/><hr/>';

      if (_.has(marker, 'anomalyScore')) {
        const score = parseInt(marker.anomalyScore);
        const displayScore = (score > 0 ? score : '< 1');
        contents += `anomaly score: ${displayScore}<br/>`;

        if (showMultiBucketAnomalyTooltip(marker) === true) {
          contents += `multi-bucket impact: ${getMultiBucketImpactLabel(marker.multiBucketImpact)}<br/>`;
        }

        if (scope.modelPlotEnabled === false) {
          // Show actual/typical when available except for rare detectors.
          // Rare detectors always have 1 as actual and the probability as typical.
          // Exposing those values in the tooltip with actual/typical labels might irritate users.
          if (_.has(marker, 'actual') && marker.function !== 'rare') {
            // Display the record actual in preference to the chart value, which may be
            // different depending on the aggregation interval of the chart.
            contents += `actual: ${formatValue(marker.actual, marker.function, fieldFormat)}`;
            contents += `<br/>typical: ${formatValue(marker.typical, marker.function, fieldFormat)}`;
          } else {
            contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`;
            if (_.has(marker, 'byFieldName') && _.has(marker, 'numberOfCauses')) {
              const numberOfCauses = marker.numberOfCauses;
              const byFieldName = mlEscape(marker.byFieldName);
              if (numberOfCauses < 10) {
                // If numberOfCauses === 1, won't go into this block as actual/typical copied to top level fields.
                contents += `<br/> ${numberOfCauses} unusual ${byFieldName} values`;
              } else {
                // Maximum of 10 causes are stored in the record, so '10' may mean more than 10.
                contents += `<br/> ${numberOfCauses}+ unusual ${byFieldName} values`;
              }
            }
          }
        } else {
          contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`;
          contents += `<br/>upper bounds: ${formatValue(marker.upper, marker.function, fieldFormat)}`;
          contents += `<br/>lower bounds: ${formatValue(marker.lower, marker.function, fieldFormat)}`;
        }
      } else {
        // TODO - need better formatting for small decimals.
        if (_.get(marker, 'isForecast', false) === true) {
          contents += `prediction: ${formatValue(marker.value, marker.function, fieldFormat)}`;
        } else {
          contents += `value: ${formatValue(marker.value, marker.function, fieldFormat)}`;
        }

        if (scope.modelPlotEnabled === true) {
          contents += `<br/>upper bounds: ${formatValue(marker.upper, marker.function, fieldFormat)}`;
          contents += `<br/>lower bounds: ${formatValue(marker.lower, marker.function, fieldFormat)}`;
        }
      }

      if (_.has(marker, 'scheduledEvents')) {
        contents += `<br/><hr/>Scheduled events:<br/>${marker.scheduledEvents.map(mlEscape).join('<br/>')}`;
      }

      mlChartTooltipService.show(contents, circle, {
        x: LINE_CHART_ANOMALY_RADIUS * 2,
        y: 0
      });
    }

    function highlightFocusChartAnomaly(record) {
      // Highlights the anomaly marker in the focus chart corresponding to the specified record.

      // Find the anomaly marker which corresponds to the time of the anomaly record.
      // Depending on the way the chart is aggregated, there may not be
      // a point at exactly the same time as the record being highlighted.
      const anomalyTime = record.source.timestamp;
      const markerToSelect = findChartPointForAnomalyTime(scope.focusChartData, anomalyTime);

      // Render an additional highlighted anomaly marker on the focus chart.
      // TODO - plot anomaly markers for cases where there is an anomaly due
      // to the absence of data and model plot is enabled.
      if (markerToSelect !== undefined) {
        const selectedMarker = d3.select('.focus-chart-markers')
          .selectAll('.focus-chart-highlighted-marker')
          .data([markerToSelect]);
        if (showMultiBucketAnomalyMarker(markerToSelect) === true) {
          selectedMarker.enter().append('path')
            .attr('d', d3.svg.symbol().size(MULTI_BUCKET_SYMBOL_SIZE).type('cross'))
            .attr('transform', d => `translate(${focusXScale(d.date)}, ${focusYScale(d.value)})`)
            .attr('class', d => `anomaly-marker multi-bucket ${getSeverityWithLow(d.anomalyScore)} highlighted`);
        } else {
          selectedMarker.enter().append('circle')
            .attr('r', LINE_CHART_ANOMALY_RADIUS)
            .attr('cx', d => focusXScale(d.date))
            .attr('cy', d => focusYScale(d.value))
            .attr('class', d => `anomaly-marker metric-value ${getSeverityWithLow(d.anomalyScore)} highlighted`);
        }

        // Display the chart tooltip for this marker.
        // Note the values of the record and marker may differ depending on the levels of aggregation.
        const anomalyMarker = $('.focus-chart-markers .anomaly-marker.highlighted');
        if (anomalyMarker.length) {
          showFocusChartTooltip(markerToSelect, anomalyMarker[0]);
        }
      }
    }

    function unhighlightFocusChartAnomaly() {
      d3.select('.focus-chart-markers').selectAll('.anomaly-marker.highlighted').remove();
      mlChartTooltipService.hide();
    }


  }
    function drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) {
      const data = scope.contextChartData;

      contextXScale = d3.time.scale().range([0, cxtWidth])
        .domain(calculateContextXAxisDomain());

      const combinedData = scope.contextForecastData === undefined ? data : data.concat(scope.contextForecastData);
      const valuesRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE };
      _.each(combinedData, (item) => {
        valuesRange.min = Math.min(item.value, valuesRange.min);
        valuesRange.max = Math.max(item.value, valuesRange.max);
      });
      let dataMin = valuesRange.min;
      let dataMax = valuesRange.max;
      const chartLimits = { min: dataMin, max: dataMax };

      if (scope.modelPlotEnabled === true ||
        (scope.contextForecastData !== undefined && scope.contextForecastData.length > 0)) {
        const boundsRange = { min: Number.MAX_VALUE, max: Number.MIN_VALUE };
        _.each(combinedData, (item) => {
          boundsRange.min = Math.min(item.lower, boundsRange.min);
          boundsRange.max = Math.max(item.upper, boundsRange.max);
        });
        dataMin = Math.min(dataMin, boundsRange.min);
        dataMax = Math.max(dataMax, boundsRange.max);

        // Set the y axis domain so that the range of actual values takes up at least 50% of the full range.
        if ((valuesRange.max - valuesRange.min) < 0.5 * (dataMax - dataMin)) {
          if (valuesRange.min > dataMin) {
            chartLimits.min = valuesRange.min - (0.5 * (valuesRange.max - valuesRange.min));
          }

          if (valuesRange.max < dataMax) {
            chartLimits.max = valuesRange.max + (0.5 * (valuesRange.max - valuesRange.min));
          }
        }
      }

      contextYScale = d3.scale.linear().range([cxtChartHeight, contextChartLineTopMargin])
        .domain([chartLimits.min, chartLimits.max]);

      const borders = cxtGroup.append('g')
        .attr('class', 'axis');

      // Add borders left and right.
      borders.append('line')
        .attr('x1', 0)
        .attr('y1', 0)
        .attr('x2', 0)
        .attr('y2', cxtChartHeight + swlHeight);
      borders.append('line')
        .attr('x1', cxtWidth)
        .attr('y1', 0)
        .attr('x2', cxtWidth)
        .attr('y2', cxtChartHeight + swlHeight);

      // Add x axis.
      const bounds = timefilter.getActiveBounds();
      const timeBuckets = new TimeBuckets();
      timeBuckets.setInterval('auto');
      timeBuckets.setBounds(bounds);
      const xAxisTickFormat = timeBuckets.getScaledDateFormat();
      const xAxis = d3.svg.axis().scale(contextXScale)
        .orient('top')
        .innerTickSize(-cxtChartHeight)
        .outerTickSize(0)
        .tickPadding(0)
        .ticks(numTicksForDateFormat(cxtWidth, xAxisTickFormat))
        .tickFormat((d) => {
          return moment(d).format(xAxisTickFormat);
        });

      cxtGroup.datum(data);

      const contextBoundsArea = d3.svg.area()
        .x((d) => { return contextXScale(d.date); })
        .y0((d) => { return contextYScale(Math.min(chartLimits.max, Math.max(d.lower, chartLimits.min))); })
        .y1((d) => { return contextYScale(Math.max(chartLimits.min, Math.min(d.upper, chartLimits.max))); })
        .defined(d => (d.lower !== null && d.upper !== null));

      if (scope.modelPlotEnabled === true) {
        cxtGroup.append('path')
          .datum(data)
          .attr('class', 'area context')
          .attr('d', contextBoundsArea);
      }

      const contextValuesLine = d3.svg.line()
        .x((d) => { return contextXScale(d.date); })
        .y((d) => { return contextYScale(d.value); })
        .defined(d => d.value !== null);

      cxtGroup.append('path')
        .datum(data)
        .attr('class', 'values-line')
        .attr('d', contextValuesLine);
      drawLineChartDots(data, cxtGroup, contextValuesLine, 1);

      // Create the path elements for the forecast value line and bounds area.
      if (scope.contextForecastData !== undefined) {
        cxtGroup.append('path')
          .datum(scope.contextForecastData)
          .attr('class', 'area forecast')
          .attr('d', contextBoundsArea);
        cxtGroup.append('path')
          .datum(scope.contextForecastData)
          .attr('class', 'values-line forecast')
          .attr('d', contextValuesLine);
      }

      // Create and draw the anomaly swimlane.
      const swimlane = cxtGroup.append('g')
        .attr('class', 'swimlane')
        .attr('transform', 'translate(0,' + cxtChartHeight + ')');

      drawSwimlane(swimlane, cxtWidth, swlHeight);

      // Draw a mask over the sections of the context chart and swimlane
      // which fall outside of the zoom brush selection area.
      mask = new ContextChartMask(cxtGroup, scope.contextChartData, scope.modelPlotEnabled, swlHeight)
        .x(contextXScale)
        .y(contextYScale);

      // Draw the x axis on top of the mask so that the labels are visible.
      cxtGroup.append('g')
        .attr('class', 'x axis context-chart-axis')
        .call(xAxis);

      // Move the x axis labels up so that they are inside the contact chart area.
      cxtGroup.selectAll('.x.context-chart-axis text')
        .attr('dy', (cxtChartHeight - 5));

      filterAxisLabels(cxtGroup.selectAll('.x.context-chart-axis'), cxtWidth);

      drawContextBrush(cxtGroup);
    }
示例#12
0
'use strict';
/**/
var React = require('react');
var d3 = require('d3');

require('styles/react-stockcharts');
require('stylesheets/re-stock');

var ReadME = require('md/MAIN.md');

document.getElementById("content").innerHTML = ReadME;

var parseDate = d3.time.format("%Y-%m-%d").parse
d3.tsv("data/MSFT.tsv", function(err, data) {
	data.forEach((d, i) => {
		d.date = new Date(parseDate(d.date).getTime());
		d.open = +d.open;
		d.high = +d.high;
		d.low = +d.low;
		d.close = +d.close;
		d.volume = +d.volume;
		// console.log(d);
	});

	var CandleStickChartWithEdge = require('lib/charts/CandleStickChartWithEdge');

	React.render(<CandleStickChartWithEdge data={data} />, document.getElementById("chart"));
});

//require('./examples/freezer-example');
示例#13
0
function link(scope, el, attr){

  var renderTimeout;
  var m = 50;

  // DATA PREP
  var parseDate =
    d3.time.format("%x").parse;

  var xScale = d3.time.scale()
           // .range([0, _o.width]);
  var yScale = d3.scale.linear()
            // .range([_o.height, 0]);

  // APPEND SVG TO CONTAINER ELEMENT
  var svg = d3.select(el[0])
    .append('svg');

  // GENERATORS
  var xAxis =
    d3.svg.axis()
      .scale(xScale)
      .orient("bottom");

  var yAxis =
    d3.svg.axis()
      .scale(yScale)
      .orient("left");

  var lineGen
    = d3.svg.line()
      .x(function (d) { return xScale(d.plotDay); })
      .y(function (d) { return yScale(d.plotCnt); })
      .interpolate("linear");

  var xAxisG = svg.append('g').attr('class', 'x-axis');
  var yAxisG = svg.append('g').attr('class', 'y-axis');
  // var lines  = svg.append('g').attr('class', 'lines').selectAll('g.line');

  // UPDATE
  function update(){

    // if (!dateRange) return;
    if (renderTimeout) {
      _o.$timeout.cancel(renderTimeout);
    }

    renderTimeout = _o.$timeout(
      function () {

        var parseDate = d3.time.format("%x").parse;

        scope.data.forEach(function (d) {
          d.plotDay = parseDate(d.day);
          d.plotCnt = +d.cnt;
        });

        xScale.domain(d3.extent(scope.data, function (d) {
          return d.plotDay;
        }));
        yScale.domain(d3.extent(scope.data, function (d) {
          return d.plotCnt;
        }));

        // RENDER
        // svg.selectAll('*').remove();

        var lines = svg
          .append("path")
          .datum(scope.data)
            .attr("class", "line")
            .attr("d", lineGen);

        lines
          .exit()
          .remove();

        xAxisG.call(xAxis);

        yAxisG.call(yAxis)
          // .append("text")
          //   .attr("transform", "rotate(-90)")
          //   .attr("y", 6)
          //   .attr("dy", ".71em")
          //   .style("text-anchor", "end")
          //   .text("#");

      },
      200
    );

  }

  // REACTIVITY
  // scope.$watch('dateRange', function(newVals, oldVals) {
  //   return update();
  // }, false);

  // scope.$watch('dateRange', function(newVals) {
  //   update();
  // }, false);

  scope.$watch(function() {
    return angular.element(_o.$window)[0].innerWidth;
  }, resize );

  // responsive
  _o.$window.onresize = function() {
    scope.$apply();
  };

  function resize(){
    svg.attr({
      width: _o.width,
      height: _o.height
    });
    xScale.range([ _o.margin.left, _o.width - _o.margin.left ]);
    yScale.range([ _o.height - _o.margin.left , _o.margin.left ]);

    xAxis.tickSize( -_o.height + 2 * _o.margin.left );
    yAxis.tickSize( -_o.width + 2 * _o.margin.left);

    xAxisG.attr('transform', 'translate(' + [0, yScale.range()[0] + 0.5] + ')');
    yAxisG.attr('transform', 'translate(' + [xScale.range()[0], 0] + ')');

    update();
  }

}
示例#14
0
                    // If the y-domain is positive and could reasonably be displayed to include zero, expand it to do so.
                    // (Eg. the range is 5 to 50, do it; if it is 5 to 8, do not. Set the boundary arbitrarily at 5 to 12.5, ie. 1:2.5.)
                    if ((thisYDomain[0] > 0) && (thisYDomain[0] / thisYDomain[1] < 0.4)) {
                        thisYDomain[0] = 0;
                    }
                    // If the y-domain is negative and could reasonably be displayed to include zero, expand it to do so.
                    if ((thisYDomain[1] < 0) && (thisYDomain[0] / thisYDomain[1] < 0.4)) {
                        thisYDomain[1] = 0;
                    }
                }
            }
        }

        let xScale;
        if (domain.x[0] instanceof Date) {
            xScale = d3.time.scale();
        } else {
            xScale = d3.scale.linear();
        }

        xScale.range([0, size.width]).domain(domain.x);

        // The x-axis takes up plot space, if it is at the bottom of the plot (ie. if the y-domain is entirely positive),
        // but not if it is in the middle of the plot (ie. if the y-domain includes zero).
        //
        // For now, we assume that the x-axis will be displayed aligned with the first data series' y-scale.

        const mainYDomain = domain.y[allUnits[0]];
        const yContainsZero = (mainYDomain[0] < 0 && mainYDomain[1] > 0);

        if (yContainsZero) {
示例#15
0
    render: function() {

        var self = this;
        var title = this.props.title;
        var values = _.without(this.props.values,'NULL');
        var period = this.props.period;
        var line, lastValue, gradingValue;
        var labelColor = 'default';

        try {
          gradingValue = Math.round(values[values.length - 1].Value) || 0; // PI value  
        }
        catch (err) {
          gradingValue = 0;
          debug(err);
        }
    

        var tooltipString = 'Performance Indicator';


        // lastValue = values[values.length - 1].Value || 0;
        lastValue = Math.round(values[values.length - 1].Value) || 0;
        tooltipString = 'KPI';
       
        // Color grading of PI label
        if(gradingValue >= 0 && gradingValue <= 2) {
            labelColor = 'danger';
        } else if(gradingValue >= 2 && gradingValue <= 3) {
            labelColor = 'warning';
        } else if(gradingValue >= 3) {
            labelColor = 'success';
        } else {
            labelColor = 'default';
        }


        // D3 configuration
        var margin = {top: 20, right: 20, bottom: 60, left: 50},
            width = 500 - margin.left - margin.right,
            height = 200 - margin.top - margin.bottom;

        var parseDate = d3.time.format("%m/%d/%Y").parse;

        if(period) {
            var startYear = moment('2014').subtract(period, 'years');    
        } else {
            var startYear = moment('2014').subtract(5, 'years');    
        }

        var x = d3.time.scale()
            .range([0, width])
            .domain([startYear.toDate(), moment('12-30-2014').toDate()]).clamp(true);

        var y = d3.scale.linear()
            .range([height, 1]);

        var xAxis = d3.svg.axis()
            .scale(x)
            .tickPadding(12)
            .orient("bottom");

        var yAxis = d3.svg.axis()
            .scale(y)
            .orient("left");

        line = d3.svg.line()
            .defined(function(d) { return d.Value != 'NULL'; })
            .x(function(d) { return x(parseDate(d.Date)); })
            .y(function(d) { return y(Number(d.Value)); });

        d3.select('#'+title.replace(/ /g, '-')).select('svg').remove(); // This should not be necessary, break up d3 into smaller components?


        var svg = d3.select('#'+title).append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
          .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        // y.domain(d3.extent(values, function(d) { return Number(d.Value); }));
        y.domain([1, 10]);    
        
        var xaxis = svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);

        xaxis.selectAll("text")
            .style("text-anchor", "end")
            .attr("dx", "-.8em")
            .attr("dy", ".15em")
            .attr("transform", function(d) {
                return "rotate(-65)";
            });          

        svg.append("g")
          .attr("class", "y axis")
          .call(yAxis)
        .append("text")
          .text('KPI')
          .attr("transform", "rotate(-90)")
          .attr("y", 6)
          .attr("dy", ".71em")
          .style("text-anchor", "end");

        var withoutNull = _.without(values, 'NULL');
        svg.append('circle')
           .attr('class', 'sparkcircle')
           .attr('cx', x(parseDate(withoutNull[withoutNull.length - 1].Date)))
           .attr('cy', y(Number(withoutNull[withoutNull.length - 1].Value)))
           .attr('r', 5); 

        var path = svg.append("path")
          .datum(values)
          .attr("class", "line")
          .attr("d", line);

        path.each(function(d) { d.totalLength = this.getTotalLength(); }) // Add total length per path, needed for animating over full length

        path
          .attr("stroke-dasharray", function(d) { return d.totalLength + " " + d.totalLength; })
          .attr("stroke-dashoffset", function(d) { return d.totalLength; })
          .transition()
            .duration(1000)
            .ease("linear")
            .attr("stroke-dashoffset", 0);


        var bisectDate = d3.bisector(function(d) { return parseDate(d.Date); }).left;

        var focus = svg.append("g")
            .attr("class", "focus")
            .style("display", "none");

        focus.append("circle")
            .attr("r", 4.5);

        focus.append("text")
            .attr("x", 9)
            .attr("dy", ".35em");

        svg.append("rect")
            .attr("class", "overlay")
            .attr("width", width)
            .attr("height", height)
            .on("mouseover", function() { focus.style("display", null); })
            .on("mouseout", function() { focus.style("display", "none"); })
            .on("mousemove", mousemove);

        function mousemove() {
          var x0 = x.invert(d3.mouse(this)[0]),
              i = bisectDate(values, x0, 1),
              d0 = values[i - 1],
              d1 = values[i],
              d = x0 - d0.Date > d1.Date - x0 ? d1 : d0;

          focus.attr("transform", "translate(" + x(parseDate(d.Date)) + "," + y(d.Value) + ")");
          focus.select("text").text(d.Value);
        }
        
        var classesTitlebar = cx({
            'panel-heading': true,
            'rightarrowdiv': self.props.active,
            'active': self.props.active            
        });
        var classesContainer = cx({
            'panel': true,
            'panel-default': true,
            'active': self.props.active
        });

        return (
            <div className={classesContainer} 
                 onClick={this.handleHistoClick}
                 ref="histoRoot">

              <div style={{position:'relative'}} className={classesTitlebar}>
                    <OverlayTrigger placement="right" overlay={<Tooltip><strong>{tooltipString}</strong></Tooltip>}>
                        <Label onClick={this.toggleGraph} style={{float:'right', fontSize:'1.1em', cursor: 'pointer', backgroundColor:Utils.quantize(lastValue)}}>{lastValue}</Label>
                    </OverlayTrigger>&nbsp;
                    <span onClick={this.handleHistoClick} style={{cursor:'pointer', fontWeight:'bold', fontSize:'1.1em'}}>{title}</span>                
              </div>
              <div className="panel-body">
                <div className="histoChart" ref="chart" id={title} />
              </div>
            </div>            
        );
    }
	d3.gantt = function() {
	    var FIT_TIME_DOMAIN_MODE = "fit";
	    var FIXED_TIME_DOMAIN_MODE = "fixed";
	    var DEFAULT_BLOCK_COLOR = "#57c17b";
	    
	    var margin = {
		top : 10,
		right : 65,
		bottom : 40,
		left : 75
	    };

	    var padding = {
		top : 10,
		right : 10,
		bottom : 10,
		left : 10
	    };

	    var timeDomainStart = d3.time.day.offset(new Date(),-3);
	    var timeDomainEnd = d3.time.hour.offset(new Date(),+3);
	    var timeDomainMode = FIT_TIME_DOMAIN_MODE;// fixed or fit
	    var taskTypes = [];
	    var height = document.body.clientHeight - margin.top - margin.bottom-5;
	    var width = document.body.clientWidth - margin.right - margin.left-5;
	    var tooltipFields = [];

	    var tickFormat = "%H:%M";
	    var ticks = 10;

	    var keyFunction = function(d) {
		return d.startDate + d.taskName + d.endDate;
	    };

	    var colorFunction = function (d) {
	    	// Default block color
	    	return DEFAULT_BLOCK_COLOR;
	    };

	    var rectTransform = function(d) {
		return "translate(" + x(d.startDate) + "," + y(d.taskName) + ")";
	    };

	    var x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width ]).clamp(true);

	    var y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);
	    
	    var xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(d3.time.format(tickFormat)).ticks(ticks);

	    var yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);

	    var initTimeDomain = function(tasks) {
			if (timeDomainMode === FIT_TIME_DOMAIN_MODE) {
			    if (tasks === undefined || tasks.length < 1) {
				timeDomainStart = d3.time.day.offset(new Date(), -3);
				timeDomainEnd = d3.time.hour.offset(new Date(), +3);
				return;
			    }
			    tasks.sort(function(a, b) {
				return a.endDate - b.endDate;
			    });
			    timeDomainEnd = tasks[tasks.length - 1].endDate;
			    tasks.sort(function(a, b) {
				return a.startDate - b.startDate;
			    });
			    timeDomainStart = new Date(tasks[0].startDate);
			    timeDomainEnd = new Date();
			}
	    };

	    var tickFormatter = function (val) {
	    	return d3.time.format(tickFormat)(val);
	    }

	    var initAxis = function() {

			x = d3.time.scale().domain([ timeDomainStart, timeDomainEnd ]).range([ 0, width - margin.left - margin.right ]).clamp(true);
			y = d3.scale.ordinal().domain(taskTypes).rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);

			xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(tickFormatter).ticks(ticks);
			yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);
	    };
	    
	    function gantt(tasks, element, timefilter) {

	    	// Set minimal and active size for the chart.
	    	width = element.width() - padding.left - padding.right;
	    	height = element.height() - padding.top - padding.bottom;

	    	width = width < 440 ? 440 : width;
	    	height = height < 150 ? 150 : height;

	    	// Init time domain and axis
			initTimeDomain(tasks);
			initAxis();

			// Empty previous chart draw, the redraw function should be used somewhow instead.
			element.empty();
			// Hide old tooltips if left after page refresh (bugfix)
			$('.gantt-tooltip').css("opacity", 0);

			var brush = d3.svg.brush()
				.x(x)
				.on("brushend", function () {
					var extent = brush.extent();

					var from = moment(extent[0]);
					var to = moment(extent[1]);

					if (to - from === 0) return;

					timefilter.time.from = from;
					timefilter.time.to = to;
					timefilter.time.mode = 'absolute';
				});

			var tooltip = d3.select("body")
				.append("div")   
			    .attr("class", "gantt-tooltip")               
			    .style("opacity", 0);

			var svg = d3.select(element[0])
				.append("svg")
				.attr("class", "chart")
				.attr("width", width)
				.attr("height", height)
				.append("g")
			    .attr("class", "gantt-chart")
				.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
			
			gBrush = svg.append("g")
				.attr("class", "brush")
				.call(brush)
				.selectAll("rect")
				.attr("height", height - margin.top - margin.bottom);

		    svg.selectAll(".chart")
				.data(tasks, keyFunction).enter()
				.append("rect")
				.attr("rx", 5)
				.attr("ry", 5)
				.style("fill", function(d){
			 		return colorFunction(d.color);
			 	})
				.attr("transform", rectTransform)
				.attr("height", function(d) { return y.rangeBand(); })
				.attr("width", function(d) {
					return (x(d.endDate) - x(d.startDate)); 
				})
				.on('mouseover', function (d) {
					tooltip.transition().duration(200).style("opacity", .93);      
				    tooltip.html(TooltipFormatter(d))
				      .style("left", (d3.event.pageX) + "px")     
				      .style("top", (d3.event.pageY) + "px");
				})
				.on('mouseout', function (d) {
					tooltip.transition().duration(300).style("opacity", 0);  
				});
			 
			 
			svg.append("g")
				.attr("class", "x axis")
				.attr("transform", "translate(0, " + (height - margin.top - margin.bottom) + ")")
				.transition()
				.call(xAxis);
			 
			svg.append("g").attr("class", "y axis").transition().call(yAxis);

			return gantt;
	    };

	    var TooltipFormatter = function (dataElement) {
	    	
	    	var html = "<table>";

	    	tooltipFields.forEach(function (elem) {
	    		html += "<tr><td><b>" + elem + "</b></td><td>" + dataElement[elem] + "</td></tr>";
	    	})

	    	return html;
	    }
	   

	    gantt.margin = function(value) {
		if (!arguments.length)
		    return margin;
		margin = value;
		return gantt;
	    };

	    gantt.timeDomain = function(start, end) {
		if ((!start) || (!end))
		    return [ timeDomainStart, timeDomainEnd ];
		timeDomainStart = start, timeDomainEnd = end;
		return gantt;
	    };

	    /**
	     * @param {string}
	     *                vale The value can be "fit" - the domain fits the data or
	     *                "fixed" - fixed domain.
	     */
	    gantt.timeDomainMode = function(value) {
			if (!arguments.length)
		    	return timeDomainMode;
	        timeDomainMode = value;
	        return gantt;

	    };

	    gantt.taskTypes = function(value) {
			if (!arguments.length)
			    return taskTypes;
			taskTypes = value;
			return gantt;
	    };

	    gantt.width = function(value) {
			if (!arguments.length)
			    return width;
			width = +value;
			return gantt;
	    };

	    gantt.height = function(value) {
			if (!arguments.length)
			    return height;
			height = +value;
			return gantt;
	    };

	    gantt.tickFormat = function(value) {
			if (!arguments.length)
			    return tickFormat;
			tickFormat = value;
			return gantt;
	    };

	    gantt.ticks = function(value) {
			if (!arguments.length)
			    return ticks;
			ticks = value;
			return gantt;
	    };

	    // Generates a color for the gantt bars.
	    gantt.colorFunction = function (value) {
	    	colorFunction = value;
	    	return gantt;
	    }

	    // Generates a color for the gantt bars.
	    gantt.tooltipFields = function (value) {
	    	tooltipFields = value;
	    	return gantt;
	    }

	    return gantt;
	};
示例#17
0
define(function(require) {
    var Base = require('util/base'),
        d3 = require('d3'),
        _ = require('underscore');

    // Interval
    return Base.extend({
        defaults: {
            name: '',
            unitWidth: 20,
            unitCellWidth: 19,
            unitFormat: d3.time.format('%-d'),
            unitInterval: d3.time.day,
            groupInterval: d3.time.week
        },

        getUnitBackgroundColor: function(d,i) { },

        getMinGroup: function(data, accessor) { },

        getMaxGroup: function(data, accessor) { },

        setDomain: function(domain) {
            this.set('domain', domain);
        },

        setRange: function(range) {
            this.set('range', range);
        },

        getUnitSteps: function() {
            return this.get('unitInterval').range.apply(undefined, this.get('domain'));
        },

        getGroupSteps: function() {
            return this.get('groupInterval').range.apply(undefined, this.get('domain'));
        },

        getGroupText: function(start) { },

        appendGroupCell: function(sel) { },

        getUnitText: function(date) { },

        getXScale: function() {
            var scale = d3.time.scale()
                .domain(this.get('domain'))
                .rangeRound(this.get('range'));

            return scale;
        },

        getWidthScale: function() {
            var scale = this.getXScale();
            var correct = this.correctDate;

            return function(start, end) {
                return scale(correct(end)) - scale(start);
            }
        },

        // Used to add one day to the input. This is done because a
        // task that ends on a certain day should be shown as filling
        // that whole day on the Gantt chart.
        correctDate: function(arg) {
            var inner = function(date) {
                return d3.time.day.offset(date, 1);
            };
            if (_.isFunction(arg))
                return _.compose(inner, arg);
            else
                return inner(arg);
        }
    });
});
d3.csv("./src/stores/ndx.csv", function(error, data) {
        var dateFormat = d3.time.format("%m/%d/%Y");
        data.forEach(function (d) {
            d.dd = dateFormat.parse(d.date);
            d.month = d3.time.month(d.dd).getMonth(); // pre-calculate month for better performance
            d.year = d3.time.year(d.dd).getFullYear();
            d.close = +d.close; // coerce to number
            d.open = +d.open;
        });
        var ndx = crossfilter(data),
            monthOfTheYearDimension = ndx.dimension(function(d) { return [+d.month, +d.year]; }),
            percentageGainByMonthOfYearGroup = monthOfTheYearDimension.group().reduce(
                /* callback for when data is added to the current filter results */
                function (p, v) {
                    ++p.count;
                    p.absGain += v.close - v.open;
                    p.fluctuation += Math.abs(v.close - v.open);
                    p.sumIndex += (v.open + v.close) / 2;
                    p.avgIndex = p.count ? p.sumIndex / p.count : 0;
                    p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
                    p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
                    return p;
                },
                /* callback for when data is removed from the current filter results */
                function (p, v) {
                    --p.count;
                    p.absGain -= v.close - v.open;
                    p.fluctuation -= Math.abs(v.close - v.open);
                    p.sumIndex -= (v.open + v.close) / 2;
                    p.avgIndex = p.count ? p.sumIndex / p.count : 0;
                    p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
                    p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
                    return p;
                },
                /* initialize p */
                function () {
                    return {count: 0, absGain: 0, fluctuation: 0, fluctuationPercentage: 0, sumIndex: 0, avgIndex: 0, percentageGain: 0};
                }
            );
        var heatColorMapping = function(d) {
            if (d < 0) {
                return d3.scale.linear().domain([-23,0]).range(["red", "#e5e5e5"])(d);
            }
            else {
                return d3.scale.linear().domain([0,23]).range(["#e5e5e5", "green"])(d);
            }
        };
        heatColorMapping.domain = function() {
            return [-23,23];
        };
        heatmapChart
                .width(12 * 80 + 80)
                .height(27 * 10 + 40)
                .dimension(monthOfTheYearDimension)
                .group(percentageGainByMonthOfYearGroup)
                .keyAccessor(function(d) { return +d.key[0]; })
                .valueAccessor(function(d) { return +d.key[1]; })
                .colorAccessor(function(d) { return +d.value.percentageGain; })
                .title(function(d) {
                    return " Month:   " + d.key[0] + "\n" +
                           "  Year:   " + d.key[1] + "\n" +
                           "  Gain:   " + d.value.percentageGain + "%";})
                .colors(heatColorMapping)
                .calculateColorDomain();
        heatmapChart.xBorderRadius(0);
        heatmapChart.yBorderRadius(0);
        heatmapChart.render();
        var monthlyDimension = ndx.dimension(function (d) { return +d.month; });
        var percentageGainByMonthArrayGroup = monthlyDimension.group().reduce(
            function(p,v) {
                var absGain = v.close - v.open;
                var percentageGain = v.open ? (absGain / v.open) * 100 : 0;
                return p + percentageGain;
            },
            function(p,v) {
                var absGain = v.close - v.open;
                var percentageGain = v.open ? (absGain / v.open) * 100 : 0;
                return p - percentageGain;
            },
            function() {
                return 0;
            }
        );
        barChart
                .dimension(monthlyDimension)
                .group(percentageGainByMonthArrayGroup)
                .width(12 * 80 + 80)
                .height(480)
                .y(d3.scale.linear().domain([-10.0,100.0]))
                .x(d3.scale.linear().domain([-0.5,11.5]))
                .elasticY(true)
                .centerBar(true);
        barChart.render();
    });
示例#19
0
 data = data.map(function(d) {
     var fff = d3.time.format(time_format);
     d[x_accessor] = fff.parse(d[x_accessor]);
     return d;
 });
示例#20
0
 This source code is available under agreement available at
 https://github.com/Talend/data-prep/blob/master/LICENSE

 You should have received a copy of the agreement
 along with this program; if not, write to Talend SA
 9 rue Pages 92150 Suresnes, France

 ============================================================================*/

import d3 from 'd3';

const DATE_FORMAT = 'YYYY-MM-DD';
const D3_DATE_FORMAT = '%Y-%m-%d';
const D3_NUMBER_DECIMAL = ',';
const d3DateFormatter = d3.time.format(D3_DATE_FORMAT);
const d3NumberFormatter = d3.format(D3_NUMBER_DECIMAL);

const numberFormatter = {
	format: value => d3NumberFormatter(value),
};
const dateFormatter = {
	parse: string => d3DateFormatter.parse(string),
	format: timestamp => d3DateFormatter(new Date(timestamp)),
};

/**
 * Checks max interval >= the max of data values and  if the min interval < max interval
 */
function adaptSelection(selection) {
	return {
示例#21
0
	},
	render() {
		return (
			<svg className='stat-chart' ref='chart'></svg>
		);
	},
	_update() {
		var width = 960;
		var height = 500;
		var padding = 100;

		var svg = d3.select('svg')
		.attr('width', width)
		.attr('height', height)

		var x = d3.time.scale()
			.range([0, width-padding*2]);

		var y = d3.scale.linear()
			.range([height-padding*2, 0]);

		x.domain(d3.extent(this.props.data, (d) => d['last-updated'] * 1000 ));
		y.domain([0,d3.max(this.props.data, (d) => d.toughness)]);

		var line = d3.svg.line()
			.x((d) => x(d['last-updated'] * 1000))
			.y((d) => y(d.damage));

		var toughnessLine = d3.svg.line()
			.x((d) => x(d['last-updated'] * 1000))
			.y((d) => y(d.toughness));
示例#22
0
import d3 from 'd3';

const timeFormat = d3.time.format('%Y-%m-%dT%H:%M:%SZ');
let totalDistanceByMonth = {};

function parseDate(date) {
  return timeFormat.parse(date);
}

function generateTotalsData() {
  const data = Object.keys(totalDistanceByMonth).map(function(k) {
    return {
      month: totalDistanceByMonth[k].month,
      distance: totalDistanceByMonth[k].distance
    };
  });
  console.log('totalsData', data);
  return data.reverse();
}

export default function(element) {

  var margin = {top: 50, right: 80, bottom: 100, left: 80},
    width = (window.innerWidth * 0.9) - margin.left - margin.right,
    height = (window.innerHeight * 0.9) - margin.top - margin.bottom;

  var x = d3.time.scale()
    .range([0, width]);

  var y = d3.scale.linear()
    .range([height, 0]);
示例#23
0
 chart.xAxis.tickFormat(function(d) {
     return d3.time.format(dateFormat)(new Date(d));
 });
示例#24
0
export default function(element) {

  var margin = {top: 50, right: 80, bottom: 100, left: 80},
    width = (window.innerWidth * 0.9) - margin.left - margin.right,
    height = (window.innerHeight * 0.9) - margin.top - margin.bottom;

  var x = d3.time.scale()
    .range([0, width]);

  var y = d3.scale.linear()
    .range([height, 0]);

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient('bottom')
    .ticks(d3.time.months)
    .tickSize(16, 0)
    .tickFormat(d3.time.format('%B'));

  var yAxis = d3.svg.axis()
    .scale(y)
    .tickFormat((d) => {return (d / 1000) + ' km';})
    .orient('left');

  var svg = d3.select(element).append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

  d3.json('activities.json', function(error, data) {

    if (error) throw error;

    data.forEach(function (d) {
      d.start_date = parseDate(d.start_date);
      d.month = d3.time.month(d.start_date);
      if (!totalDistanceByMonth[d.month]) {
        totalDistanceByMonth[d.month] = {
          month: d.month,
          distance: 0
        };
      }
      totalDistanceByMonth[d.month] = {
        month: d.month,
        distance: totalDistanceByMonth[d.month].distance + d.distance
      };
    });

    const totalsData = generateTotalsData();

    const firstMonth = new Date(totalsData[0].month);
    const lastMonth = new Date(totalsData[totalsData.length - 1].month);
    const lastMonthPlusOne = new Date(lastMonth);
    lastMonthPlusOne.setMonth(lastMonth.getMonth() + 1);

    let highestMonthTotal = 0;

    totalsData.forEach(data => {
      highestMonthTotal = Math.max(highestMonthTotal, data.distance);
    });

    x.domain([firstMonth, lastMonth]);
    y.domain([0, highestMonthTotal + 1000]);

    svg.append('g')
      .attr('class', 'x axis')
      .attr('transform', 'translate(0,' + height + ')')
      .call(xAxis);

    svg.append('g')
      .attr('class', 'y axis')
      .call(yAxis)
      .append('text')
      .attr('transform', 'rotate(-90)')
      .attr('y', 6)
      .attr('dy', '.71em')
      .style('text-anchor', 'end');

    svg.selectAll(".bar")
      .data(totalsData)
      .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.month); })
      .attr("width", width / (totalsData.length + 1))
      .attr("y", function(d) { return y(d.distance); })
      .attr("height", function(d) { return height - y(d.distance); });

  });

}
示例#25
0
//  ,'235_50'
//  ,'236_50'


// handy

var ts_regex=/(\d*-\d*-\d*)\s(\d*:\d*)/
function parseDate(d) {
    var match = ts_regex.exec(d)
    var canonical_date = [match[1],'T',match[2],':00-0800'].join('')
    return new Date(canonical_date)
}
// Various formatters.
var formatNumber = d3.format(",d"),
    formatChange = d3.format("+,d"),
    formatDate = d3.time.format("%B %d, %Y"),
    formatMonth = //d3.time.format("%B"),
function(d){
    return month_abbrev[d]
},

    formatDOW = //d3.time.format("%a"),
function(d){
    return dow[d]
},
    formatTime = d3.time.format("%I:%M %p");

var formatTOD = function(d){
    if (d===0) return '12:00 AM'
    if (d<12) return d+':00 AM'
    if (d===12) return '12:00 PM'
示例#26
0
/* global updateTime, forecast */

var d3 = require('d3');
d3.sankey = require('./sankey/d3plugin.js');
var debounce = require('lodash/function/debounce');
var parties = require('uk-political-parties');

var sankeyData = require('./sankey/sankey-data.js');
var logo = require('./sankey/logo.js');


var nodeWidth = 30;
var updateString = '';

var timeFormat = d3.time.format("%B %e, %Y %I:%M %p");
updateString = timeFormat(updateTime);
var data = sankeyData(forecast);

var partyColour = parties.converter('colour','electionForecast');
var partyShortName = parties.converter('short','electionForecast');

var resize = debounce(function(e) {
  drawSankey(data);
}, 200);
window.addEventListener("resize", resize, false);
drawSankey(data);
d3.select('#svg-source').text( 'Source: electionforecast.co.uk, ' + updateString );

d3.selectAll('.chart-preset').on('click',function(){
  clearSelections();
示例#27
0
 .key(function(d) { return d3.time.day(d.date); });
示例#28
0
Utils.cloneMe = function(obj) {
	if(obj == null || typeof(obj) !== 'object')
		return obj;
	if (obj instanceof Date) {
		return new Date(obj.getTime());
	}
	var temp = {};//obj.constructor(); // changed

	for(var key in obj) {
		if(obj.hasOwnProperty(key)) {
			temp[key] = Utils.cloneMe(obj[key]);
		}
	}
	return temp;
}
Utils.displayDateFormat = d3.time.format("%Y-%m-%d");
Utils.displayNumberFormat = function(x) {
	return Utils.numberWithCommas(x.toFixed(2));
};
Utils.numberWithCommas = function(x) {
	return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
Utils.isNumeric = function(n) {
	return !isNaN(parseFloat(n)) && isFinite(n);
};
Utils.mousePosition = function(e) {
	var container = e.currentTarget,
		rect = container.getBoundingClientRect(),
		x = e.clientX - rect.left - container.clientLeft,
		y = e.clientY - rect.top - container.clientTop,
		xy = [ Math.round(x), Math.round(y) ];
    setData: function (data) {
        var startDate, endDate, date, dayFilter, parse;

        // Does the loading indicator needs to be displayed?
        if (data.loading) {
            if (!this._data.loading) {
                this._data = data;
                this.emitChangeData();
            }

            // Do nothing when loading is in progress
            return;
        }

        // Store errors for later usage
        if (data.error) {
            requestErrors.push(data);
        }
        else {
            parse = d3.time.format("%Y-%m-%d %H:%M").parse; // Date formatting parser
            startDate = DateUtils.parseDate(this.getRestFilter(START_DATE_FILTER));
            endDate = DateUtils.parseDate(this.getRestFilter(END_DATE_FILTER));
            date = DateUtils.parseDate(this.getRestFilter(CURRENT_DATE_FILTER));

            if (date.getFullYear() == startDate.getFullYear() && date.getMonth() == startDate.getMonth()) {
                dayFilter = startDate.getDate();
                data.data = data.data.filter(function (row) {
                    return DateUtils.parseDate(row.date, true).getDate() >= dayFilter
                });
            }

            if (date.getFullYear() == endDate.getFullYear() && date.getMonth() == endDate.getMonth()) {
                dayFilter = endDate.getDate();
                data.data = data.data.filter(function (row) {
                    return DateUtils.parseDate(row.date, true).getDate() <= dayFilter
                });
            }

            // Parse dates and numbers.
            data.data.forEach(function (d) {
                d.date = parse(d.date);
                d.flows = +d.flows;
            });

            // Sort the data by date ASC
            data.data.sort(function (a, b) {
                return a.date - b.date;
            });

            this._data.data.push(data.data);
        }

        this._data.loading = requestQueue.length > 0;

        if (!this._data.loading) {
            if (this._data.data.length==0) {
                // Broadcast first found error
                this._data = requestErrors[0];
            }
            this.emitChangeData();
        }
        else {
            setTimeout(this.dequeue.bind(this), 1);
        }
    }
示例#30
0
function timeScale(model, options, orientation) {
    var range = getRange(model, orientation);
    return d3.time.scale()
        .domain(model.independentDomain)
        .range(range);
}