├── .gitignore ├── js ├── comparisonGeometricZoom.js ├── comparisonSemanticZoom.js ├── components │ ├── sl.js │ ├── ohlcBar.js │ ├── ohlcBarSeries.js │ ├── gridlines.js │ ├── candlestickSeries.js │ ├── ohlcSeries.js │ └── comparisonSeries.js ├── require.config.js ├── utils │ ├── yScaleTransform.js │ └── tickWidth.js ├── ohlcSemanticZoom.js ├── filteredData.js ├── candlestick.js ├── ohlc.js ├── MockData.js ├── zoomChart.js ├── ohlcGeometricZoom.js ├── comparisonChart.js ├── lib │ ├── modernizr.js │ ├── moment-range.js │ └── require.js └── transformDiv.js ├── ohlc.html ├── candlestick.html ├── filteredData.html ├── transformDiv.html ├── ohlcSemanticZoom.html ├── ohlcGeometricZoom.html ├── comparisonGeometricZoom.html ├── comparisonSemanticZoom.html ├── README.md ├── index.html └── style └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | node_modules -------------------------------------------------------------------------------- /js/comparisonGeometricZoom.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'comparisonChart' 3 | ], function (comparisonChart) { 4 | 'use strict'; 5 | comparisonChart("Geometric"); 6 | }); -------------------------------------------------------------------------------- /js/comparisonSemanticZoom.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'comparisonChart' 3 | ], function (comparisonChart) { 4 | 'use strict'; 5 | comparisonChart("Semantic"); 6 | }); -------------------------------------------------------------------------------- /js/components/sl.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 'use strict'; 3 | 4 | return { 5 | version: '0.0.0', 6 | series: {}, 7 | svg: {}, 8 | example: {} 9 | }; 10 | }); -------------------------------------------------------------------------------- /ohlc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /candlestick.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /filteredData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /transformDiv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /ohlcSemanticZoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /ohlcGeometricZoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /comparisonGeometricZoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /comparisonSemanticZoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /js/require.config.js: -------------------------------------------------------------------------------- 1 | /* jshint unused:false */ 2 | /* global require: true */ 3 | 4 | var require = { 5 | paths: { 6 | 'd3': 'lib/d3', 7 | 'jstat': 'lib/jstat', 8 | 'moment': 'lib/moment', 9 | 'moment-range': 'lib/moment-range', 10 | 'modernizr': 'lib/modernizr' 11 | }, 12 | shim: { 13 | 'modernizr': { 14 | exports: 'Modernizr' 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | #### Looking for [d3fc](https://d3fc.io)? 3 | 4 |
5 | 6 | Chart examples for Scott Logic blog posts on D3 7 | * [An OHLC Chart Component for D3](http://www.scottlogic.com/blog/2014/08/19/an-ohlc-chart-component-for-d3.html) 8 | * [D3 SVG chart performance](http://www.scottlogic.com/blog/2014/09/19/d3-svg-chart-performance.html) 9 | * [An Interactive Stock Comparison Chart with D3 ](http://www.scottlogic.com/blog/2014/09/26/an-interactive-stock-comparison-chart-with-d3.html) 10 | -------------------------------------------------------------------------------- /js/utils/yScaleTransform.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3' 3 | ], function () { 4 | return function (oldScale, newScale) { 5 | var oldDomain = oldScale.domain(), 6 | newDomain = newScale.domain(), 7 | scale = (oldDomain[1] - oldDomain[0]) / (newDomain[1] - newDomain[0]), 8 | translate = scale * (oldScale.range()[1] - oldScale(newDomain[1])); 9 | 10 | return { 11 | translate: translate, 12 | scale: scale 13 | }; 14 | }; 15 | }); -------------------------------------------------------------------------------- /js/utils/tickWidth.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'moment', 3 | 'moment-range' 4 | ], function (moment) { 5 | 'use strict'; 6 | 7 | var countDays = function (range) { 8 | return range / (1000 * 60 * 60 * 24); 9 | }; 10 | 11 | return function calculateTickWidth(scale, fromDate, toDate) { 12 | var scaleRange = scale.range(), 13 | dayRange = moment().range(fromDate, toDate); 14 | return (scaleRange[1] - scaleRange[0]) / (countDays(dayRange) * 2.5); 15 | }; 16 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | D3.js Charts 5 | 6 | 7 | 17 | 18 | -------------------------------------------------------------------------------- /style/style.css: -------------------------------------------------------------------------------- 1 | html * { 2 | box-sizing: border-box; 3 | } 4 | 5 | #chart { 6 | width: 1280px; 7 | position: relative; 8 | } 9 | 10 | .chart .axis text { 11 | font: 10px sans-serif; 12 | } 13 | 14 | .chart .axis .domain, 15 | .chart .axis .tick line { 16 | fill: none; 17 | stroke: black; 18 | shape-rendering: crispEdges; 19 | } 20 | 21 | .chart .gridlines { 22 | stroke: lightgrey; 23 | shape-rendering: crispEdges; 24 | stroke-width: 1; 25 | } 26 | 27 | .chart .ohlc-series path { 28 | vector-effect: non-scaling-stroke; 29 | stroke-width: 1.5; 30 | } 31 | 32 | .chart .ohlc-series .up-days { 33 | stroke: green; 34 | } 35 | 36 | .chart .ohlc-series .down-days { 37 | stroke: red; 38 | } 39 | 40 | .chart .ohlc-series .bar.up-day { 41 | stroke: green; 42 | } 43 | 44 | .chart .ohlc-series .bar.down-day { 45 | stroke: red; 46 | } 47 | 48 | .chart .candlestick-series .bar path { 49 | stroke: black; 50 | } 51 | 52 | .chart .comparison-series .line { 53 | fill: none; 54 | stroke: steelblue; 55 | stroke-width: 1.5px; 56 | vector-effect: non-scaling-stroke; 57 | } 58 | 59 | .chart .bar.up-day rect { 60 | fill: green; 61 | } 62 | 63 | .chart .bar.down-day rect { 64 | fill: red; 65 | } 66 | 67 | .chart .zoom-pane { 68 | fill: none; 69 | pointer-events: all; 70 | cursor: -webkit-grab; 71 | cursor: -moz-grab; 72 | } -------------------------------------------------------------------------------- /js/ohlcSemanticZoom.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'utils/tickWidth', 6 | 'moment', 7 | 'components/ohlcSeries', 8 | 'zoomChart' 9 | ], function (d3, sl, MockData, tickWidth, moment) { 10 | 'use strict'; 11 | 12 | var mockData = new MockData(0, 0.1, 100, 50, function (moment) { 13 | return !(moment.day() === 0 || moment.day() === 6); 14 | }); 15 | 16 | var fromDate = new Date(2013, 8, 1), 17 | toDate = new Date(2014, 8, 1); 18 | 19 | var data = mockData.generateOHLC(fromDate, toDate); 20 | 21 | var xScale = d3.time.scale(), 22 | yScale = d3.scale.linear(); 23 | 24 | var xAxis = d3.svg.axis() 25 | .scale(xScale) 26 | .orient('bottom') 27 | .ticks(5); 28 | 29 | var yAxis = d3.svg.axis() 30 | .scale(yScale) 31 | .orient('left'); 32 | 33 | var series = sl.series.ohlc() 34 | .xScale(xScale) 35 | .yScale(yScale); 36 | 37 | var zoom = d3.behavior.zoom() 38 | .x(xScale) 39 | .scaleExtent([0.5, 50]) 40 | .on('zoom', zoomed) 41 | .on('zoomend', zoomend); 42 | 43 | var zoomChart = sl.example.zoomChart(zoom, data, series, xScale, yScale, xAxis, yAxis, fromDate, toDate); 44 | zoomChart(); 45 | 46 | function zoomed() { 47 | var xDomain = xScale.domain(); 48 | var range = moment().range(xDomain[0], xDomain[1]); 49 | var filteredData = []; 50 | var g = d3.selectAll('svg').select('g'); 51 | 52 | for (var i = 0; i < data.length; i += 1) { 53 | if (range.contains(data[i].date)) { 54 | filteredData.push(data[i]); 55 | } 56 | } 57 | yScale.domain( 58 | [ 59 | d3.min(filteredData, function (d) { 60 | return d.low; 61 | }), 62 | d3.max(filteredData, function (d) { 63 | return d.high; 64 | }) 65 | ] 66 | ); 67 | 68 | g.select('.x.axis') 69 | .call(xAxis); 70 | 71 | g.select('.y.axis') 72 | .call(yAxis); 73 | 74 | series.tickWidth(tickWidth(xScale, xDomain[0], xDomain[1])); 75 | 76 | g.select('.series') 77 | .call(series); 78 | } 79 | 80 | function zoomend() { 81 | } 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /js/filteredData.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'utils/tickWidth', 6 | 'moment', 7 | 'moment-range', 8 | 'components/ohlcBarSeries', 9 | 'modernizr', 10 | 'zoomChart' 11 | ], function (d3, sl, MockData, tickWidth, moment) { 12 | 'use strict'; 13 | 14 | var mockData = new MockData(0, 0.1, 100, 50, function (moment) { 15 | return !(moment.day() === 0 || moment.day() === 6); 16 | }); 17 | 18 | var fromDate = new Date(2011, 8, 1), 19 | toDate = new Date(2014, 8, 1); 20 | 21 | var data = mockData.generateOHLC(fromDate, toDate); 22 | 23 | var xScale = d3.time.scale(), 24 | yScale = d3.scale.linear(); 25 | 26 | var xAxis = d3.svg.axis() 27 | .scale(xScale) 28 | .orient('bottom') 29 | .ticks(5); 30 | 31 | var yAxis = d3.svg.axis() 32 | .scale(yScale) 33 | .orient('left'); 34 | 35 | var series = sl.series.ohlcBar() 36 | .xScale(xScale) 37 | .yScale(yScale); 38 | 39 | var zoom = d3.behavior.zoom() 40 | .x(xScale) 41 | .scaleExtent([0.5, 500]) 42 | .on('zoom', zoomed) 43 | .on('zoomend', zoomend); 44 | 45 | var zoomChart = sl.example.zoomChart(zoom, data, series, xScale, yScale, xAxis, yAxis, fromDate, toDate); 46 | zoomChart(); 47 | 48 | function zoomed() { 49 | var xDomain = xScale.domain(); 50 | var range = moment().range(xDomain[0], xDomain[1]); 51 | var filteredData = []; 52 | var g = d3.selectAll('svg').select('g'); 53 | 54 | for (var i = 0; i < data.length; i += 1) { 55 | if (range.contains(data[i].date)) { 56 | filteredData.push(data[i]); 57 | } 58 | } 59 | 60 | yScale.domain( 61 | [ 62 | d3.min(filteredData, function (d) { 63 | return d.low; 64 | }), 65 | d3.max(filteredData, function (d) { 66 | return d.high; 67 | }) 68 | ] 69 | ); 70 | 71 | g.select('.x.axis') 72 | .call(xAxis); 73 | 74 | g.select('.y.axis') 75 | .call(yAxis); 76 | 77 | series.tickWidth(tickWidth(xScale, xDomain[0], xDomain[1])); 78 | 79 | g.select('.series') 80 | .datum(filteredData) 81 | .call(series); 82 | } 83 | 84 | function zoomend() { 85 | } 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /js/components/ohlcBar.js: -------------------------------------------------------------------------------- 1 | define ([ 2 | 'd3', 3 | 'components/sl' 4 | ], function (d3, sl) { 5 | 'use strict'; 6 | 7 | sl.svg.ohlcBar = function () { 8 | 9 | var open = function (d) { 10 | return d.open; 11 | }, 12 | high = function (d) { 13 | return d.high; 14 | }, 15 | low = function (d) { 16 | return d.low; 17 | }, 18 | close = function (d) { 19 | return d.close; 20 | }, 21 | date = function (d) { 22 | return d.date; 23 | }; 24 | 25 | var tickWidth = 5; 26 | 27 | var ohlcBar = function (d) { 28 | // return the path 29 | var moveToLow = 'M' + date(d) + ',' + low(d), 30 | verticalToHigh = 'V' + high(d), 31 | openTick = 'M' + date(d) + "," + open(d) + 'h' + (-tickWidth), 32 | closeTick = 'M' + date(d) + "," + close(d) + 'h' + tickWidth; 33 | 34 | return moveToLow + verticalToHigh + openTick + closeTick; 35 | }; 36 | 37 | ohlcBar.tickWidth = function (value) { 38 | if (!arguments.length) { 39 | return tickWidth; 40 | } 41 | tickWidth = value; 42 | return ohlcBar; 43 | }; 44 | 45 | ohlcBar.open = function (value) { 46 | if (!arguments.length) { 47 | return open; 48 | } 49 | open = value; 50 | return ohlcBar; 51 | }; 52 | 53 | ohlcBar.high = function (value) { 54 | if (!arguments.length) { 55 | return high; 56 | } 57 | high = value; 58 | return ohlcBar; 59 | }; 60 | 61 | ohlcBar.low = function (value) { 62 | if (!arguments.length) { 63 | return low; 64 | } 65 | low = value; 66 | return ohlcBar; 67 | }; 68 | 69 | ohlcBar.close = function (value) { 70 | if (!arguments.length) { 71 | return close; 72 | } 73 | close = value; 74 | return ohlcBar; 75 | }; 76 | 77 | ohlcBar.date = function (value) { 78 | if (!arguments.length) { 79 | return date; 80 | } 81 | date = value; 82 | return ohlcBar; 83 | }; 84 | 85 | return ohlcBar; 86 | 87 | }; 88 | }); -------------------------------------------------------------------------------- /js/candlestick.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'components/candlestickSeries' 6 | ], function (d3, sl, MockData) { 7 | 'use strict'; 8 | 9 | var mockData = new MockData(0.1, 0.1, 100, 50, function (moment) { 10 | return !(moment.day() === 0 || moment.day() === 6); 11 | }); 12 | 13 | var data = mockData.generateOHLC(new Date(2014, 6, 1), new Date(2014, 8, 1)); 14 | 15 | var margin = {top: 20, right: 20, bottom: 30, left: 50}, 16 | width = 660 - margin.left - margin.right, 17 | height = 400 - margin.top - margin.bottom; 18 | 19 | var xScale = d3.time.scale(), 20 | yScale = d3.scale.linear(); 21 | 22 | var xAxis = d3.svg.axis() 23 | .scale(xScale) 24 | .orient('bottom') 25 | .ticks(5); 26 | 27 | var yAxis = d3.svg.axis() 28 | .scale(yScale) 29 | .orient('left'); 30 | 31 | var series = sl.series.candlestick() 32 | .xScale(xScale) 33 | .yScale(yScale); 34 | 35 | // Create svg element 36 | var svg = d3.select('#chart').classed('chart', true).append('svg') 37 | .attr('width', width + margin.left + margin.right) 38 | .attr('height', height + margin.top + margin.bottom); 39 | 40 | // Ceate chart 41 | var g = svg.append('g') 42 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 43 | 44 | var plotArea = g.append('g'); 45 | plotArea.append('clipPath') 46 | .attr('id', 'plotAreaClip') 47 | .append('rect') 48 | .attr({ width: width, height: height }); 49 | plotArea.attr('clip-path', 'url(#plotAreaClip)'); 50 | 51 | // Set scale domains 52 | var maxDate = d3.max(data, function (d) { 53 | return d.date; 54 | }); 55 | 56 | xScale.domain([ 57 | new Date(maxDate.getTime() - (8.64e7 * 31.5)), 58 | new Date(maxDate.getTime() + 8.64e7) 59 | ]); 60 | 61 | yScale.domain( 62 | [ 63 | d3.min(data, function (d) { 64 | return d.low; 65 | }), 66 | d3.max(data, function (d) { 67 | return d.high; 68 | }) 69 | ] 70 | ).nice(); 71 | 72 | // Set scale ranges 73 | xScale.range([0, width]); 74 | yScale.range([height, 0]); 75 | 76 | // Draw axes 77 | g.append('g') 78 | .attr('class', 'x axis') 79 | .attr('transform', 'translate(0,' + height + ')') 80 | .call(xAxis); 81 | 82 | g.append('g') 83 | .attr('class', 'y axis') 84 | .call(yAxis); 85 | 86 | // Draw series. 87 | plotArea.append('g') 88 | .attr('class', 'series') 89 | .datum(data) 90 | .call(series); 91 | }); -------------------------------------------------------------------------------- /js/ohlc.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'components/ohlcSeries' 6 | ], function (d3, sl, MockData) { 7 | 'use strict'; 8 | 9 | var mockData = new MockData(0.1, 0.1, 100, 50, function (moment) { 10 | return !(moment.day() === 0 || moment.day() === 6); 11 | }); 12 | 13 | var data = mockData.generateOHLC(new Date(2014, 6, 1), new Date(2014, 8, 1)); 14 | 15 | var margin = {top: 20, right: 20, bottom: 30, left: 50}, 16 | width = 660 - margin.left - margin.right, 17 | height = 400 - margin.top - margin.bottom; 18 | 19 | var xScale = d3.time.scale(), 20 | yScale = d3.scale.linear(); 21 | 22 | var xAxis = d3.svg.axis() 23 | .scale(xScale) 24 | .orient('bottom') 25 | .ticks(5); 26 | 27 | var yAxis = d3.svg.axis() 28 | .scale(yScale) 29 | .orient('left'); 30 | 31 | var series = sl.series.ohlc() 32 | .xScale(xScale) 33 | .yScale(yScale); 34 | 35 | // Create svg element 36 | var svg = d3.select('#chart').classed('chart', true).append('svg') 37 | .attr('width', width + margin.left + margin.right) 38 | .attr('height', height + margin.top + margin.bottom); 39 | 40 | // Ceate chart 41 | var g = svg.append('g') 42 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 43 | 44 | // Create plot area 45 | var plotArea = g.append('g'); 46 | plotArea.append('clipPath') 47 | .attr('id', 'plotAreaClip') 48 | .append('rect') 49 | .attr({ width: width, height: height }); 50 | plotArea.attr('clip-path', 'url(#plotAreaClip)'); 51 | 52 | // Set scale domains 53 | var maxDate = d3.max(data, function (d) { 54 | return d.date; 55 | }); 56 | 57 | xScale.domain([ 58 | new Date(maxDate.getTime() - (8.64e7 * 31.5)), 59 | new Date(maxDate.getTime() + 8.64e7) 60 | ]); 61 | 62 | yScale.domain( 63 | [ 64 | d3.min(data, function (d) { 65 | return d.low; 66 | }), 67 | d3.max(data, function (d) { 68 | return d.high; 69 | }) 70 | ] 71 | ).nice(); 72 | 73 | // Set scale ranges 74 | xScale.range([0, width]); 75 | yScale.range([height, 0]); 76 | 77 | // Draw axes 78 | g.append('g') 79 | .attr('class', 'x axis') 80 | .attr('transform', 'translate(0,' + height + ')') 81 | .call(xAxis); 82 | 83 | g.append('g') 84 | .attr('class', 'y axis') 85 | .call(yAxis); 86 | 87 | // Draw series. 88 | plotArea.append('g') 89 | .attr('class', 'series') 90 | .datum(data) 91 | .call(series); 92 | }); -------------------------------------------------------------------------------- /js/MockData.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'moment', 3 | 'moment-range', 4 | 'jstat' 5 | ], function (moment) { 6 | 'use strict'; 7 | var MockData; 8 | 9 | MockData = function MockData(mu, sigma, startingPrice, intraDaySteps, filter) { 10 | this.mu = mu; 11 | this.sigma = sigma; 12 | this.startingPrice = startingPrice; 13 | this.intraDaySteps = intraDaySteps; 14 | this.filter = filter; 15 | }; 16 | 17 | MockData.prototype = { 18 | 19 | generateOHLC: function (fromDate, toDate) { 20 | var range = moment().range(fromDate, toDate), 21 | rangeYears = range / 3.15569e10, 22 | daysIncluded = 0, 23 | steps, 24 | prices, 25 | ohlc = [], 26 | daySteps, 27 | currentStep = 0, 28 | self = this; 29 | 30 | range.by('days', function (moment) { 31 | if (self.filter(moment)) { 32 | daysIncluded += 1; 33 | } 34 | }); 35 | 36 | steps = daysIncluded * this.intraDaySteps; 37 | prices = this.generatePrices(rangeYears, steps); 38 | 39 | range.by('days', function (moment) { 40 | if (self.filter(moment)) { 41 | daySteps = prices.slice(currentStep, currentStep += self.intraDaySteps); 42 | ohlc.push({ 43 | date: moment.toDate(), 44 | open: daySteps[0], 45 | high: Math.max.apply({}, daySteps), 46 | low: Math.min.apply({}, daySteps), 47 | close: daySteps[self.intraDaySteps - 1] 48 | }) 49 | } 50 | }); 51 | 52 | return ohlc; 53 | }, 54 | 55 | generatePrices: function (period, steps) { 56 | var increments = this._generateIncrements(period, steps), 57 | i, prices = []; 58 | 59 | prices[0] = this.startingPrice; 60 | for (i = 1; i < increments.length; i += 1) { 61 | prices[i] = prices[i - 1] * increments[i]; 62 | } 63 | return prices; 64 | }, 65 | 66 | _generateIncrements: function (period, steps) { 67 | // Geometric Brownian motion model. 68 | var deltaY = period / steps, 69 | sqrtDeltaY = Math.sqrt(deltaY), 70 | deltaW = jStat().randn(1, steps).multiply(sqrtDeltaY), 71 | increments = deltaW 72 | .multiply(this.sigma) 73 | .add((this.mu - ((this.sigma * this.sigma) / 2)) * deltaY), 74 | expIncrements = increments.map(function (x) { 75 | return Math.exp(x); 76 | }); 77 | 78 | return jStat.row(expIncrements, 0); 79 | } 80 | }; 81 | 82 | return MockData; 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /js/components/ohlcBarSeries.js: -------------------------------------------------------------------------------- 1 | define ([ 2 | 'd3', 3 | 'components/sl', 4 | 'components/ohlcBar' 5 | ], function (d3, sl) { 6 | 'use strict'; 7 | 8 | sl.series.ohlcBar = function () { 9 | 10 | var xScale = d3.time.scale(), 11 | yScale = d3.scale.linear(); 12 | 13 | var tickWidth = 5; 14 | 15 | var isUpDay = function(d) { 16 | return d.close > d.open; 17 | }; 18 | var isDownDay = function (d) { 19 | return !isUpDay(d); 20 | }; 21 | 22 | var ohlcBar = sl.svg.ohlcBar() 23 | .open(function (d) { 24 | return yScale(d.open); 25 | }) 26 | .high(function (d) { 27 | return yScale(d.high); 28 | }) 29 | .low(function (d) { 30 | return yScale(d.low); 31 | }) 32 | .close(function (d) { 33 | return yScale(d.close); 34 | }) 35 | .date(function (d) { 36 | return xScale(d.date); 37 | }); 38 | 39 | var ohlc = function (selection) { 40 | var series, bars; 41 | 42 | selection.each(function (data) { 43 | series = d3.select(this).selectAll('.ohlc-series').data([data]); 44 | 45 | series.enter().append('g') 46 | .classed('ohlc-series', true); 47 | 48 | bars = series.selectAll('.bar') 49 | .data(data, function (d) { 50 | return d.date; 51 | }); 52 | 53 | bars.enter() 54 | .append('path') 55 | .classed('bar', true); 56 | 57 | bars.classed({ 58 | 'up-day': isUpDay, 59 | 'down-day': isDownDay 60 | }); 61 | 62 | ohlcBar.tickWidth(tickWidth); 63 | 64 | bars.attr('d', function (d) { 65 | return ohlcBar(d); 66 | }); 67 | 68 | bars.exit().remove(); 69 | series.exit().remove(); 70 | 71 | 72 | }); 73 | }; 74 | 75 | ohlc.xScale = function (value) { 76 | if (!arguments.length) { 77 | return xScale; 78 | } 79 | xScale = value; 80 | return ohlc; 81 | }; 82 | 83 | ohlc.yScale = function (value) { 84 | if (!arguments.length) { 85 | return yScale; 86 | } 87 | yScale = value; 88 | return ohlc; 89 | }; 90 | 91 | ohlc.tickWidth = function (value) { 92 | if (!arguments.length) { 93 | return tickWidth; 94 | } 95 | tickWidth = value; 96 | return ohlc; 97 | }; 98 | 99 | return ohlc; 100 | 101 | }; 102 | }); 103 | -------------------------------------------------------------------------------- /js/zoomChart.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'utils/tickWidth', 4 | 'components/sl' 5 | ], function (d3, tickWidth, sl) { 6 | 'use strict'; 7 | 8 | sl.example.zoomChart = function (zoom, data, series, xScale, yScale, xAxis, yAxis, fromDate, toDate) { 9 | 10 | var initialScale = d3.scale.linear(); 11 | 12 | var zoomChart = function () { 13 | var margin = {top: 20, right: 20, bottom: 30, left: 50}, 14 | width = 800 - margin.left - margin.right, 15 | height = 400 - margin.top - margin.bottom; 16 | 17 | // Create svg element 18 | var svg = d3.select('#chart').classed('chart', true).append('svg') 19 | .attr('width', width + margin.left + margin.right) 20 | .attr('height', height + margin.top + margin.bottom); 21 | 22 | // Ceate chart 23 | var g = svg.append('g') 24 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 25 | 26 | // Create plot area 27 | var plotArea = g.append('g'); 28 | plotArea.append('clipPath') 29 | .attr('id', 'plotAreaClip') 30 | .append('rect') 31 | .attr({ width: width, height: height }); 32 | plotArea.attr('clip-path', 'url(#plotAreaClip)'); 33 | 34 | // Create zoom pane 35 | plotArea.append('rect') 36 | .attr('class', 'zoom-pane') 37 | .attr('width', width) 38 | .attr('height', height) 39 | .call(zoom); 40 | 41 | // Set scale domains 42 | xScale.domain(d3.extent(data, function (d) { 43 | return d.date; 44 | })); 45 | 46 | yScale.domain( 47 | [ 48 | d3.min(data, function (d) { 49 | return d.low; 50 | }), 51 | d3.max(data, function (d) { 52 | return d.high; 53 | }) 54 | ] 55 | ); 56 | 57 | // Set scale ranges 58 | xScale.range([0, width]); 59 | yScale.range([height, 0]); 60 | 61 | // Reset zoom. 62 | zoom.x(xScale); 63 | 64 | series.tickWidth(tickWidth(xScale, fromDate, toDate)); 65 | 66 | // Draw axes 67 | g.append('g') 68 | .attr('class', 'x axis') 69 | .attr('transform', 'translate(0,' + height + ')') 70 | .call(xAxis); 71 | 72 | g.append('g') 73 | .attr('class', 'y axis') 74 | .call(yAxis); 75 | 76 | // Draw series. 77 | plotArea.append('g') 78 | .attr('class', 'series') 79 | .datum(data) 80 | .call(series); 81 | 82 | initialScale = yScale.copy(); 83 | }; 84 | 85 | zoomChart.initialScale = function () { 86 | return initialScale; 87 | }; 88 | 89 | return zoomChart; 90 | }; 91 | }); -------------------------------------------------------------------------------- /js/components/gridlines.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl' 4 | ], function (d3, sl) { 5 | 'use strict'; 6 | 7 | sl.svg.gridlines = function () { 8 | 9 | var xScale = d3.time.scale(), 10 | yScale = d3.scale.linear(), 11 | xTicks = 10, 12 | yTicks = 10; 13 | 14 | var xLines = function (data, grid) { 15 | var xlines = grid.selectAll('.x') 16 | .data(data); 17 | xlines 18 | .enter().append('line') 19 | .attr({ 20 | 'class': 'x', 21 | 'x1': function(d) { return xScale(d);}, 22 | 'x2': function(d) { return xScale(d);}, 23 | 'y1': yScale.range()[0], 24 | 'y2': yScale.range()[1] 25 | }); 26 | xlines 27 | .attr({ 28 | 'x1': function(d) { return xScale(d);}, 29 | 'x2': function(d) { return xScale(d);}, 30 | 'y1': yScale.range()[0], 31 | 'y2': yScale.range()[1] 32 | }); 33 | xlines.exit().remove(); 34 | }; 35 | 36 | var yLines = function (data, grid) { 37 | var ylines = grid.selectAll('.y') 38 | .data(data); 39 | ylines 40 | .enter().append('line') 41 | .attr({ 42 | 'class': 'y', 43 | 'x1': xScale.range()[0], 44 | 'x2': xScale.range()[1], 45 | 'y1': function(d) { return yScale(d);}, 46 | 'y2': function(d) { return yScale(d);} 47 | }); 48 | ylines 49 | .attr({ 50 | 'x1': xScale.range()[0], 51 | 'x2': xScale.range()[1], 52 | 'y1': function(d) { return yScale(d);}, 53 | 'y2': function(d) { return yScale(d);} 54 | }); 55 | ylines.exit().remove(); 56 | }; 57 | 58 | var gridlines = function (selection) { 59 | var grid, xTickData, yTickData; 60 | 61 | selection.each(function () { 62 | xTickData = xScale.ticks(xTicks); 63 | yTickData = yScale.ticks(yTicks); 64 | 65 | grid = d3.select(this).selectAll('.gridlines').data([[xTickData, yTickData]]); 66 | grid.enter().append('g').classed('gridlines', true); 67 | xLines(xTickData, grid); 68 | yLines(yTickData, grid); 69 | }); 70 | }; 71 | 72 | gridlines.xScale = function (value) { 73 | if (!arguments.length) { 74 | return xScale; 75 | } 76 | xScale = value; 77 | return gridlines; 78 | }; 79 | 80 | gridlines.yScale = function (value) { 81 | if (!arguments.length) { 82 | return yScale; 83 | } 84 | yScale = value; 85 | return gridlines; 86 | }; 87 | 88 | gridlines.xTicks = function (value) { 89 | if (!arguments.length) { 90 | return xTicks; 91 | } 92 | xTicks = value; 93 | return gridlines; 94 | }; 95 | 96 | gridlines.yTicks = function (value) { 97 | if (!arguments.length) { 98 | return yTicks; 99 | } 100 | yTicks = value; 101 | return gridlines; 102 | }; 103 | 104 | return gridlines; 105 | }; 106 | 107 | }); -------------------------------------------------------------------------------- /js/ohlcGeometricZoom.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'utils/tickWidth', 6 | 'utils/yScaleTransform', 7 | 'moment', 8 | 'moment-range', 9 | 'components/ohlcBarSeries', 10 | 'modernizr', 11 | 'zoomChart' 12 | ], function (d3, sl, MockData, tickWidth, yScaleTransform, moment) { 13 | 'use strict'; 14 | 15 | var hasVectorEffect = Modernizr.testProp('vectorEffect'); 16 | 17 | var mockData = new MockData(0, 0.1, 100, 50, function (moment) { 18 | return !(moment.day() === 0 || moment.day() === 6); 19 | }); 20 | 21 | var fromDate = new Date(2008, 8, 1), 22 | toDate = new Date(2014, 8, 1); 23 | 24 | var data = mockData.generateOHLC(fromDate, toDate); 25 | 26 | var xScale = d3.time.scale(), 27 | yScale = d3.scale.linear(), 28 | initialScale; 29 | 30 | var xAxis = d3.svg.axis() 31 | .scale(xScale) 32 | .orient('bottom') 33 | .ticks(5); 34 | 35 | var yAxis = d3.svg.axis() 36 | .scale(yScale) 37 | .orient('left'); 38 | 39 | var series = sl.series.ohlcBar() 40 | .xScale(xScale) 41 | .yScale(yScale); 42 | 43 | var zoom = d3.behavior.zoom() 44 | .x(xScale) 45 | .scaleExtent([0.5, 500]) 46 | .on('zoom', zoomed) 47 | .on('zoomend', zoomend); 48 | 49 | 50 | var zoomChart = sl.example.zoomChart(zoom, data, series, xScale, yScale, xAxis, yAxis, fromDate, toDate); 51 | zoomChart(); 52 | 53 | initialScale = zoomChart.initialScale(); 54 | 55 | function zoomed() { 56 | 57 | var xDomain = xScale.domain(), 58 | yTransform, 59 | xTransformTranslate = d3.event.translate[0], 60 | xTransformScale = d3.event.scale; 61 | 62 | var range = moment().range(xDomain[0], xDomain[1]); 63 | var rangeData = []; 64 | var g = d3.selectAll('svg').select('g'); 65 | 66 | for (var i = 0; i < data.length; i += 1) { 67 | if (range.contains(data[i].date)) { 68 | rangeData.push(data[i]); 69 | } 70 | } 71 | 72 | yScale.domain( 73 | [ 74 | d3.min(rangeData, function (d) { 75 | return d.low; 76 | }), 77 | d3.max(rangeData, function (d) { 78 | return d.high; 79 | }) 80 | ] 81 | ); 82 | 83 | yTransform = yScaleTransform(initialScale, yScale); 84 | 85 | g.select('.x.axis') 86 | .call(xAxis); 87 | 88 | g.select('.y.axis') 89 | .call(yAxis); 90 | 91 | g.select('.series') 92 | .attr('transform', 'translate(' + xTransformTranslate + ',' + yTransform.translate+ ')' + 93 | ' scale(' + xTransformScale + ',' + yTransform.scale + ')'); 94 | 95 | } 96 | 97 | function zoomend() { 98 | var g, xDomain; 99 | 100 | if (!hasVectorEffect) { 101 | g = d3.selectAll('svg').select('g'); 102 | xDomain = xScale.domain(); 103 | 104 | initialScale = yScale.copy(); 105 | 106 | zoom.x(xScale); 107 | series.tickWidth(tickWidth(xScale, xDomain[0], xDomain[1])); 108 | 109 | g.select('.x.axis') 110 | .call(xAxis); 111 | 112 | g.select('.y.axis') 113 | .call(yAxis); 114 | 115 | g.selectAll('.series') 116 | .datum(data); 117 | 118 | g.select('.series') 119 | .call(series); 120 | 121 | g.select('.series') 122 | .attr('transform', 'translate(0,0) scale(1)'); 123 | } 124 | } 125 | 126 | return zoomChart; 127 | }); 128 | -------------------------------------------------------------------------------- /js/components/candlestickSeries.js: -------------------------------------------------------------------------------- 1 | define ([ 2 | 'd3', 3 | 'components/sl' 4 | ], function (d3, sl) { 5 | 'use strict'; 6 | 7 | sl.series.candlestick = function () { 8 | 9 | var xScale = d3.time.scale(), 10 | yScale = d3.scale.linear(); 11 | 12 | var rectangleWidth = 5; 13 | 14 | var isUpDay = function(d) { 15 | return d.close > d.open; 16 | }; 17 | var isDownDay = function (d) { 18 | return !isUpDay(d); 19 | }; 20 | 21 | var line = d3.svg.line() 22 | .x(function (d) { 23 | return d.x; 24 | }) 25 | .y(function (d) { 26 | return d.y; 27 | }); 28 | 29 | var highLowLines = function (bars) { 30 | 31 | var paths = bars.selectAll('.high-low-line').data(function (d) { 32 | return [d]; 33 | }); 34 | 35 | paths.enter().append('path'); 36 | 37 | paths.classed('high-low-line', true) 38 | .attr('d', function (d) { 39 | return line([ 40 | { x: xScale(d.date), y: yScale(d.high) }, 41 | { x: xScale(d.date), y: yScale(d.low) } 42 | ]); 43 | }); 44 | }; 45 | 46 | var rectangles = function (bars) { 47 | var rect; 48 | 49 | rect = bars.selectAll('rect').data(function (d) { 50 | return [d]; 51 | }); 52 | 53 | rect.enter().append('rect'); 54 | 55 | rect.attr('x', function (d) { 56 | return xScale(d.date) - rectangleWidth; 57 | }) 58 | .attr('y', function (d) { 59 | return isUpDay(d) ? yScale(d.close) : yScale(d.open); 60 | }) 61 | .attr('width', rectangleWidth * 2) 62 | .attr('height', function (d) { 63 | return isUpDay(d) ? 64 | yScale(d.open) - yScale(d.close) : 65 | yScale(d.close) - yScale(d.open); 66 | }); 67 | }; 68 | 69 | var candlestick = function (selection) { 70 | var series, bars; 71 | 72 | selection.each(function (data) { 73 | series = d3.select(this).selectAll('.candlestick-series').data([data]); 74 | 75 | series.enter().append('g') 76 | .classed('candlestick-series', true); 77 | 78 | bars = series.selectAll('.bar') 79 | .data(data, function (d) { 80 | return d.date; 81 | }); 82 | 83 | bars.enter() 84 | .append('g') 85 | .classed('bar', true); 86 | 87 | bars.classed({ 88 | 'up-day': isUpDay, 89 | 'down-day': isDownDay 90 | }); 91 | 92 | highLowLines(bars); 93 | rectangles(bars); 94 | 95 | bars.exit().remove(); 96 | 97 | 98 | }); 99 | }; 100 | 101 | candlestick.xScale = function (value) { 102 | if (!arguments.length) { 103 | return xScale; 104 | } 105 | xScale = value; 106 | return candlestick; 107 | }; 108 | 109 | candlestick.yScale = function (value) { 110 | if (!arguments.length) { 111 | return yScale; 112 | } 113 | yScale = value; 114 | return candlestick; 115 | }; 116 | 117 | candlestick.rectangleWidth = function (value) { 118 | if (!arguments.length) { 119 | return rectangleWidth; 120 | } 121 | rectangleWidth = value; 122 | return candlestick; 123 | }; 124 | 125 | return candlestick; 126 | 127 | }; 128 | }); -------------------------------------------------------------------------------- /js/components/ohlcSeries.js: -------------------------------------------------------------------------------- 1 | define ([ 2 | 'd3', 3 | 'components/sl' 4 | ], function (d3, sl) { 5 | 'use strict'; 6 | 7 | sl.series.ohlc = function () { 8 | 9 | var xScale = d3.time.scale(), 10 | yScale = d3.scale.linear(); 11 | 12 | var isUpDay = function(d) { 13 | return d.close > d.open; 14 | }; 15 | var isDownDay = function (d) { 16 | return !isUpDay(d); 17 | }; 18 | 19 | var tickWidth = 5; 20 | 21 | var line = d3.svg.line() 22 | .x(function (d) { 23 | return d.x; 24 | }) 25 | .y(function (d) { 26 | return d.y; 27 | }); 28 | 29 | var highLowLines = function (bars) { 30 | 31 | var paths = bars.selectAll('.high-low-line').data(function (d) { 32 | return [d]; 33 | }); 34 | 35 | paths.enter().append('path'); 36 | 37 | paths.classed('high-low-line', true) 38 | .attr('d', function (d) { 39 | return line([ 40 | { x: xScale(d.date), y: yScale(d.high) }, 41 | { x: xScale(d.date), y: yScale(d.low) } 42 | ]); 43 | }); 44 | }; 45 | 46 | var openCloseTicks = function (bars) { 47 | var open, 48 | close; 49 | 50 | open = bars.selectAll('.open-tick').data(function (d) { 51 | return [d]; 52 | }); 53 | 54 | close = bars.selectAll('.close-tick').data(function (d) { 55 | return [d]; 56 | }); 57 | 58 | open.enter().append('path'); 59 | close.enter().append('path'); 60 | 61 | open.classed('open-tick', true) 62 | .attr('d', function (d) { 63 | return line([ 64 | { x: xScale(d.date) - tickWidth, y: yScale(d.open) }, 65 | { x: xScale(d.date), y: yScale(d.open) } 66 | ]); 67 | }); 68 | 69 | close.classed('close-tick', true) 70 | .attr('d', function (d) { 71 | return line([ 72 | { x: xScale(d.date), y: yScale(d.close) }, 73 | { x: xScale(d.date) + tickWidth, y: yScale(d.close) } 74 | ]); 75 | }); 76 | 77 | open.exit().remove(); 78 | close.exit().remove(); 79 | }; 80 | 81 | var ohlc = function (selection) { 82 | var series, bars; 83 | 84 | selection.each(function (data) { 85 | // series = d3.select(this); 86 | 87 | series = d3.select(this).selectAll('.ohlc-series').data([data]); 88 | series.enter().append('g').classed('ohlc-series', true); 89 | 90 | bars = series.selectAll('.bar') 91 | .data(data, function (d) { 92 | return d.date; 93 | }); 94 | 95 | bars.enter() 96 | .append('g') 97 | .classed('bar', true); 98 | 99 | bars.classed({ 100 | 'up-day': isUpDay, 101 | 'down-day': isDownDay 102 | }); 103 | highLowLines(bars); 104 | openCloseTicks(bars); 105 | 106 | bars.exit().remove(); 107 | 108 | 109 | }); 110 | }; 111 | 112 | ohlc.xScale = function (value) { 113 | if (!arguments.length) { 114 | return xScale; 115 | } 116 | xScale = value; 117 | return ohlc; 118 | }; 119 | 120 | ohlc.yScale = function (value) { 121 | if (!arguments.length) { 122 | return yScale; 123 | } 124 | yScale = value; 125 | return ohlc; 126 | }; 127 | 128 | ohlc.tickWidth = function (value) { 129 | if (!arguments.length) { 130 | return tickWidth; 131 | } 132 | tickWidth = value; 133 | return ohlc; 134 | }; 135 | 136 | return ohlc; 137 | 138 | }; 139 | }); -------------------------------------------------------------------------------- /js/comparisonChart.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'components/comparisonSeries', 6 | 'components/gridlines' 7 | ], function (d3, sl, MockData) { 8 | 'use strict'; 9 | 10 | return function (zoomMethod) { 11 | var mockData = new MockData(0, 0.1, 100, 50, function (moment) { 12 | return !(moment.day() === 0 || moment.day() === 6); 13 | }); 14 | 15 | var fromDate = new Date(2012, 8, 1), 16 | toDate = new Date(2014, 8, 1); 17 | 18 | var names = ['Series 1', 'Series 2', 'Series 3']; 19 | 20 | var data = names.map(function (name) { 21 | return { 22 | name: name, 23 | data: mockData.generateOHLC(fromDate, toDate) 24 | }; 25 | }); 26 | 27 | var xScale = d3.time.scale(), 28 | yScale = d3.scale.linear(); 29 | 30 | var xAxis = d3.svg.axis() 31 | .scale(xScale) 32 | .orient('bottom') 33 | .ticks(5); 34 | 35 | var yAxis = d3.svg.axis() 36 | .scale(yScale) 37 | .orient('left') 38 | .ticks(5) 39 | .tickFormat(d3.format('%')); 40 | 41 | var series = sl.series.comparison() 42 | .xScale(xScale) 43 | .yScale(yScale); 44 | 45 | var gridlines = sl.svg.gridlines() 46 | .xScale(xScale) 47 | .yScale(yScale) 48 | .xTicks(10) 49 | .yTicks(5); 50 | 51 | var zoom = d3.behavior.zoom() 52 | .x(xScale) 53 | .scaleExtent([1, 50]) 54 | .on('zoom', zoomed) 55 | .on('zoomend', zoomend); 56 | 57 | var margin = {top: 20, right: 20, bottom: 30, left: 50}, 58 | width = 680 - margin.left - margin.right, 59 | height = 400 - margin.top - margin.bottom; 60 | 61 | // Create svg element 62 | var svg = d3.select('#chart').classed('chart', true).append('svg') 63 | .attr('width', width + margin.left + margin.right) 64 | .attr('height', height + margin.top + margin.bottom); 65 | 66 | // Ceate chart 67 | var g = svg.append('g') 68 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 69 | 70 | // Create plot area 71 | var plotArea = g.append('g'); 72 | plotArea.append('clipPath') 73 | .attr('id', 'plotAreaClip') 74 | .append('rect') 75 | .attr({ width: width, height: height }); 76 | plotArea.attr('clip-path', 'url(#plotAreaClip)'); 77 | 78 | // Create zoom pane 79 | plotArea.append('rect') 80 | .attr('class', 'zoom-pane') 81 | .attr('width', width) 82 | .attr('height', height) 83 | .call(zoom); 84 | 85 | // Set scale domain 86 | 87 | xScale.domain([fromDate, toDate]); 88 | 89 | var xWidth = xScale(toDate); 90 | xScale.domain([xScale.invert(xWidth / 4), xScale.invert(xWidth - (xWidth / 4))]); 91 | 92 | // Set scale ranges 93 | xScale.range([0, width]); 94 | yScale.range([height, 0]); 95 | 96 | // Reset zoom. 97 | zoom.x(xScale); 98 | 99 | // Draw series. 100 | plotArea.append('g') 101 | .attr('class', 'series') 102 | .datum(data) 103 | .call(series); 104 | 105 | // Draw axes 106 | g.append('g') 107 | .attr('class', 'x axis') 108 | .attr('transform', 'translate(0,' + height + ')') 109 | .call(xAxis); 110 | 111 | g.append('g') 112 | .attr('class', 'y axis') 113 | .call(yAxis); 114 | 115 | // Draw gridlines 116 | plotArea 117 | .call(gridlines); 118 | 119 | function zoomed() { 120 | 121 | var xDomain = xScale.domain(); 122 | var xRange = xScale.range(); 123 | var translate = zoom.translate()[0]; 124 | 125 | if (xDomain[0] < fromDate) { 126 | translate = translate - xScale(fromDate) + xRange[0]; 127 | } else if (xDomain[1] > toDate) { 128 | translate = translate - xScale(toDate) + xRange[1]; 129 | } 130 | zoom.translate([translate, 0]); 131 | 132 | if (zoomMethod === "Geometric") { 133 | series.geometricZoom(g.select('.series'), translate, d3.event.scale); 134 | } else { 135 | g.select('.series') 136 | .call(series); 137 | } 138 | 139 | g.select('.x.axis') 140 | .call(xAxis); 141 | 142 | g.select('.y.axis') 143 | .call(yAxis); 144 | 145 | plotArea 146 | .call(gridlines); 147 | } 148 | 149 | function zoomend() { 150 | } 151 | }; 152 | }); 153 | -------------------------------------------------------------------------------- /js/lib/modernizr.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.8.3 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-testprop 3 | */ 4 | ; 5 | 6 | 7 | 8 | window.Modernizr = (function( window, document, undefined ) { 9 | 10 | var version = '2.8.3', 11 | 12 | Modernizr = {}, 13 | 14 | 15 | docElement = document.documentElement, 16 | 17 | mod = 'modernizr', 18 | modElem = document.createElement(mod), 19 | mStyle = modElem.style, 20 | 21 | inputElem , 22 | 23 | 24 | toString = {}.toString, tests = {}, 25 | inputs = {}, 26 | attrs = {}, 27 | 28 | classes = [], 29 | 30 | slice = classes.slice, 31 | 32 | featureName, 33 | 34 | 35 | 36 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; 37 | 38 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { 39 | hasOwnProp = function (object, property) { 40 | return _hasOwnProperty.call(object, property); 41 | }; 42 | } 43 | else { 44 | hasOwnProp = function (object, property) { 45 | return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 46 | }; 47 | } 48 | 49 | 50 | if (!Function.prototype.bind) { 51 | Function.prototype.bind = function bind(that) { 52 | 53 | var target = this; 54 | 55 | if (typeof target != "function") { 56 | throw new TypeError(); 57 | } 58 | 59 | var args = slice.call(arguments, 1), 60 | bound = function () { 61 | 62 | if (this instanceof bound) { 63 | 64 | var F = function(){}; 65 | F.prototype = target.prototype; 66 | var self = new F(); 67 | 68 | var result = target.apply( 69 | self, 70 | args.concat(slice.call(arguments)) 71 | ); 72 | if (Object(result) === result) { 73 | return result; 74 | } 75 | return self; 76 | 77 | } else { 78 | 79 | return target.apply( 80 | that, 81 | args.concat(slice.call(arguments)) 82 | ); 83 | 84 | } 85 | 86 | }; 87 | 88 | return bound; 89 | }; 90 | } 91 | 92 | function setCss( str ) { 93 | mStyle.cssText = str; 94 | } 95 | 96 | function setCssAll( str1, str2 ) { 97 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 98 | } 99 | 100 | function is( obj, type ) { 101 | return typeof obj === type; 102 | } 103 | 104 | function contains( str, substr ) { 105 | return !!~('' + str).indexOf(substr); 106 | } 107 | 108 | function testProps( props, prefixed ) { 109 | for ( var i in props ) { 110 | var prop = props[i]; 111 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { 112 | return prefixed == 'pfx' ? prop : true; 113 | } 114 | } 115 | return false; 116 | } 117 | 118 | function testDOMProps( props, obj, elem ) { 119 | for ( var i in props ) { 120 | var item = obj[props[i]]; 121 | if ( item !== undefined) { 122 | 123 | if (elem === false) return props[i]; 124 | 125 | if (is(item, 'function')){ 126 | return item.bind(elem || obj); 127 | } 128 | 129 | return item; 130 | } 131 | } 132 | return false; 133 | } 134 | for ( var feature in tests ) { 135 | if ( hasOwnProp(tests, feature) ) { 136 | featureName = feature.toLowerCase(); 137 | Modernizr[featureName] = tests[feature](); 138 | 139 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 140 | } 141 | } 142 | 143 | 144 | 145 | Modernizr.addTest = function ( feature, test ) { 146 | if ( typeof feature == 'object' ) { 147 | for ( var key in feature ) { 148 | if ( hasOwnProp( feature, key ) ) { 149 | Modernizr.addTest( key, feature[ key ] ); 150 | } 151 | } 152 | } else { 153 | 154 | feature = feature.toLowerCase(); 155 | 156 | if ( Modernizr[feature] !== undefined ) { 157 | return Modernizr; 158 | } 159 | 160 | test = typeof test == 'function' ? test() : test; 161 | 162 | if (typeof enableClasses !== "undefined" && enableClasses) { 163 | docElement.className += ' ' + (test ? '' : 'no-') + feature; 164 | } 165 | Modernizr[feature] = test; 166 | 167 | } 168 | 169 | return Modernizr; 170 | }; 171 | 172 | 173 | setCss(''); 174 | modElem = inputElem = null; 175 | 176 | 177 | Modernizr._version = version; Modernizr.testProp = function(prop){ 178 | return testProps([prop]); 179 | }; 180 | 181 | 182 | 183 | return Modernizr; 184 | 185 | })(this, this.document); 186 | ; -------------------------------------------------------------------------------- /js/transformDiv.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'MockData', 5 | 'utils/tickWidth', 6 | 'moment', 7 | 'moment-range', 8 | 'components/ohlcBarSeries' 9 | ], function (d3, sl, MockData, tickWidth, moment) { 10 | 'use strict'; 11 | 12 | var mockData = new MockData(0, 0.1, 100, 50, function (moment) { 13 | return !(moment.day() === 0 || moment.day() === 6); 14 | }); 15 | 16 | var fromDate = new Date(2012, 8, 1), 17 | toDate = new Date(2014, 8, 1); 18 | 19 | var data = mockData.generateOHLC(fromDate, toDate); 20 | 21 | var margin = {top: 20, right: 20, bottom: 30, left: 50}, 22 | width = 800 - margin.left - margin.right, 23 | height = 400 - margin.top - margin.bottom; 24 | 25 | var xScale = d3.time.scale(), 26 | yScale = d3.scale.linear(); 27 | 28 | var oldScale; 29 | 30 | var xAxis = d3.svg.axis() 31 | .scale(xScale) 32 | .orient('bottom') 33 | .ticks(5); 34 | 35 | var yAxis = d3.svg.axis() 36 | .scale(yScale) 37 | .orient('left'); 38 | 39 | var series = sl.series.ohlcBar() 40 | .xScale(xScale) 41 | .yScale(yScale) 42 | .tickWidth(tickWidth(xScale, fromDate, toDate)); 43 | 44 | var zoom = d3.behavior.zoom() 45 | .x(xScale) 46 | .scaleExtent([0.5, 500]) 47 | .on('zoom', zoomed) 48 | .on('zoomend', zoomend); 49 | 50 | 51 | function zoomed() { 52 | 53 | var yTransformTranslate = 0, 54 | yTransformScale, 55 | xTransformTranslate = d3.event.translate[0], 56 | xTransformScale = d3.event.scale; 57 | 58 | var xDomain = xScale.domain(); 59 | var range = moment().range(xDomain[0], xDomain[1]); 60 | var rangeData = []; 61 | 62 | var oldDomain = oldScale.domain(); 63 | 64 | var g = d3.selectAll('svg').select('g'); 65 | var seriesDiv = d3.selectAll('#series-container'); 66 | var transform; 67 | 68 | for (var i = 0; i < data.length; i += 1) { 69 | if (range.contains(data[i].date)) { 70 | rangeData.push(data[i]); 71 | } 72 | } 73 | 74 | yScale.domain( 75 | [ 76 | d3.min(rangeData, function (d) { 77 | return d.low; 78 | }), 79 | d3.max(rangeData, function (d) { 80 | return d.high; 81 | }) 82 | ] 83 | ); 84 | 85 | g.select('.x.axis') 86 | .call(xAxis); 87 | 88 | g.select('.y.axis') 89 | .call(yAxis); 90 | 91 | yTransformScale = (oldDomain[1] - oldDomain[0]) / (yScale.domain()[1] - yScale.domain()[0]); 92 | 93 | if (yScale.domain()[1] !== oldDomain[1]) { 94 | yTransformTranslate = oldScale(oldDomain[1]) - oldScale(yScale.domain()[1]) ; 95 | yTransformTranslate *= yTransformScale; 96 | } 97 | 98 | seriesDiv = document.getElementById('series-container'); 99 | 100 | transform = 'translate3d(' + xTransformTranslate + 'px,' + yTransformTranslate + 'px, 0px) scale3d(' + xTransformScale + ',' + yTransformScale + ', 1)'; 101 | seriesDiv.style.webkitTransform = transform; 102 | seriesDiv.style.webkitTransformOrigin = "0 0"; 103 | seriesDiv.style.MozTransform = transform; 104 | seriesDiv.style.MozTransformOrigin = "0 0"; 105 | 106 | } 107 | 108 | function zoomend() { 109 | var xDomain = xScale.domain(); 110 | var seriesDiv = d3.select('#series-container'); 111 | var nullTransform = 'translate3d(0,0,0) scale3d(1,1,1)'; 112 | 113 | oldScale = yScale.copy(); 114 | 115 | zoom.x(xScale); 116 | series.tickWidth(tickWidth(xScale, xDomain[0], xDomain[1])); 117 | 118 | seriesDiv.select('.series') 119 | .call(series); 120 | 121 | seriesDiv = document.getElementById('series-container'); 122 | seriesDiv.style.webkitTransform = nullTransform; 123 | seriesDiv.style.MozTransform = nullTransform; 124 | 125 | } 126 | 127 | // Create svg element 128 | 129 | var clipDiv = d3.select('#chart').classed('chart', true).append('div') 130 | .attr('id', 'series-clip') 131 | .style('position', 'absolute') 132 | .style('overflow', 'hidden') 133 | .style('top', margin.top + 'px') 134 | .style('left', margin.left + 'px'); 135 | 136 | 137 | var seriesDiv = clipDiv.append('div') 138 | .attr('id', 'series-container'); 139 | 140 | var svg = d3.select('#chart').append('svg') 141 | .style('position', 'absolute') 142 | .attr('width', width + margin.left + margin.right) 143 | .attr('height', height + margin.top + margin.bottom); 144 | 145 | var seriesSvg = seriesDiv.append('svg') 146 | .attr('width', width) 147 | .attr('height', height); 148 | 149 | // Ceate chart 150 | var g = svg.append('g') 151 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 152 | 153 | // Create plot area 154 | var plotArea = g.append('g'); 155 | plotArea.append('clipPath') 156 | .attr('id', 'plotAreaClip') 157 | .append('rect') 158 | .attr({ width: width, height: height }); 159 | plotArea.attr('clip-path', 'url(#plotAreaClip)'); 160 | 161 | plotArea.append('rect') 162 | .attr('class', 'zoom-pane') 163 | .attr('width', width) 164 | .attr('height', height) 165 | .call(zoom); 166 | 167 | // Set scale domains 168 | xScale.domain(d3.extent(data, function (d) { 169 | return d.date; 170 | })); 171 | 172 | yScale.domain( 173 | [ 174 | d3.min(data, function (d) { 175 | return d.low; 176 | }), 177 | d3.max(data, function (d) { 178 | return d.high; 179 | }) 180 | ] 181 | ); 182 | 183 | // Set scale ranges 184 | xScale.range([0, width]); 185 | yScale.range([height, 0]); 186 | 187 | zoom.x(xScale); 188 | oldScale = yScale.copy(); 189 | 190 | // Draw axes 191 | g.append('g') 192 | .attr('class', 'x axis') 193 | .attr('transform', 'translate(0,' + height + ')') 194 | .call(xAxis); 195 | 196 | g.append('g') 197 | .attr('class', 'y axis') 198 | .call(yAxis); 199 | 200 | // Draw series. 201 | seriesSvg.append('g') 202 | .attr('class', 'series') 203 | .datum(data) 204 | .call(series); 205 | }); 206 | -------------------------------------------------------------------------------- /js/components/comparisonSeries.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'd3', 3 | 'components/sl', 4 | 'utils/yScaleTransform' 5 | ], function (d3, sl, yScaleTransform) { 6 | 'use strict'; 7 | 8 | sl.series.comparison = function () { 9 | 10 | var xScale = d3.time.scale(), 11 | yScale = d3.scale.linear(); 12 | 13 | var cachedData, cachedScale; 14 | 15 | var findIndex = function (seriesData, date) { 16 | var bisect = d3.bisector( 17 | function (d) { 18 | return d.date; 19 | }).left; 20 | 21 | var initialIndex = bisect(seriesData, date); 22 | if (!initialIndex) { 23 | initialIndex += 1; 24 | } 25 | return initialIndex; 26 | }; 27 | 28 | var percentageChange = function (seriesData, initialDate) { 29 | var initialIndex = findIndex(seriesData, initialDate) - 1; 30 | var initialClose = seriesData[initialIndex].close; 31 | 32 | return seriesData.map(function (d) { 33 | return { 34 | date: d.date, 35 | change: (d.close / initialClose) - 1 36 | }; 37 | }); 38 | }; 39 | 40 | var rebaseChange = function (seriesData, initialDate) { 41 | var initialIndex = findIndex(seriesData, initialDate) - 1; 42 | var initialChange = seriesData[initialIndex].change; 43 | 44 | return seriesData.map(function (d) { 45 | return { 46 | date: d.date, 47 | change: d.change - initialChange 48 | }; 49 | }); 50 | }; 51 | 52 | var calculateYDomain = function (data, xDomain) { 53 | var start, end; 54 | 55 | data = data.map(function (series) { 56 | series = series.data; 57 | start = findIndex(series,xDomain[0]) - 1; 58 | end = findIndex(series,xDomain[1]) + 1; 59 | return series.slice(start, end); 60 | }); 61 | 62 | var allPoints = data.reduce(function(prev, curr) { 63 | return prev.concat(curr); 64 | }, []); 65 | 66 | if (allPoints.length) { 67 | return d3.extent(allPoints, function(d) { 68 | return d.change; 69 | }); 70 | } else { 71 | return [0, 0]; 72 | } 73 | }; 74 | 75 | var color = d3.scale.category10(); 76 | 77 | var line = d3.svg.line() 78 | .interpolate("linear") 79 | .x(function (d) { 80 | return xScale(d.date); 81 | }) 82 | .y(function (d) { 83 | return yScale(d.change); 84 | }); 85 | 86 | var comparison = function (selection) { 87 | var series, lines; 88 | 89 | selection.each(function (data) { 90 | 91 | data = data.map(function (d) { 92 | return { 93 | name: d.name, 94 | data: percentageChange(d.data, xScale.domain()[0]) 95 | }; 96 | }); 97 | 98 | cachedData = data; // Save for rebasing. 99 | 100 | color.domain(data.map(function (d) { 101 | return d.name; 102 | })); 103 | 104 | yScale.domain(calculateYDomain(data, xScale.domain())); 105 | cachedScale = yScale.copy(); 106 | 107 | series = d3.select(this).selectAll('.comparison-series').data([data]); 108 | series.enter().append('g').classed('comparison-series', true); 109 | 110 | lines = series.selectAll('.line') 111 | .data(data, function(d) { 112 | return d.name; 113 | }) 114 | .enter().append("path") 115 | .attr("class", "line") 116 | .attr("d", function (d) { 117 | return line(d.data); 118 | }) 119 | .style("stroke", function (d) { 120 | return color(d.name); 121 | }); 122 | 123 | series.selectAll('.line') 124 | .attr("d", function (d) { 125 | return line(d.data); 126 | }); 127 | }); 128 | }; 129 | 130 | comparison.geometricZoom = function (selection, xTransformTranslate, xTransformScale) { 131 | // Apply a transformation for each line to update its position wrt the new initial date, 132 | // then apply the yScale transformation to reflect the updated yScale domain. 133 | 134 | var initialIndex, 135 | yTransform; 136 | 137 | var lineTransform = function (initialChange) { 138 | var yTransformLineTranslate = cachedScale(0) - cachedScale(initialChange); 139 | 140 | yTransformLineTranslate *= yTransform.scale; 141 | yTransformLineTranslate += yTransform.translate; 142 | 143 | return 'translate(' + xTransformTranslate + ',' + yTransformLineTranslate + ')' + 144 | ' scale(' + xTransformScale + ',' + yTransform.scale + ')'; 145 | }; 146 | 147 | var domainData = cachedData.map(function (d) { 148 | return { 149 | name: d.name, 150 | data: rebaseChange(d.data, xScale.domain()[0]) 151 | } 152 | }); 153 | 154 | yScale.domain(calculateYDomain(domainData, xScale.domain())); 155 | yTransform = yScaleTransform(cachedScale, yScale); 156 | 157 | cachedData = cachedData.map(function (d) { 158 | initialIndex = findIndex(d.data, xScale.domain()[0]) - 1; 159 | return { 160 | name: d.name, 161 | data: d.data, 162 | transform: lineTransform(d.data[initialIndex].change) 163 | }; 164 | }); 165 | 166 | selection.selectAll('.line') 167 | .data(cachedData) 168 | .attr('transform', function (d) { return d.transform; }); 169 | }; 170 | 171 | comparison.xScale = function (value) { 172 | if (!arguments.length) { 173 | return xScale; 174 | } 175 | xScale = value; 176 | return comparison; 177 | }; 178 | 179 | comparison.yScale = function (value) { 180 | if (!arguments.length) { 181 | return yScale; 182 | } 183 | yScale = value; 184 | return comparison; 185 | }; 186 | 187 | return comparison; 188 | }; 189 | }); -------------------------------------------------------------------------------- /js/lib/moment-range.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if(typeof exports === 'object') { 3 | module.exports = factory(require('moment')); 4 | } 5 | else if(typeof define === 'function' && define.amd) { 6 | define('moment-range', ['moment'], factory); 7 | } 8 | else { 9 | root.moment = factory(root.moment); 10 | } 11 | }(this, function(moment) { 12 | var DateRange, INTERVALS; 13 | 14 | INTERVALS = { 15 | year: true, 16 | month: true, 17 | week: true, 18 | day: true, 19 | hour: true, 20 | minute: true, 21 | second: true 22 | }; 23 | 24 | /** 25 | * DateRange class to store ranges and query dates. 26 | * @typedef {!Object} 27 | * 28 | */ 29 | 30 | 31 | DateRange = (function() { 32 | /** 33 | * DateRange instance. 34 | * @param {(Moment|Date)} start Start of interval. 35 | * @param {(Moment|Date)} end End of interval. 36 | * @constructor 37 | * 38 | */ 39 | 40 | function DateRange(start, end) { 41 | this.start = moment(start); 42 | this.end = moment(end); 43 | } 44 | 45 | /** 46 | * Determine if the current interval contains a given moment/date/range. 47 | * @param {(Moment|Date|DateRange)} other Date to check. 48 | * @return {!boolean} 49 | * 50 | */ 51 | 52 | 53 | DateRange.prototype.contains = function(other) { 54 | if (other instanceof DateRange) { 55 | return this.start <= other.start && this.end >= other.end; 56 | } else { 57 | return (this.start <= other && other <= this.end); 58 | } 59 | }; 60 | 61 | /** 62 | * @private 63 | * 64 | */ 65 | 66 | 67 | DateRange.prototype._by_string = function(interval, hollaback) { 68 | var current, _results; 69 | current = moment(this.start); 70 | _results = []; 71 | while (this.contains(current)) { 72 | hollaback.call(this, current.clone()); 73 | _results.push(current.add(interval, 1)); 74 | } 75 | return _results; 76 | }; 77 | 78 | /** 79 | * @private 80 | * 81 | */ 82 | 83 | 84 | DateRange.prototype._by_range = function(range_interval, hollaback) { 85 | var i, l, _i, _results; 86 | l = Math.round(this / range_interval); 87 | if (l === Infinity) { 88 | return this; 89 | } 90 | _results = []; 91 | for (i = _i = 0; 0 <= l ? _i <= l : _i >= l; i = 0 <= l ? ++_i : --_i) { 92 | _results.push(hollaback.call(this, moment(this.start.valueOf() + range_interval.valueOf() * i))); 93 | } 94 | return _results; 95 | }; 96 | 97 | /** 98 | * Determine if the current date range overlaps a given date range. 99 | * @param {!DateRange} range Date range to check. 100 | * @return {!boolean} 101 | * 102 | */ 103 | 104 | 105 | DateRange.prototype.overlaps = function(range) { 106 | return this.intersect(range) !== null; 107 | }; 108 | 109 | /** 110 | * Determine the intersecting periods from one or more date ranges. 111 | * @param {!DateRange} other A date range to intersect with this one. 112 | * @return {!DateRange|null} 113 | * 114 | */ 115 | 116 | 117 | DateRange.prototype.intersect = function(other) { 118 | var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; 119 | if (((this.start <= (_ref1 = other.start) && _ref1 < (_ref = this.end)) && _ref < other.end)) { 120 | return new DateRange(other.start, this.end); 121 | } else if (((other.start < (_ref3 = this.start) && _ref3 < (_ref2 = other.end)) && _ref2 <= this.end)) { 122 | return new DateRange(this.start, other.end); 123 | } else if (((other.start < (_ref5 = this.start) && _ref5 < (_ref4 = this.end)) && _ref4 < other.end)) { 124 | return this; 125 | } else if (((this.start <= (_ref7 = other.start) && _ref7 < (_ref6 = other.end)) && _ref6 <= this.end)) { 126 | return other; 127 | } else { 128 | return null; 129 | } 130 | }; 131 | 132 | /** 133 | * Subtract one range from another. 134 | * @param {!DateRange} other A date range to substract from this one. 135 | * @return {!DateRange[]} 136 | * 137 | */ 138 | 139 | 140 | DateRange.prototype.subtract = function(other) { 141 | var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; 142 | if (this.intersect(other) === null) { 143 | return [this]; 144 | } else if (((other.start <= (_ref1 = this.start) && _ref1 < (_ref = this.end)) && _ref <= other.end)) { 145 | return []; 146 | } else if (((other.start <= (_ref3 = this.start) && _ref3 < (_ref2 = other.end)) && _ref2 < this.end)) { 147 | return [new DateRange(other.end, this.end)]; 148 | } else if (((this.start < (_ref5 = other.start) && _ref5 < (_ref4 = this.end)) && _ref4 <= other.end)) { 149 | return [new DateRange(this.start, other.start)]; 150 | } else if (((this.start < (_ref7 = other.start) && _ref7 < (_ref6 = other.end)) && _ref6 < this.end)) { 151 | return [new DateRange(this.start, other.start), new DateRange(other.end, this.end)]; 152 | } 153 | }; 154 | 155 | /** 156 | * Iterate over the date range by a given date range, executing a function 157 | * for each sub-range. 158 | * @param {!DateRange|String} range Date range to be used for iteration 159 | * or shorthand string (shorthands: 160 | * http://momentjs.com/docs/#/manipulating/add/) 161 | * @param {!function(Moment)} hollaback Function to execute for each sub-range. 162 | * @return {!boolean} 163 | * 164 | */ 165 | 166 | 167 | DateRange.prototype.by = function(range, hollaback) { 168 | if (typeof range === 'string') { 169 | this._by_string(range, hollaback); 170 | } else { 171 | this._by_range(range, hollaback); 172 | } 173 | return this; 174 | }; 175 | 176 | /** 177 | * Date range in milliseconds. Allows basic coercion math of date ranges. 178 | * @return {!number} 179 | * 180 | */ 181 | 182 | 183 | DateRange.prototype.valueOf = function() { 184 | return this.end - this.start; 185 | }; 186 | 187 | /** 188 | * Date range toDate 189 | * @return {!Array} 190 | * 191 | */ 192 | 193 | 194 | DateRange.prototype.toDate = function() { 195 | return [this.start.toDate(), this.end.toDate()]; 196 | }; 197 | 198 | /** 199 | * Determine if this date range is the same as another. 200 | * @param {!DateRange} other Another date range to compare to. 201 | * @return {!boolean} 202 | * 203 | */ 204 | 205 | 206 | DateRange.prototype.isSame = function(other) { 207 | return this.start.isSame(other.start) && this.end.isSame(other.end); 208 | }; 209 | 210 | /** 211 | * Return the difference of the end vs start. 212 | * - To get the difference in milliseconds, use range#diff 213 | * - To get the difference in another unit of measurement, pass that measurement as the second argument. 214 | * @return milliseconds if no measure is passed in, otherwise an increment of measure 215 | * 216 | */ 217 | 218 | 219 | DateRange.prototype.diff = function(unit) { 220 | if (unit == null) { 221 | unit = void 0; 222 | } 223 | return this.end.diff(this.start, unit); 224 | }; 225 | 226 | return DateRange; 227 | 228 | })(); 229 | 230 | /** 231 | * Build a date range. 232 | * @param {(Moment|Date)} start Start of range. 233 | * @param {(Moment|Date)} end End of range. 234 | * @this {Moment} 235 | * @return {!DateRange} 236 | * 237 | */ 238 | 239 | 240 | moment.fn.range = function(start, end) { 241 | if (start in INTERVALS) { 242 | return new DateRange(moment(this).startOf(start), moment(this).endOf(start)); 243 | } else { 244 | return new DateRange(start, end); 245 | } 246 | }; 247 | 248 | /** 249 | * Build a date range. 250 | * @param {(Moment|Date)} start Start of range. 251 | * @param {(Moment|Date)} end End of range. 252 | * @this {Moment} 253 | * @return {!DateRange} 254 | * 255 | */ 256 | 257 | 258 | moment.range = function(start, end) { 259 | return new DateRange(start, end); 260 | }; 261 | 262 | /** 263 | * Check if the current moment is within a given date range. 264 | * @param {!DateRange} range Date range to check. 265 | * @this {Moment} 266 | * @return {!boolean} 267 | * 268 | */ 269 | 270 | 271 | moment.fn.within = function(range) { 272 | return range.contains(this._d); 273 | }; 274 | 275 | return moment; 276 | })); 277 | -------------------------------------------------------------------------------- /js/lib/require.js: -------------------------------------------------------------------------------- 1 | /** vim: et:ts=4:sw=4:sts=4 2 | * @license RequireJS 2.1.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/requirejs for details 5 | */ 6 | //Not using strict: uneven strict support in browsers, #392, and causes 7 | //problems with requirejs.exec()/transpiler plugins that may not be strict. 8 | /*jslint regexp: true, nomen: true, sloppy: true */ 9 | /*global window, navigator, document, importScripts, setTimeout, opera */ 10 | 11 | var requirejs, require, define; 12 | (function (global) { 13 | var req, s, head, baseElement, dataMain, src, 14 | interactiveScript, currentlyAddingScript, mainScript, subPath, 15 | version = '2.1.14', 16 | commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, 17 | cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, 18 | jsSuffixRegExp = /\.js$/, 19 | currDirRegExp = /^\.\//, 20 | op = Object.prototype, 21 | ostring = op.toString, 22 | hasOwn = op.hasOwnProperty, 23 | ap = Array.prototype, 24 | apsp = ap.splice, 25 | isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document), 26 | isWebWorker = !isBrowser && typeof importScripts !== 'undefined', 27 | //PS3 indicates loaded and complete, but need to wait for complete 28 | //specifically. Sequence is 'loading', 'loaded', execution, 29 | // then 'complete'. The UA check is unfortunate, but not sure how 30 | //to feature test w/o causing perf issues. 31 | readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? 32 | /^complete$/ : /^(complete|loaded)$/, 33 | defContextName = '_', 34 | //Oh the tragedy, detecting opera. See the usage of isOpera for reason. 35 | isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', 36 | contexts = {}, 37 | cfg = {}, 38 | globalDefQueue = [], 39 | useInteractive = false; 40 | 41 | function isFunction(it) { 42 | return ostring.call(it) === '[object Function]'; 43 | } 44 | 45 | function isArray(it) { 46 | return ostring.call(it) === '[object Array]'; 47 | } 48 | 49 | /** 50 | * Helper function for iterating over an array. If the func returns 51 | * a true value, it will break out of the loop. 52 | */ 53 | function each(ary, func) { 54 | if (ary) { 55 | var i; 56 | for (i = 0; i < ary.length; i += 1) { 57 | if (ary[i] && func(ary[i], i, ary)) { 58 | break; 59 | } 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Helper function for iterating over an array backwards. If the func 66 | * returns a true value, it will break out of the loop. 67 | */ 68 | function eachReverse(ary, func) { 69 | if (ary) { 70 | var i; 71 | for (i = ary.length - 1; i > -1; i -= 1) { 72 | if (ary[i] && func(ary[i], i, ary)) { 73 | break; 74 | } 75 | } 76 | } 77 | } 78 | 79 | function hasProp(obj, prop) { 80 | return hasOwn.call(obj, prop); 81 | } 82 | 83 | function getOwn(obj, prop) { 84 | return hasProp(obj, prop) && obj[prop]; 85 | } 86 | 87 | /** 88 | * Cycles over properties in an object and calls a function for each 89 | * property value. If the function returns a truthy value, then the 90 | * iteration is stopped. 91 | */ 92 | function eachProp(obj, func) { 93 | var prop; 94 | for (prop in obj) { 95 | if (hasProp(obj, prop)) { 96 | if (func(obj[prop], prop)) { 97 | break; 98 | } 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Simple function to mix in properties from source into target, 105 | * but only if target does not already have a property of the same name. 106 | */ 107 | function mixin(target, source, force, deepStringMixin) { 108 | if (source) { 109 | eachProp(source, function (value, prop) { 110 | if (force || !hasProp(target, prop)) { 111 | if (deepStringMixin && typeof value === 'object' && value && 112 | !isArray(value) && !isFunction(value) && 113 | !(value instanceof RegExp)) { 114 | 115 | if (!target[prop]) { 116 | target[prop] = {}; 117 | } 118 | mixin(target[prop], value, force, deepStringMixin); 119 | } else { 120 | target[prop] = value; 121 | } 122 | } 123 | }); 124 | } 125 | return target; 126 | } 127 | 128 | //Similar to Function.prototype.bind, but the 'this' object is specified 129 | //first, since it is easier to read/figure out what 'this' will be. 130 | function bind(obj, fn) { 131 | return function () { 132 | return fn.apply(obj, arguments); 133 | }; 134 | } 135 | 136 | function scripts() { 137 | return document.getElementsByTagName('script'); 138 | } 139 | 140 | function defaultOnError(err) { 141 | throw err; 142 | } 143 | 144 | //Allow getting a global that is expressed in 145 | //dot notation, like 'a.b.c'. 146 | function getGlobal(value) { 147 | if (!value) { 148 | return value; 149 | } 150 | var g = global; 151 | each(value.split('.'), function (part) { 152 | g = g[part]; 153 | }); 154 | return g; 155 | } 156 | 157 | /** 158 | * Constructs an error with a pointer to an URL with more information. 159 | * @param {String} id the error ID that maps to an ID on a web page. 160 | * @param {String} message human readable error. 161 | * @param {Error} [err] the original error, if there is one. 162 | * 163 | * @returns {Error} 164 | */ 165 | function makeError(id, msg, err, requireModules) { 166 | var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); 167 | e.requireType = id; 168 | e.requireModules = requireModules; 169 | if (err) { 170 | e.originalError = err; 171 | } 172 | return e; 173 | } 174 | 175 | if (typeof define !== 'undefined') { 176 | //If a define is already in play via another AMD loader, 177 | //do not overwrite. 178 | return; 179 | } 180 | 181 | if (typeof requirejs !== 'undefined') { 182 | if (isFunction(requirejs)) { 183 | //Do not overwrite an existing requirejs instance. 184 | return; 185 | } 186 | cfg = requirejs; 187 | requirejs = undefined; 188 | } 189 | 190 | //Allow for a require config object 191 | if (typeof require !== 'undefined' && !isFunction(require)) { 192 | //assume it is a config object. 193 | cfg = require; 194 | require = undefined; 195 | } 196 | 197 | function newContext(contextName) { 198 | var inCheckLoaded, Module, context, handlers, 199 | checkLoadedTimeoutId, 200 | config = { 201 | //Defaults. Do not set a default for map 202 | //config to speed up normalize(), which 203 | //will run faster if there is no default. 204 | waitSeconds: 7, 205 | baseUrl: './', 206 | paths: {}, 207 | bundles: {}, 208 | pkgs: {}, 209 | shim: {}, 210 | config: {} 211 | }, 212 | registry = {}, 213 | //registry of just enabled modules, to speed 214 | //cycle breaking code when lots of modules 215 | //are registered, but not activated. 216 | enabledRegistry = {}, 217 | undefEvents = {}, 218 | defQueue = [], 219 | defined = {}, 220 | urlFetched = {}, 221 | bundlesMap = {}, 222 | requireCounter = 1, 223 | unnormalizedCounter = 1; 224 | 225 | /** 226 | * Trims the . and .. from an array of path segments. 227 | * It will keep a leading path segment if a .. will become 228 | * the first path segment, to help with module name lookups, 229 | * which act like paths, but can be remapped. But the end result, 230 | * all paths that use this function should look normalized. 231 | * NOTE: this method MODIFIES the input array. 232 | * @param {Array} ary the array of path segments. 233 | */ 234 | function trimDots(ary) { 235 | var i, part; 236 | for (i = 0; i < ary.length; i++) { 237 | part = ary[i]; 238 | if (part === '.') { 239 | ary.splice(i, 1); 240 | i -= 1; 241 | } else if (part === '..') { 242 | // If at the start, or previous value is still .., 243 | // keep them so that when converted to a path it may 244 | // still work when converted to a path, even though 245 | // as an ID it is less than ideal. In larger point 246 | // releases, may be better to just kick out an error. 247 | if (i === 0 || (i == 1 && ary[2] === '..') || ary[i - 1] === '..') { 248 | continue; 249 | } else if (i > 0) { 250 | ary.splice(i - 1, 2); 251 | i -= 2; 252 | } 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * Given a relative module name, like ./something, normalize it to 259 | * a real name that can be mapped to a path. 260 | * @param {String} name the relative name 261 | * @param {String} baseName a real name that the name arg is relative 262 | * to. 263 | * @param {Boolean} applyMap apply the map config to the value. Should 264 | * only be done if this normalization is for a dependency ID. 265 | * @returns {String} normalized name 266 | */ 267 | function normalize(name, baseName, applyMap) { 268 | var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, 269 | foundMap, foundI, foundStarMap, starI, normalizedBaseParts, 270 | baseParts = (baseName && baseName.split('/')), 271 | map = config.map, 272 | starMap = map && map['*']; 273 | 274 | //Adjust any relative paths. 275 | if (name) { 276 | name = name.split('/'); 277 | lastIndex = name.length - 1; 278 | 279 | // If wanting node ID compatibility, strip .js from end 280 | // of IDs. Have to do this here, and not in nameToUrl 281 | // because node allows either .js or non .js to map 282 | // to same file. 283 | if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 284 | name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 285 | } 286 | 287 | // Starts with a '.' so need the baseName 288 | if (name[0].charAt(0) === '.' && baseParts) { 289 | //Convert baseName to array, and lop off the last part, 290 | //so that . matches that 'directory' and not name of the baseName's 291 | //module. For instance, baseName of 'one/two/three', maps to 292 | //'one/two/three.js', but we want the directory, 'one/two' for 293 | //this normalization. 294 | normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); 295 | name = normalizedBaseParts.concat(name); 296 | } 297 | 298 | trimDots(name); 299 | name = name.join('/'); 300 | } 301 | 302 | //Apply map config if available. 303 | if (applyMap && map && (baseParts || starMap)) { 304 | nameParts = name.split('/'); 305 | 306 | outerLoop: for (i = nameParts.length; i > 0; i -= 1) { 307 | nameSegment = nameParts.slice(0, i).join('/'); 308 | 309 | if (baseParts) { 310 | //Find the longest baseName segment match in the config. 311 | //So, do joins on the biggest to smallest lengths of baseParts. 312 | for (j = baseParts.length; j > 0; j -= 1) { 313 | mapValue = getOwn(map, baseParts.slice(0, j).join('/')); 314 | 315 | //baseName segment has config, find if it has one for 316 | //this name. 317 | if (mapValue) { 318 | mapValue = getOwn(mapValue, nameSegment); 319 | if (mapValue) { 320 | //Match, update name to the new value. 321 | foundMap = mapValue; 322 | foundI = i; 323 | break outerLoop; 324 | } 325 | } 326 | } 327 | } 328 | 329 | //Check for a star map match, but just hold on to it, 330 | //if there is a shorter segment match later in a matching 331 | //config, then favor over this star map. 332 | if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { 333 | foundStarMap = getOwn(starMap, nameSegment); 334 | starI = i; 335 | } 336 | } 337 | 338 | if (!foundMap && foundStarMap) { 339 | foundMap = foundStarMap; 340 | foundI = starI; 341 | } 342 | 343 | if (foundMap) { 344 | nameParts.splice(0, foundI, foundMap); 345 | name = nameParts.join('/'); 346 | } 347 | } 348 | 349 | // If the name points to a package's name, use 350 | // the package main instead. 351 | pkgMain = getOwn(config.pkgs, name); 352 | 353 | return pkgMain ? pkgMain : name; 354 | } 355 | 356 | function removeScript(name) { 357 | if (isBrowser) { 358 | each(scripts(), function (scriptNode) { 359 | if (scriptNode.getAttribute('data-requiremodule') === name && 360 | scriptNode.getAttribute('data-requirecontext') === context.contextName) { 361 | scriptNode.parentNode.removeChild(scriptNode); 362 | return true; 363 | } 364 | }); 365 | } 366 | } 367 | 368 | function hasPathFallback(id) { 369 | var pathConfig = getOwn(config.paths, id); 370 | if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { 371 | //Pop off the first array value, since it failed, and 372 | //retry 373 | pathConfig.shift(); 374 | context.require.undef(id); 375 | 376 | //Custom require that does not do map translation, since 377 | //ID is "absolute", already mapped/resolved. 378 | context.makeRequire(null, { 379 | skipMap: true 380 | })([id]); 381 | 382 | return true; 383 | } 384 | } 385 | 386 | //Turns a plugin!resource to [plugin, resource] 387 | //with the plugin being undefined if the name 388 | //did not have a plugin prefix. 389 | function splitPrefix(name) { 390 | var prefix, 391 | index = name ? name.indexOf('!') : -1; 392 | if (index > -1) { 393 | prefix = name.substring(0, index); 394 | name = name.substring(index + 1, name.length); 395 | } 396 | return [prefix, name]; 397 | } 398 | 399 | /** 400 | * Creates a module mapping that includes plugin prefix, module 401 | * name, and path. If parentModuleMap is provided it will 402 | * also normalize the name via require.normalize() 403 | * 404 | * @param {String} name the module name 405 | * @param {String} [parentModuleMap] parent module map 406 | * for the module name, used to resolve relative names. 407 | * @param {Boolean} isNormalized: is the ID already normalized. 408 | * This is true if this call is done for a define() module ID. 409 | * @param {Boolean} applyMap: apply the map config to the ID. 410 | * Should only be true if this map is for a dependency. 411 | * 412 | * @returns {Object} 413 | */ 414 | function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { 415 | var url, pluginModule, suffix, nameParts, 416 | prefix = null, 417 | parentName = parentModuleMap ? parentModuleMap.name : null, 418 | originalName = name, 419 | isDefine = true, 420 | normalizedName = ''; 421 | 422 | //If no name, then it means it is a require call, generate an 423 | //internal name. 424 | if (!name) { 425 | isDefine = false; 426 | name = '_@r' + (requireCounter += 1); 427 | } 428 | 429 | nameParts = splitPrefix(name); 430 | prefix = nameParts[0]; 431 | name = nameParts[1]; 432 | 433 | if (prefix) { 434 | prefix = normalize(prefix, parentName, applyMap); 435 | pluginModule = getOwn(defined, prefix); 436 | } 437 | 438 | //Account for relative paths if there is a base name. 439 | if (name) { 440 | if (prefix) { 441 | if (pluginModule && pluginModule.normalize) { 442 | //Plugin is loaded, use its normalize method. 443 | normalizedName = pluginModule.normalize(name, function (name) { 444 | return normalize(name, parentName, applyMap); 445 | }); 446 | } else { 447 | // If nested plugin references, then do not try to 448 | // normalize, as it will not normalize correctly. This 449 | // places a restriction on resourceIds, and the longer 450 | // term solution is not to normalize until plugins are 451 | // loaded and all normalizations to allow for async 452 | // loading of a loader plugin. But for now, fixes the 453 | // common uses. Details in #1131 454 | normalizedName = name.indexOf('!') === -1 ? 455 | normalize(name, parentName, applyMap) : 456 | name; 457 | } 458 | } else { 459 | //A regular module. 460 | normalizedName = normalize(name, parentName, applyMap); 461 | 462 | //Normalized name may be a plugin ID due to map config 463 | //application in normalize. The map config values must 464 | //already be normalized, so do not need to redo that part. 465 | nameParts = splitPrefix(normalizedName); 466 | prefix = nameParts[0]; 467 | normalizedName = nameParts[1]; 468 | isNormalized = true; 469 | 470 | url = context.nameToUrl(normalizedName); 471 | } 472 | } 473 | 474 | //If the id is a plugin id that cannot be determined if it needs 475 | //normalization, stamp it with a unique ID so two matching relative 476 | //ids that may conflict can be separate. 477 | suffix = prefix && !pluginModule && !isNormalized ? 478 | '_unnormalized' + (unnormalizedCounter += 1) : 479 | ''; 480 | 481 | return { 482 | prefix: prefix, 483 | name: normalizedName, 484 | parentMap: parentModuleMap, 485 | unnormalized: !!suffix, 486 | url: url, 487 | originalName: originalName, 488 | isDefine: isDefine, 489 | id: (prefix ? 490 | prefix + '!' + normalizedName : 491 | normalizedName) + suffix 492 | }; 493 | } 494 | 495 | function getModule(depMap) { 496 | var id = depMap.id, 497 | mod = getOwn(registry, id); 498 | 499 | if (!mod) { 500 | mod = registry[id] = new context.Module(depMap); 501 | } 502 | 503 | return mod; 504 | } 505 | 506 | function on(depMap, name, fn) { 507 | var id = depMap.id, 508 | mod = getOwn(registry, id); 509 | 510 | if (hasProp(defined, id) && 511 | (!mod || mod.defineEmitComplete)) { 512 | if (name === 'defined') { 513 | fn(defined[id]); 514 | } 515 | } else { 516 | mod = getModule(depMap); 517 | if (mod.error && name === 'error') { 518 | fn(mod.error); 519 | } else { 520 | mod.on(name, fn); 521 | } 522 | } 523 | } 524 | 525 | function onError(err, errback) { 526 | var ids = err.requireModules, 527 | notified = false; 528 | 529 | if (errback) { 530 | errback(err); 531 | } else { 532 | each(ids, function (id) { 533 | var mod = getOwn(registry, id); 534 | if (mod) { 535 | //Set error on module, so it skips timeout checks. 536 | mod.error = err; 537 | if (mod.events.error) { 538 | notified = true; 539 | mod.emit('error', err); 540 | } 541 | } 542 | }); 543 | 544 | if (!notified) { 545 | req.onError(err); 546 | } 547 | } 548 | } 549 | 550 | /** 551 | * Internal method to transfer globalQueue items to this context's 552 | * defQueue. 553 | */ 554 | function takeGlobalQueue() { 555 | //Push all the globalDefQueue items into the context's defQueue 556 | if (globalDefQueue.length) { 557 | //Array splice in the values since the context code has a 558 | //local var ref to defQueue, so cannot just reassign the one 559 | //on context. 560 | apsp.apply(defQueue, 561 | [defQueue.length, 0].concat(globalDefQueue)); 562 | globalDefQueue = []; 563 | } 564 | } 565 | 566 | handlers = { 567 | 'require': function (mod) { 568 | if (mod.require) { 569 | return mod.require; 570 | } else { 571 | return (mod.require = context.makeRequire(mod.map)); 572 | } 573 | }, 574 | 'exports': function (mod) { 575 | mod.usingExports = true; 576 | if (mod.map.isDefine) { 577 | if (mod.exports) { 578 | return (defined[mod.map.id] = mod.exports); 579 | } else { 580 | return (mod.exports = defined[mod.map.id] = {}); 581 | } 582 | } 583 | }, 584 | 'module': function (mod) { 585 | if (mod.module) { 586 | return mod.module; 587 | } else { 588 | return (mod.module = { 589 | id: mod.map.id, 590 | uri: mod.map.url, 591 | config: function () { 592 | return getOwn(config.config, mod.map.id) || {}; 593 | }, 594 | exports: mod.exports || (mod.exports = {}) 595 | }); 596 | } 597 | } 598 | }; 599 | 600 | function cleanRegistry(id) { 601 | //Clean up machinery used for waiting modules. 602 | delete registry[id]; 603 | delete enabledRegistry[id]; 604 | } 605 | 606 | function breakCycle(mod, traced, processed) { 607 | var id = mod.map.id; 608 | 609 | if (mod.error) { 610 | mod.emit('error', mod.error); 611 | } else { 612 | traced[id] = true; 613 | each(mod.depMaps, function (depMap, i) { 614 | var depId = depMap.id, 615 | dep = getOwn(registry, depId); 616 | 617 | //Only force things that have not completed 618 | //being defined, so still in the registry, 619 | //and only if it has not been matched up 620 | //in the module already. 621 | if (dep && !mod.depMatched[i] && !processed[depId]) { 622 | if (getOwn(traced, depId)) { 623 | mod.defineDep(i, defined[depId]); 624 | mod.check(); //pass false? 625 | } else { 626 | breakCycle(dep, traced, processed); 627 | } 628 | } 629 | }); 630 | processed[id] = true; 631 | } 632 | } 633 | 634 | function checkLoaded() { 635 | var err, usingPathFallback, 636 | waitInterval = config.waitSeconds * 1000, 637 | //It is possible to disable the wait interval by using waitSeconds of 0. 638 | expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), 639 | noLoads = [], 640 | reqCalls = [], 641 | stillLoading = false, 642 | needCycleCheck = true; 643 | 644 | //Do not bother if this call was a result of a cycle break. 645 | if (inCheckLoaded) { 646 | return; 647 | } 648 | 649 | inCheckLoaded = true; 650 | 651 | //Figure out the state of all the modules. 652 | eachProp(enabledRegistry, function (mod) { 653 | var map = mod.map, 654 | modId = map.id; 655 | 656 | //Skip things that are not enabled or in error state. 657 | if (!mod.enabled) { 658 | return; 659 | } 660 | 661 | if (!map.isDefine) { 662 | reqCalls.push(mod); 663 | } 664 | 665 | if (!mod.error) { 666 | //If the module should be executed, and it has not 667 | //been inited and time is up, remember it. 668 | if (!mod.inited && expired) { 669 | if (hasPathFallback(modId)) { 670 | usingPathFallback = true; 671 | stillLoading = true; 672 | } else { 673 | noLoads.push(modId); 674 | removeScript(modId); 675 | } 676 | } else if (!mod.inited && mod.fetched && map.isDefine) { 677 | stillLoading = true; 678 | if (!map.prefix) { 679 | //No reason to keep looking for unfinished 680 | //loading. If the only stillLoading is a 681 | //plugin resource though, keep going, 682 | //because it may be that a plugin resource 683 | //is waiting on a non-plugin cycle. 684 | return (needCycleCheck = false); 685 | } 686 | } 687 | } 688 | }); 689 | 690 | if (expired && noLoads.length) { 691 | //If wait time expired, throw error of unloaded modules. 692 | err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); 693 | err.contextName = context.contextName; 694 | return onError(err); 695 | } 696 | 697 | //Not expired, check for a cycle. 698 | if (needCycleCheck) { 699 | each(reqCalls, function (mod) { 700 | breakCycle(mod, {}, {}); 701 | }); 702 | } 703 | 704 | //If still waiting on loads, and the waiting load is something 705 | //other than a plugin resource, or there are still outstanding 706 | //scripts, then just try back later. 707 | if ((!expired || usingPathFallback) && stillLoading) { 708 | //Something is still waiting to load. Wait for it, but only 709 | //if a timeout is not already in effect. 710 | if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { 711 | checkLoadedTimeoutId = setTimeout(function () { 712 | checkLoadedTimeoutId = 0; 713 | checkLoaded(); 714 | }, 50); 715 | } 716 | } 717 | 718 | inCheckLoaded = false; 719 | } 720 | 721 | Module = function (map) { 722 | this.events = getOwn(undefEvents, map.id) || {}; 723 | this.map = map; 724 | this.shim = getOwn(config.shim, map.id); 725 | this.depExports = []; 726 | this.depMaps = []; 727 | this.depMatched = []; 728 | this.pluginMaps = {}; 729 | this.depCount = 0; 730 | 731 | /* this.exports this.factory 732 | this.depMaps = [], 733 | this.enabled, this.fetched 734 | */ 735 | }; 736 | 737 | Module.prototype = { 738 | init: function (depMaps, factory, errback, options) { 739 | options = options || {}; 740 | 741 | //Do not do more inits if already done. Can happen if there 742 | //are multiple define calls for the same module. That is not 743 | //a normal, common case, but it is also not unexpected. 744 | if (this.inited) { 745 | return; 746 | } 747 | 748 | this.factory = factory; 749 | 750 | if (errback) { 751 | //Register for errors on this module. 752 | this.on('error', errback); 753 | } else if (this.events.error) { 754 | //If no errback already, but there are error listeners 755 | //on this module, set up an errback to pass to the deps. 756 | errback = bind(this, function (err) { 757 | this.emit('error', err); 758 | }); 759 | } 760 | 761 | //Do a copy of the dependency array, so that 762 | //source inputs are not modified. For example 763 | //"shim" deps are passed in here directly, and 764 | //doing a direct modification of the depMaps array 765 | //would affect that config. 766 | this.depMaps = depMaps && depMaps.slice(0); 767 | 768 | this.errback = errback; 769 | 770 | //Indicate this module has be initialized 771 | this.inited = true; 772 | 773 | this.ignore = options.ignore; 774 | 775 | //Could have option to init this module in enabled mode, 776 | //or could have been previously marked as enabled. However, 777 | //the dependencies are not known until init is called. So 778 | //if enabled previously, now trigger dependencies as enabled. 779 | if (options.enabled || this.enabled) { 780 | //Enable this module and dependencies. 781 | //Will call this.check() 782 | this.enable(); 783 | } else { 784 | this.check(); 785 | } 786 | }, 787 | 788 | defineDep: function (i, depExports) { 789 | //Because of cycles, defined callback for a given 790 | //export can be called more than once. 791 | if (!this.depMatched[i]) { 792 | this.depMatched[i] = true; 793 | this.depCount -= 1; 794 | this.depExports[i] = depExports; 795 | } 796 | }, 797 | 798 | fetch: function () { 799 | if (this.fetched) { 800 | return; 801 | } 802 | this.fetched = true; 803 | 804 | context.startTime = (new Date()).getTime(); 805 | 806 | var map = this.map; 807 | 808 | //If the manager is for a plugin managed resource, 809 | //ask the plugin to load it now. 810 | if (this.shim) { 811 | context.makeRequire(this.map, { 812 | enableBuildCallback: true 813 | })(this.shim.deps || [], bind(this, function () { 814 | return map.prefix ? this.callPlugin() : this.load(); 815 | })); 816 | } else { 817 | //Regular dependency. 818 | return map.prefix ? this.callPlugin() : this.load(); 819 | } 820 | }, 821 | 822 | load: function () { 823 | var url = this.map.url; 824 | 825 | //Regular dependency. 826 | if (!urlFetched[url]) { 827 | urlFetched[url] = true; 828 | context.load(this.map.id, url); 829 | } 830 | }, 831 | 832 | /** 833 | * Checks if the module is ready to define itself, and if so, 834 | * define it. 835 | */ 836 | check: function () { 837 | if (!this.enabled || this.enabling) { 838 | return; 839 | } 840 | 841 | var err, cjsModule, 842 | id = this.map.id, 843 | depExports = this.depExports, 844 | exports = this.exports, 845 | factory = this.factory; 846 | 847 | if (!this.inited) { 848 | this.fetch(); 849 | } else if (this.error) { 850 | this.emit('error', this.error); 851 | } else if (!this.defining) { 852 | //The factory could trigger another require call 853 | //that would result in checking this module to 854 | //define itself again. If already in the process 855 | //of doing that, skip this work. 856 | this.defining = true; 857 | 858 | if (this.depCount < 1 && !this.defined) { 859 | if (isFunction(factory)) { 860 | //If there is an error listener, favor passing 861 | //to that instead of throwing an error. However, 862 | //only do it for define()'d modules. require 863 | //errbacks should not be called for failures in 864 | //their callbacks (#699). However if a global 865 | //onError is set, use that. 866 | if ((this.events.error && this.map.isDefine) || 867 | req.onError !== defaultOnError) { 868 | try { 869 | exports = context.execCb(id, factory, depExports, exports); 870 | } catch (e) { 871 | err = e; 872 | } 873 | } else { 874 | exports = context.execCb(id, factory, depExports, exports); 875 | } 876 | 877 | // Favor return value over exports. If node/cjs in play, 878 | // then will not have a return value anyway. Favor 879 | // module.exports assignment over exports object. 880 | if (this.map.isDefine && exports === undefined) { 881 | cjsModule = this.module; 882 | if (cjsModule) { 883 | exports = cjsModule.exports; 884 | } else if (this.usingExports) { 885 | //exports already set the defined value. 886 | exports = this.exports; 887 | } 888 | } 889 | 890 | if (err) { 891 | err.requireMap = this.map; 892 | err.requireModules = this.map.isDefine ? [this.map.id] : null; 893 | err.requireType = this.map.isDefine ? 'define' : 'require'; 894 | return onError((this.error = err)); 895 | } 896 | 897 | } else { 898 | //Just a literal value 899 | exports = factory; 900 | } 901 | 902 | this.exports = exports; 903 | 904 | if (this.map.isDefine && !this.ignore) { 905 | defined[id] = exports; 906 | 907 | if (req.onResourceLoad) { 908 | req.onResourceLoad(context, this.map, this.depMaps); 909 | } 910 | } 911 | 912 | //Clean up 913 | cleanRegistry(id); 914 | 915 | this.defined = true; 916 | } 917 | 918 | //Finished the define stage. Allow calling check again 919 | //to allow define notifications below in the case of a 920 | //cycle. 921 | this.defining = false; 922 | 923 | if (this.defined && !this.defineEmitted) { 924 | this.defineEmitted = true; 925 | this.emit('defined', this.exports); 926 | this.defineEmitComplete = true; 927 | } 928 | 929 | } 930 | }, 931 | 932 | callPlugin: function () { 933 | var map = this.map, 934 | id = map.id, 935 | //Map already normalized the prefix. 936 | pluginMap = makeModuleMap(map.prefix); 937 | 938 | //Mark this as a dependency for this plugin, so it 939 | //can be traced for cycles. 940 | this.depMaps.push(pluginMap); 941 | 942 | on(pluginMap, 'defined', bind(this, function (plugin) { 943 | var load, normalizedMap, normalizedMod, 944 | bundleId = getOwn(bundlesMap, this.map.id), 945 | name = this.map.name, 946 | parentName = this.map.parentMap ? this.map.parentMap.name : null, 947 | localRequire = context.makeRequire(map.parentMap, { 948 | enableBuildCallback: true 949 | }); 950 | 951 | //If current map is not normalized, wait for that 952 | //normalized name to load instead of continuing. 953 | if (this.map.unnormalized) { 954 | //Normalize the ID if the plugin allows it. 955 | if (plugin.normalize) { 956 | name = plugin.normalize(name, function (name) { 957 | return normalize(name, parentName, true); 958 | }) || ''; 959 | } 960 | 961 | //prefix and name should already be normalized, no need 962 | //for applying map config again either. 963 | normalizedMap = makeModuleMap(map.prefix + '!' + name, 964 | this.map.parentMap); 965 | on(normalizedMap, 966 | 'defined', bind(this, function (value) { 967 | this.init([], function () { return value; }, null, { 968 | enabled: true, 969 | ignore: true 970 | }); 971 | })); 972 | 973 | normalizedMod = getOwn(registry, normalizedMap.id); 974 | if (normalizedMod) { 975 | //Mark this as a dependency for this plugin, so it 976 | //can be traced for cycles. 977 | this.depMaps.push(normalizedMap); 978 | 979 | if (this.events.error) { 980 | normalizedMod.on('error', bind(this, function (err) { 981 | this.emit('error', err); 982 | })); 983 | } 984 | normalizedMod.enable(); 985 | } 986 | 987 | return; 988 | } 989 | 990 | //If a paths config, then just load that file instead to 991 | //resolve the plugin, as it is built into that paths layer. 992 | if (bundleId) { 993 | this.map.url = context.nameToUrl(bundleId); 994 | this.load(); 995 | return; 996 | } 997 | 998 | load = bind(this, function (value) { 999 | this.init([], function () { return value; }, null, { 1000 | enabled: true 1001 | }); 1002 | }); 1003 | 1004 | load.error = bind(this, function (err) { 1005 | this.inited = true; 1006 | this.error = err; 1007 | err.requireModules = [id]; 1008 | 1009 | //Remove temp unnormalized modules for this module, 1010 | //since they will never be resolved otherwise now. 1011 | eachProp(registry, function (mod) { 1012 | if (mod.map.id.indexOf(id + '_unnormalized') === 0) { 1013 | cleanRegistry(mod.map.id); 1014 | } 1015 | }); 1016 | 1017 | onError(err); 1018 | }); 1019 | 1020 | //Allow plugins to load other code without having to know the 1021 | //context or how to 'complete' the load. 1022 | load.fromText = bind(this, function (text, textAlt) { 1023 | /*jslint evil: true */ 1024 | var moduleName = map.name, 1025 | moduleMap = makeModuleMap(moduleName), 1026 | hasInteractive = useInteractive; 1027 | 1028 | //As of 2.1.0, support just passing the text, to reinforce 1029 | //fromText only being called once per resource. Still 1030 | //support old style of passing moduleName but discard 1031 | //that moduleName in favor of the internal ref. 1032 | if (textAlt) { 1033 | text = textAlt; 1034 | } 1035 | 1036 | //Turn off interactive script matching for IE for any define 1037 | //calls in the text, then turn it back on at the end. 1038 | if (hasInteractive) { 1039 | useInteractive = false; 1040 | } 1041 | 1042 | //Prime the system by creating a module instance for 1043 | //it. 1044 | getModule(moduleMap); 1045 | 1046 | //Transfer any config to this other module. 1047 | if (hasProp(config.config, id)) { 1048 | config.config[moduleName] = config.config[id]; 1049 | } 1050 | 1051 | try { 1052 | req.exec(text); 1053 | } catch (e) { 1054 | return onError(makeError('fromtexteval', 1055 | 'fromText eval for ' + id + 1056 | ' failed: ' + e, 1057 | e, 1058 | [id])); 1059 | } 1060 | 1061 | if (hasInteractive) { 1062 | useInteractive = true; 1063 | } 1064 | 1065 | //Mark this as a dependency for the plugin 1066 | //resource 1067 | this.depMaps.push(moduleMap); 1068 | 1069 | //Support anonymous modules. 1070 | context.completeLoad(moduleName); 1071 | 1072 | //Bind the value of that module to the value for this 1073 | //resource ID. 1074 | localRequire([moduleName], load); 1075 | }); 1076 | 1077 | //Use parentName here since the plugin's name is not reliable, 1078 | //could be some weird string with no path that actually wants to 1079 | //reference the parentName's path. 1080 | plugin.load(map.name, localRequire, load, config); 1081 | })); 1082 | 1083 | context.enable(pluginMap, this); 1084 | this.pluginMaps[pluginMap.id] = pluginMap; 1085 | }, 1086 | 1087 | enable: function () { 1088 | enabledRegistry[this.map.id] = this; 1089 | this.enabled = true; 1090 | 1091 | //Set flag mentioning that the module is enabling, 1092 | //so that immediate calls to the defined callbacks 1093 | //for dependencies do not trigger inadvertent load 1094 | //with the depCount still being zero. 1095 | this.enabling = true; 1096 | 1097 | //Enable each dependency 1098 | each(this.depMaps, bind(this, function (depMap, i) { 1099 | var id, mod, handler; 1100 | 1101 | if (typeof depMap === 'string') { 1102 | //Dependency needs to be converted to a depMap 1103 | //and wired up to this module. 1104 | depMap = makeModuleMap(depMap, 1105 | (this.map.isDefine ? this.map : this.map.parentMap), 1106 | false, 1107 | !this.skipMap); 1108 | this.depMaps[i] = depMap; 1109 | 1110 | handler = getOwn(handlers, depMap.id); 1111 | 1112 | if (handler) { 1113 | this.depExports[i] = handler(this); 1114 | return; 1115 | } 1116 | 1117 | this.depCount += 1; 1118 | 1119 | on(depMap, 'defined', bind(this, function (depExports) { 1120 | this.defineDep(i, depExports); 1121 | this.check(); 1122 | })); 1123 | 1124 | if (this.errback) { 1125 | on(depMap, 'error', bind(this, this.errback)); 1126 | } 1127 | } 1128 | 1129 | id = depMap.id; 1130 | mod = registry[id]; 1131 | 1132 | //Skip special modules like 'require', 'exports', 'module' 1133 | //Also, don't call enable if it is already enabled, 1134 | //important in circular dependency cases. 1135 | if (!hasProp(handlers, id) && mod && !mod.enabled) { 1136 | context.enable(depMap, this); 1137 | } 1138 | })); 1139 | 1140 | //Enable each plugin that is used in 1141 | //a dependency 1142 | eachProp(this.pluginMaps, bind(this, function (pluginMap) { 1143 | var mod = getOwn(registry, pluginMap.id); 1144 | if (mod && !mod.enabled) { 1145 | context.enable(pluginMap, this); 1146 | } 1147 | })); 1148 | 1149 | this.enabling = false; 1150 | 1151 | this.check(); 1152 | }, 1153 | 1154 | on: function (name, cb) { 1155 | var cbs = this.events[name]; 1156 | if (!cbs) { 1157 | cbs = this.events[name] = []; 1158 | } 1159 | cbs.push(cb); 1160 | }, 1161 | 1162 | emit: function (name, evt) { 1163 | each(this.events[name], function (cb) { 1164 | cb(evt); 1165 | }); 1166 | if (name === 'error') { 1167 | //Now that the error handler was triggered, remove 1168 | //the listeners, since this broken Module instance 1169 | //can stay around for a while in the registry. 1170 | delete this.events[name]; 1171 | } 1172 | } 1173 | }; 1174 | 1175 | function callGetModule(args) { 1176 | //Skip modules already defined. 1177 | if (!hasProp(defined, args[0])) { 1178 | getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); 1179 | } 1180 | } 1181 | 1182 | function removeListener(node, func, name, ieName) { 1183 | //Favor detachEvent because of IE9 1184 | //issue, see attachEvent/addEventListener comment elsewhere 1185 | //in this file. 1186 | if (node.detachEvent && !isOpera) { 1187 | //Probably IE. If not it will throw an error, which will be 1188 | //useful to know. 1189 | if (ieName) { 1190 | node.detachEvent(ieName, func); 1191 | } 1192 | } else { 1193 | node.removeEventListener(name, func, false); 1194 | } 1195 | } 1196 | 1197 | /** 1198 | * Given an event from a script node, get the requirejs info from it, 1199 | * and then removes the event listeners on the node. 1200 | * @param {Event} evt 1201 | * @returns {Object} 1202 | */ 1203 | function getScriptData(evt) { 1204 | //Using currentTarget instead of target for Firefox 2.0's sake. Not 1205 | //all old browsers will be supported, but this one was easy enough 1206 | //to support and still makes sense. 1207 | var node = evt.currentTarget || evt.srcElement; 1208 | 1209 | //Remove the listeners once here. 1210 | removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); 1211 | removeListener(node, context.onScriptError, 'error'); 1212 | 1213 | return { 1214 | node: node, 1215 | id: node && node.getAttribute('data-requiremodule') 1216 | }; 1217 | } 1218 | 1219 | function intakeDefines() { 1220 | var args; 1221 | 1222 | //Any defined modules in the global queue, intake them now. 1223 | takeGlobalQueue(); 1224 | 1225 | //Make sure any remaining defQueue items get properly processed. 1226 | while (defQueue.length) { 1227 | args = defQueue.shift(); 1228 | if (args[0] === null) { 1229 | return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); 1230 | } else { 1231 | //args are id, deps, factory. Should be normalized by the 1232 | //define() function. 1233 | callGetModule(args); 1234 | } 1235 | } 1236 | } 1237 | 1238 | context = { 1239 | config: config, 1240 | contextName: contextName, 1241 | registry: registry, 1242 | defined: defined, 1243 | urlFetched: urlFetched, 1244 | defQueue: defQueue, 1245 | Module: Module, 1246 | makeModuleMap: makeModuleMap, 1247 | nextTick: req.nextTick, 1248 | onError: onError, 1249 | 1250 | /** 1251 | * Set a configuration for the context. 1252 | * @param {Object} cfg config object to integrate. 1253 | */ 1254 | configure: function (cfg) { 1255 | //Make sure the baseUrl ends in a slash. 1256 | if (cfg.baseUrl) { 1257 | if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { 1258 | cfg.baseUrl += '/'; 1259 | } 1260 | } 1261 | 1262 | //Save off the paths since they require special processing, 1263 | //they are additive. 1264 | var shim = config.shim, 1265 | objs = { 1266 | paths: true, 1267 | bundles: true, 1268 | config: true, 1269 | map: true 1270 | }; 1271 | 1272 | eachProp(cfg, function (value, prop) { 1273 | if (objs[prop]) { 1274 | if (!config[prop]) { 1275 | config[prop] = {}; 1276 | } 1277 | mixin(config[prop], value, true, true); 1278 | } else { 1279 | config[prop] = value; 1280 | } 1281 | }); 1282 | 1283 | //Reverse map the bundles 1284 | if (cfg.bundles) { 1285 | eachProp(cfg.bundles, function (value, prop) { 1286 | each(value, function (v) { 1287 | if (v !== prop) { 1288 | bundlesMap[v] = prop; 1289 | } 1290 | }); 1291 | }); 1292 | } 1293 | 1294 | //Merge shim 1295 | if (cfg.shim) { 1296 | eachProp(cfg.shim, function (value, id) { 1297 | //Normalize the structure 1298 | if (isArray(value)) { 1299 | value = { 1300 | deps: value 1301 | }; 1302 | } 1303 | if ((value.exports || value.init) && !value.exportsFn) { 1304 | value.exportsFn = context.makeShimExports(value); 1305 | } 1306 | shim[id] = value; 1307 | }); 1308 | config.shim = shim; 1309 | } 1310 | 1311 | //Adjust packages if necessary. 1312 | if (cfg.packages) { 1313 | each(cfg.packages, function (pkgObj) { 1314 | var location, name; 1315 | 1316 | pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; 1317 | 1318 | name = pkgObj.name; 1319 | location = pkgObj.location; 1320 | if (location) { 1321 | config.paths[name] = pkgObj.location; 1322 | } 1323 | 1324 | //Save pointer to main module ID for pkg name. 1325 | //Remove leading dot in main, so main paths are normalized, 1326 | //and remove any trailing .js, since different package 1327 | //envs have different conventions: some use a module name, 1328 | //some use a file name. 1329 | config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main') 1330 | .replace(currDirRegExp, '') 1331 | .replace(jsSuffixRegExp, ''); 1332 | }); 1333 | } 1334 | 1335 | //If there are any "waiting to execute" modules in the registry, 1336 | //update the maps for them, since their info, like URLs to load, 1337 | //may have changed. 1338 | eachProp(registry, function (mod, id) { 1339 | //If module already has init called, since it is too 1340 | //late to modify them, and ignore unnormalized ones 1341 | //since they are transient. 1342 | if (!mod.inited && !mod.map.unnormalized) { 1343 | mod.map = makeModuleMap(id); 1344 | } 1345 | }); 1346 | 1347 | //If a deps array or a config callback is specified, then call 1348 | //require with those args. This is useful when require is defined as a 1349 | //config object before require.js is loaded. 1350 | if (cfg.deps || cfg.callback) { 1351 | context.require(cfg.deps || [], cfg.callback); 1352 | } 1353 | }, 1354 | 1355 | makeShimExports: function (value) { 1356 | function fn() { 1357 | var ret; 1358 | if (value.init) { 1359 | ret = value.init.apply(global, arguments); 1360 | } 1361 | return ret || (value.exports && getGlobal(value.exports)); 1362 | } 1363 | return fn; 1364 | }, 1365 | 1366 | makeRequire: function (relMap, options) { 1367 | options = options || {}; 1368 | 1369 | function localRequire(deps, callback, errback) { 1370 | var id, map, requireMod; 1371 | 1372 | if (options.enableBuildCallback && callback && isFunction(callback)) { 1373 | callback.__requireJsBuild = true; 1374 | } 1375 | 1376 | if (typeof deps === 'string') { 1377 | if (isFunction(callback)) { 1378 | //Invalid call 1379 | return onError(makeError('requireargs', 'Invalid require call'), errback); 1380 | } 1381 | 1382 | //If require|exports|module are requested, get the 1383 | //value for them from the special handlers. Caveat: 1384 | //this only works while module is being defined. 1385 | if (relMap && hasProp(handlers, deps)) { 1386 | return handlers[deps](registry[relMap.id]); 1387 | } 1388 | 1389 | //Synchronous access to one module. If require.get is 1390 | //available (as in the Node adapter), prefer that. 1391 | if (req.get) { 1392 | return req.get(context, deps, relMap, localRequire); 1393 | } 1394 | 1395 | //Normalize module name, if it contains . or .. 1396 | map = makeModuleMap(deps, relMap, false, true); 1397 | id = map.id; 1398 | 1399 | if (!hasProp(defined, id)) { 1400 | return onError(makeError('notloaded', 'Module name "' + 1401 | id + 1402 | '" has not been loaded yet for context: ' + 1403 | contextName + 1404 | (relMap ? '' : '. Use require([])'))); 1405 | } 1406 | return defined[id]; 1407 | } 1408 | 1409 | //Grab defines waiting in the global queue. 1410 | intakeDefines(); 1411 | 1412 | //Mark all the dependencies as needing to be loaded. 1413 | context.nextTick(function () { 1414 | //Some defines could have been added since the 1415 | //require call, collect them. 1416 | intakeDefines(); 1417 | 1418 | requireMod = getModule(makeModuleMap(null, relMap)); 1419 | 1420 | //Store if map config should be applied to this require 1421 | //call for dependencies. 1422 | requireMod.skipMap = options.skipMap; 1423 | 1424 | requireMod.init(deps, callback, errback, { 1425 | enabled: true 1426 | }); 1427 | 1428 | checkLoaded(); 1429 | }); 1430 | 1431 | return localRequire; 1432 | } 1433 | 1434 | mixin(localRequire, { 1435 | isBrowser: isBrowser, 1436 | 1437 | /** 1438 | * Converts a module name + .extension into an URL path. 1439 | * *Requires* the use of a module name. It does not support using 1440 | * plain URLs like nameToUrl. 1441 | */ 1442 | toUrl: function (moduleNamePlusExt) { 1443 | var ext, 1444 | index = moduleNamePlusExt.lastIndexOf('.'), 1445 | segment = moduleNamePlusExt.split('/')[0], 1446 | isRelative = segment === '.' || segment === '..'; 1447 | 1448 | //Have a file extension alias, and it is not the 1449 | //dots from a relative path. 1450 | if (index !== -1 && (!isRelative || index > 1)) { 1451 | ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); 1452 | moduleNamePlusExt = moduleNamePlusExt.substring(0, index); 1453 | } 1454 | 1455 | return context.nameToUrl(normalize(moduleNamePlusExt, 1456 | relMap && relMap.id, true), ext, true); 1457 | }, 1458 | 1459 | defined: function (id) { 1460 | return hasProp(defined, makeModuleMap(id, relMap, false, true).id); 1461 | }, 1462 | 1463 | specified: function (id) { 1464 | id = makeModuleMap(id, relMap, false, true).id; 1465 | return hasProp(defined, id) || hasProp(registry, id); 1466 | } 1467 | }); 1468 | 1469 | //Only allow undef on top level require calls 1470 | if (!relMap) { 1471 | localRequire.undef = function (id) { 1472 | //Bind any waiting define() calls to this context, 1473 | //fix for #408 1474 | takeGlobalQueue(); 1475 | 1476 | var map = makeModuleMap(id, relMap, true), 1477 | mod = getOwn(registry, id); 1478 | 1479 | removeScript(id); 1480 | 1481 | delete defined[id]; 1482 | delete urlFetched[map.url]; 1483 | delete undefEvents[id]; 1484 | 1485 | //Clean queued defines too. Go backwards 1486 | //in array so that the splices do not 1487 | //mess up the iteration. 1488 | eachReverse(defQueue, function(args, i) { 1489 | if(args[0] === id) { 1490 | defQueue.splice(i, 1); 1491 | } 1492 | }); 1493 | 1494 | if (mod) { 1495 | //Hold on to listeners in case the 1496 | //module will be attempted to be reloaded 1497 | //using a different config. 1498 | if (mod.events.defined) { 1499 | undefEvents[id] = mod.events; 1500 | } 1501 | 1502 | cleanRegistry(id); 1503 | } 1504 | }; 1505 | } 1506 | 1507 | return localRequire; 1508 | }, 1509 | 1510 | /** 1511 | * Called to enable a module if it is still in the registry 1512 | * awaiting enablement. A second arg, parent, the parent module, 1513 | * is passed in for context, when this method is overridden by 1514 | * the optimizer. Not shown here to keep code compact. 1515 | */ 1516 | enable: function (depMap) { 1517 | var mod = getOwn(registry, depMap.id); 1518 | if (mod) { 1519 | getModule(depMap).enable(); 1520 | } 1521 | }, 1522 | 1523 | /** 1524 | * Internal method used by environment adapters to complete a load event. 1525 | * A load event could be a script load or just a load pass from a synchronous 1526 | * load call. 1527 | * @param {String} moduleName the name of the module to potentially complete. 1528 | */ 1529 | completeLoad: function (moduleName) { 1530 | var found, args, mod, 1531 | shim = getOwn(config.shim, moduleName) || {}, 1532 | shExports = shim.exports; 1533 | 1534 | takeGlobalQueue(); 1535 | 1536 | while (defQueue.length) { 1537 | args = defQueue.shift(); 1538 | if (args[0] === null) { 1539 | args[0] = moduleName; 1540 | //If already found an anonymous module and bound it 1541 | //to this name, then this is some other anon module 1542 | //waiting for its completeLoad to fire. 1543 | if (found) { 1544 | break; 1545 | } 1546 | found = true; 1547 | } else if (args[0] === moduleName) { 1548 | //Found matching define call for this script! 1549 | found = true; 1550 | } 1551 | 1552 | callGetModule(args); 1553 | } 1554 | 1555 | //Do this after the cycle of callGetModule in case the result 1556 | //of those calls/init calls changes the registry. 1557 | mod = getOwn(registry, moduleName); 1558 | 1559 | if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { 1560 | if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { 1561 | if (hasPathFallback(moduleName)) { 1562 | return; 1563 | } else { 1564 | return onError(makeError('nodefine', 1565 | 'No define call for ' + moduleName, 1566 | null, 1567 | [moduleName])); 1568 | } 1569 | } else { 1570 | //A script that does not call define(), so just simulate 1571 | //the call for it. 1572 | callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); 1573 | } 1574 | } 1575 | 1576 | checkLoaded(); 1577 | }, 1578 | 1579 | /** 1580 | * Converts a module name to a file path. Supports cases where 1581 | * moduleName may actually be just an URL. 1582 | * Note that it **does not** call normalize on the moduleName, 1583 | * it is assumed to have already been normalized. This is an 1584 | * internal API, not a public one. Use toUrl for the public API. 1585 | */ 1586 | nameToUrl: function (moduleName, ext, skipExt) { 1587 | var paths, syms, i, parentModule, url, 1588 | parentPath, bundleId, 1589 | pkgMain = getOwn(config.pkgs, moduleName); 1590 | 1591 | if (pkgMain) { 1592 | moduleName = pkgMain; 1593 | } 1594 | 1595 | bundleId = getOwn(bundlesMap, moduleName); 1596 | 1597 | if (bundleId) { 1598 | return context.nameToUrl(bundleId, ext, skipExt); 1599 | } 1600 | 1601 | //If a colon is in the URL, it indicates a protocol is used and it is just 1602 | //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) 1603 | //or ends with .js, then assume the user meant to use an url and not a module id. 1604 | //The slash is important for protocol-less URLs as well as full paths. 1605 | if (req.jsExtRegExp.test(moduleName)) { 1606 | //Just a plain path, not module name lookup, so just return it. 1607 | //Add extension if it is included. This is a bit wonky, only non-.js things pass 1608 | //an extension, this method probably needs to be reworked. 1609 | url = moduleName + (ext || ''); 1610 | } else { 1611 | //A module that needs to be converted to a path. 1612 | paths = config.paths; 1613 | 1614 | syms = moduleName.split('/'); 1615 | //For each module name segment, see if there is a path 1616 | //registered for it. Start with most specific name 1617 | //and work up from it. 1618 | for (i = syms.length; i > 0; i -= 1) { 1619 | parentModule = syms.slice(0, i).join('/'); 1620 | 1621 | parentPath = getOwn(paths, parentModule); 1622 | if (parentPath) { 1623 | //If an array, it means there are a few choices, 1624 | //Choose the one that is desired 1625 | if (isArray(parentPath)) { 1626 | parentPath = parentPath[0]; 1627 | } 1628 | syms.splice(0, i, parentPath); 1629 | break; 1630 | } 1631 | } 1632 | 1633 | //Join the path parts together, then figure out if baseUrl is needed. 1634 | url = syms.join('/'); 1635 | url += (ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js')); 1636 | url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; 1637 | } 1638 | 1639 | return config.urlArgs ? url + 1640 | ((url.indexOf('?') === -1 ? '?' : '&') + 1641 | config.urlArgs) : url; 1642 | }, 1643 | 1644 | //Delegates to req.load. Broken out as a separate function to 1645 | //allow overriding in the optimizer. 1646 | load: function (id, url) { 1647 | req.load(context, id, url); 1648 | }, 1649 | 1650 | /** 1651 | * Executes a module callback function. Broken out as a separate function 1652 | * solely to allow the build system to sequence the files in the built 1653 | * layer in the right sequence. 1654 | * 1655 | * @private 1656 | */ 1657 | execCb: function (name, callback, args, exports) { 1658 | return callback.apply(exports, args); 1659 | }, 1660 | 1661 | /** 1662 | * callback for script loads, used to check status of loading. 1663 | * 1664 | * @param {Event} evt the event from the browser for the script 1665 | * that was loaded. 1666 | */ 1667 | onScriptLoad: function (evt) { 1668 | //Using currentTarget instead of target for Firefox 2.0's sake. Not 1669 | //all old browsers will be supported, but this one was easy enough 1670 | //to support and still makes sense. 1671 | if (evt.type === 'load' || 1672 | (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { 1673 | //Reset interactive script so a script node is not held onto for 1674 | //to long. 1675 | interactiveScript = null; 1676 | 1677 | //Pull out the name of the module and the context. 1678 | var data = getScriptData(evt); 1679 | context.completeLoad(data.id); 1680 | } 1681 | }, 1682 | 1683 | /** 1684 | * Callback for script errors. 1685 | */ 1686 | onScriptError: function (evt) { 1687 | var data = getScriptData(evt); 1688 | if (!hasPathFallback(data.id)) { 1689 | return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id])); 1690 | } 1691 | } 1692 | }; 1693 | 1694 | context.require = context.makeRequire(); 1695 | return context; 1696 | } 1697 | 1698 | /** 1699 | * Main entry point. 1700 | * 1701 | * If the only argument to require is a string, then the module that 1702 | * is represented by that string is fetched for the appropriate context. 1703 | * 1704 | * If the first argument is an array, then it will be treated as an array 1705 | * of dependency string names to fetch. An optional function callback can 1706 | * be specified to execute when all of those dependencies are available. 1707 | * 1708 | * Make a local req variable to help Caja compliance (it assumes things 1709 | * on a require that are not standardized), and to give a short 1710 | * name for minification/local scope use. 1711 | */ 1712 | req = requirejs = function (deps, callback, errback, optional) { 1713 | 1714 | //Find the right context, use default 1715 | var context, config, 1716 | contextName = defContextName; 1717 | 1718 | // Determine if have config object in the call. 1719 | if (!isArray(deps) && typeof deps !== 'string') { 1720 | // deps is a config object 1721 | config = deps; 1722 | if (isArray(callback)) { 1723 | // Adjust args if there are dependencies 1724 | deps = callback; 1725 | callback = errback; 1726 | errback = optional; 1727 | } else { 1728 | deps = []; 1729 | } 1730 | } 1731 | 1732 | if (config && config.context) { 1733 | contextName = config.context; 1734 | } 1735 | 1736 | context = getOwn(contexts, contextName); 1737 | if (!context) { 1738 | context = contexts[contextName] = req.s.newContext(contextName); 1739 | } 1740 | 1741 | if (config) { 1742 | context.configure(config); 1743 | } 1744 | 1745 | return context.require(deps, callback, errback); 1746 | }; 1747 | 1748 | /** 1749 | * Support require.config() to make it easier to cooperate with other 1750 | * AMD loaders on globally agreed names. 1751 | */ 1752 | req.config = function (config) { 1753 | return req(config); 1754 | }; 1755 | 1756 | /** 1757 | * Execute something after the current tick 1758 | * of the event loop. Override for other envs 1759 | * that have a better solution than setTimeout. 1760 | * @param {Function} fn function to execute later. 1761 | */ 1762 | req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { 1763 | setTimeout(fn, 4); 1764 | } : function (fn) { fn(); }; 1765 | 1766 | /** 1767 | * Export require as a global, but only if it does not already exist. 1768 | */ 1769 | if (!require) { 1770 | require = req; 1771 | } 1772 | 1773 | req.version = version; 1774 | 1775 | //Used to filter out dependencies that are already paths. 1776 | req.jsExtRegExp = /^\/|:|\?|\.js$/; 1777 | req.isBrowser = isBrowser; 1778 | s = req.s = { 1779 | contexts: contexts, 1780 | newContext: newContext 1781 | }; 1782 | 1783 | //Create default context. 1784 | req({}); 1785 | 1786 | //Exports some context-sensitive methods on global require. 1787 | each([ 1788 | 'toUrl', 1789 | 'undef', 1790 | 'defined', 1791 | 'specified' 1792 | ], function (prop) { 1793 | //Reference from contexts instead of early binding to default context, 1794 | //so that during builds, the latest instance of the default context 1795 | //with its config gets used. 1796 | req[prop] = function () { 1797 | var ctx = contexts[defContextName]; 1798 | return ctx.require[prop].apply(ctx, arguments); 1799 | }; 1800 | }); 1801 | 1802 | if (isBrowser) { 1803 | head = s.head = document.getElementsByTagName('head')[0]; 1804 | //If BASE tag is in play, using appendChild is a problem for IE6. 1805 | //When that browser dies, this can be removed. Details in this jQuery bug: 1806 | //http://dev.jquery.com/ticket/2709 1807 | baseElement = document.getElementsByTagName('base')[0]; 1808 | if (baseElement) { 1809 | head = s.head = baseElement.parentNode; 1810 | } 1811 | } 1812 | 1813 | /** 1814 | * Any errors that require explicitly generates will be passed to this 1815 | * function. Intercept/override it if you want custom error handling. 1816 | * @param {Error} err the error object. 1817 | */ 1818 | req.onError = defaultOnError; 1819 | 1820 | /** 1821 | * Creates the node for the load command. Only used in browser envs. 1822 | */ 1823 | req.createNode = function (config, moduleName, url) { 1824 | var node = config.xhtml ? 1825 | document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : 1826 | document.createElement('script'); 1827 | node.type = config.scriptType || 'text/javascript'; 1828 | node.charset = 'utf-8'; 1829 | node.async = true; 1830 | return node; 1831 | }; 1832 | 1833 | /** 1834 | * Does the request to load a module for the browser case. 1835 | * Make this a separate function to allow other environments 1836 | * to override it. 1837 | * 1838 | * @param {Object} context the require context to find state. 1839 | * @param {String} moduleName the name of the module. 1840 | * @param {Object} url the URL to the module. 1841 | */ 1842 | req.load = function (context, moduleName, url) { 1843 | var config = (context && context.config) || {}, 1844 | node; 1845 | if (isBrowser) { 1846 | //In the browser so use a script tag 1847 | node = req.createNode(config, moduleName, url); 1848 | 1849 | node.setAttribute('data-requirecontext', context.contextName); 1850 | node.setAttribute('data-requiremodule', moduleName); 1851 | 1852 | //Set up load listener. Test attachEvent first because IE9 has 1853 | //a subtle issue in its addEventListener and script onload firings 1854 | //that do not match the behavior of all other browsers with 1855 | //addEventListener support, which fire the onload event for a 1856 | //script right after the script execution. See: 1857 | //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution 1858 | //UNFORTUNATELY Opera implements attachEvent but does not follow the script 1859 | //script execution mode. 1860 | if (node.attachEvent && 1861 | //Check if node.attachEvent is artificially added by custom script or 1862 | //natively supported by browser 1863 | //read https://github.com/jrburke/requirejs/issues/187 1864 | //if we can NOT find [native code] then it must NOT natively supported. 1865 | //in IE8, node.attachEvent does not have toString() 1866 | //Note the test for "[native code" with no closing brace, see: 1867 | //https://github.com/jrburke/requirejs/issues/273 1868 | !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && 1869 | !isOpera) { 1870 | //Probably IE. IE (at least 6-8) do not fire 1871 | //script onload right after executing the script, so 1872 | //we cannot tie the anonymous define call to a name. 1873 | //However, IE reports the script as being in 'interactive' 1874 | //readyState at the time of the define call. 1875 | useInteractive = true; 1876 | 1877 | node.attachEvent('onreadystatechange', context.onScriptLoad); 1878 | //It would be great to add an error handler here to catch 1879 | //404s in IE9+. However, onreadystatechange will fire before 1880 | //the error handler, so that does not help. If addEventListener 1881 | //is used, then IE will fire error before load, but we cannot 1882 | //use that pathway given the connect.microsoft.com issue 1883 | //mentioned above about not doing the 'script execute, 1884 | //then fire the script load event listener before execute 1885 | //next script' that other browsers do. 1886 | //Best hope: IE10 fixes the issues, 1887 | //and then destroys all installs of IE 6-9. 1888 | //node.attachEvent('onerror', context.onScriptError); 1889 | } else { 1890 | node.addEventListener('load', context.onScriptLoad, false); 1891 | node.addEventListener('error', context.onScriptError, false); 1892 | } 1893 | node.src = url; 1894 | 1895 | //For some cache cases in IE 6-8, the script executes before the end 1896 | //of the appendChild execution, so to tie an anonymous define 1897 | //call to the module name (which is stored on the node), hold on 1898 | //to a reference to this node, but clear after the DOM insertion. 1899 | currentlyAddingScript = node; 1900 | if (baseElement) { 1901 | head.insertBefore(node, baseElement); 1902 | } else { 1903 | head.appendChild(node); 1904 | } 1905 | currentlyAddingScript = null; 1906 | 1907 | return node; 1908 | } else if (isWebWorker) { 1909 | try { 1910 | //In a web worker, use importScripts. This is not a very 1911 | //efficient use of importScripts, importScripts will block until 1912 | //its script is downloaded and evaluated. However, if web workers 1913 | //are in play, the expectation that a build has been done so that 1914 | //only one script needs to be loaded anyway. This may need to be 1915 | //reevaluated if other use cases become common. 1916 | importScripts(url); 1917 | 1918 | //Account for anonymous modules 1919 | context.completeLoad(moduleName); 1920 | } catch (e) { 1921 | context.onError(makeError('importscripts', 1922 | 'importScripts failed for ' + 1923 | moduleName + ' at ' + url, 1924 | e, 1925 | [moduleName])); 1926 | } 1927 | } 1928 | }; 1929 | 1930 | function getInteractiveScript() { 1931 | if (interactiveScript && interactiveScript.readyState === 'interactive') { 1932 | return interactiveScript; 1933 | } 1934 | 1935 | eachReverse(scripts(), function (script) { 1936 | if (script.readyState === 'interactive') { 1937 | return (interactiveScript = script); 1938 | } 1939 | }); 1940 | return interactiveScript; 1941 | } 1942 | 1943 | //Look for a data-main script attribute, which could also adjust the baseUrl. 1944 | if (isBrowser && !cfg.skipDataMain) { 1945 | //Figure out baseUrl. Get it from the script tag with require.js in it. 1946 | eachReverse(scripts(), function (script) { 1947 | //Set the 'head' where we can append children by 1948 | //using the script's parent. 1949 | if (!head) { 1950 | head = script.parentNode; 1951 | } 1952 | 1953 | //Look for a data-main attribute to set main script for the page 1954 | //to load. If it is there, the path to data main becomes the 1955 | //baseUrl, if it is not already set. 1956 | dataMain = script.getAttribute('data-main'); 1957 | if (dataMain) { 1958 | //Preserve dataMain in case it is a path (i.e. contains '?') 1959 | mainScript = dataMain; 1960 | 1961 | //Set final baseUrl if there is not already an explicit one. 1962 | if (!cfg.baseUrl) { 1963 | //Pull off the directory of data-main for use as the 1964 | //baseUrl. 1965 | src = mainScript.split('/'); 1966 | mainScript = src.pop(); 1967 | subPath = src.length ? src.join('/') + '/' : './'; 1968 | 1969 | cfg.baseUrl = subPath; 1970 | } 1971 | 1972 | //Strip off any trailing .js since mainScript is now 1973 | //like a module name. 1974 | mainScript = mainScript.replace(jsSuffixRegExp, ''); 1975 | 1976 | //If mainScript is still a path, fall back to dataMain 1977 | if (req.jsExtRegExp.test(mainScript)) { 1978 | mainScript = dataMain; 1979 | } 1980 | 1981 | //Put the data-main script in the files to load. 1982 | cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript]; 1983 | 1984 | return true; 1985 | } 1986 | }); 1987 | } 1988 | 1989 | /** 1990 | * The function that handles definitions of modules. Differs from 1991 | * require() in that a string for the module should be the first argument, 1992 | * and the function to execute after dependencies are loaded should 1993 | * return a value to define the module corresponding to the first argument's 1994 | * name. 1995 | */ 1996 | define = function (name, deps, callback) { 1997 | var node, context; 1998 | 1999 | //Allow for anonymous modules 2000 | if (typeof name !== 'string') { 2001 | //Adjust args appropriately 2002 | callback = deps; 2003 | deps = name; 2004 | name = null; 2005 | } 2006 | 2007 | //This module may not have dependencies 2008 | if (!isArray(deps)) { 2009 | callback = deps; 2010 | deps = null; 2011 | } 2012 | 2013 | //If no name, and callback is a function, then figure out if it a 2014 | //CommonJS thing with dependencies. 2015 | if (!deps && isFunction(callback)) { 2016 | deps = []; 2017 | //Remove comments from the callback string, 2018 | //look for require calls, and pull them into the dependencies, 2019 | //but only if there are function args. 2020 | if (callback.length) { 2021 | callback 2022 | .toString() 2023 | .replace(commentRegExp, '') 2024 | .replace(cjsRequireRegExp, function (match, dep) { 2025 | deps.push(dep); 2026 | }); 2027 | 2028 | //May be a CommonJS thing even without require calls, but still 2029 | //could use exports, and module. Avoid doing exports and module 2030 | //work though if it just needs require. 2031 | //REQUIRES the function to expect the CommonJS variables in the 2032 | //order listed below. 2033 | deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); 2034 | } 2035 | } 2036 | 2037 | //If in IE 6-8 and hit an anonymous define() call, do the interactive 2038 | //work. 2039 | if (useInteractive) { 2040 | node = currentlyAddingScript || getInteractiveScript(); 2041 | if (node) { 2042 | if (!name) { 2043 | name = node.getAttribute('data-requiremodule'); 2044 | } 2045 | context = contexts[node.getAttribute('data-requirecontext')]; 2046 | } 2047 | } 2048 | 2049 | //Always save off evaluating the def call until the script onload handler. 2050 | //This allows multiple modules to be in a file without prematurely 2051 | //tracing dependencies, and allows for anonymous module support, 2052 | //where the module name is not known until the script onload event 2053 | //occurs. If no context, use the global queue, and get it processed 2054 | //in the onscript load callback. 2055 | (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); 2056 | }; 2057 | 2058 | define.amd = { 2059 | jQuery: true 2060 | }; 2061 | 2062 | 2063 | /** 2064 | * Executes the text. Normally just uses eval, but can be modified 2065 | * to use a better, environment-specific call. Only used for transpiling 2066 | * loader plugins, not for plain JS modules. 2067 | * @param {String} text the text to execute/evaluate. 2068 | */ 2069 | req.exec = function (text) { 2070 | /*jslint evil: true */ 2071 | return eval(text); 2072 | }; 2073 | 2074 | //Set up with config info. 2075 | req(cfg); 2076 | }(this)); 2077 | --------------------------------------------------------------------------------