From 0de3da048509062488bc9ee3a7841fb6760ef355 Mon Sep 17 00:00:00 2001 From: Bob Monteverde Date: Wed, 18 Jul 2012 01:49:57 -0400 Subject: [PATCH] Lots of tweaks to historicalStockChart, still more to come --- examples/historicalStockChart.html | 12 ++-- src/models/historicalStockChart.js | 112 ++++++++++++++++++++++++++++- src/models/ohlcBar.js | 10 +-- 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/examples/historicalStockChart.html b/examples/historicalStockChart.html index 81393d2..14f2f3e 100644 --- a/examples/historicalStockChart.html +++ b/examples/historicalStockChart.html @@ -114,9 +114,10 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { { "key": "Volume", "bar": true, - "values": data.map(function(d) { + "values": data.map(function(d,i) { return { 'x': Date.parse(d.Date), + 'dx': i, 'y': d.Volume / 1000 //TODO: figure out why y domain was maxing out at 9933800 } }) @@ -124,9 +125,10 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { { "key": "Price", "bar": false, - "values": data.map(function(d) { + "values": data.map(function(d,i) { return { 'x': Date.parse(d.Date), + 'dx': i, 'y': d.Close, 'open': d.Open, 'close': d.Close, @@ -145,7 +147,7 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { nv.addGraph(function() { var chart = nv.models.historicalStockChart() //.margin({top: 30, right: 70, bottom: 50, left: 55}) - .x(function(d,i) { return i }) + .x(function(d,i) { return d.dx }) .color(d3.scale.category10().range()); // Use if we are removing weekends/holidays @@ -153,6 +155,7 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { //.tickPadding(7) .tickFormat(function(d) { return ''; + d = parseInt(d); var dx = lineData[0].values[d] && lineData[0].values[d].x || 0; return d3.time.format('%x')(new Date(dx)) }); @@ -160,6 +163,7 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { chart.xAxis2 .tickPadding(7) .tickFormat(function(d) { + d = parseInt(d); var dx = lineData[0].values[d] && lineData[0].values[d].x || 0; return d3.time.format('%x')(new Date(dx)) }); @@ -167,7 +171,7 @@ function getHistoricalStockData(symbol, startDate, endDate, frequency) { chart.xAxis3 .tickPadding(7) .tickFormat(function(d) { - var dx = lineData[0].values[d] && lineData[0].values[d].x || 0; + var dx = lineData[0].values[d] && lineData[0].values[parseInt(d)].x || 0; return d3.time.format('%x')(new Date(dx)) }); diff --git a/src/models/historicalStockChart.js b/src/models/historicalStockChart.js index c8f56a7..39b5ef6 100644 --- a/src/models/historicalStockChart.js +++ b/src/models/historicalStockChart.js @@ -21,7 +21,8 @@ nv.models.historicalStockChart = function() { var stocks = nv.models.ohlcBar(), bars = nv.models.historicalBar(), lines = nv.models.line().interactive(false), - x = d3.scale.linear(), // needs to be both line and historicalBar x Axis + //x = d3.scale.linear(), // needs to be both line and historicalBar x Axis + x = stocks.xScale(), x3 = lines.xScale(), y1 = bars.yScale(), y2 = stocks.yScale(), @@ -33,7 +34,8 @@ nv.models.historicalStockChart = function() { yAxis2 = nv.models.axis().scale(y2).orient('left'), yAxis3 = nv.models.axis().scale(y3).orient('left'), legend = nv.models.legend().height(30), - dispatch = d3.dispatch('tooltipShow', 'tooltipHide'); + dispatch = d3.dispatch('tooltipShow', 'tooltipHide'), + brush = d3.svg.brush().x(x3); var showTooltip = function(e, offsetElement) { var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ), @@ -60,6 +62,9 @@ nv.models.historicalStockChart = function() { availableHeight3 = height3 - margin3.top - margin3.bottom; + brush.on('brush', onBrush); + + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); var dataStocks = data.filter(function(d) { return !d.disabled && !d.bar }); @@ -68,6 +73,9 @@ nv.models.historicalStockChart = function() { //TODO: try to remove x scale computation from this layer + + + /* var series1 = data.filter(function(d) { return !d.disabled && d.bar }) .map(function(d) { return d.values.map(function(d,i) { @@ -87,7 +95,6 @@ nv.models.historicalStockChart = function() { - /* x .domain(d3.extent(d3.merge(data.map(function(d) { return d.values })), getX )) .range([0, availableWidth]); @@ -113,6 +120,7 @@ nv.models.historicalStockChart = function() { gEnter.append('g').attr('class', 'stocksWrap'); gEnter.append('g').attr('class', 'linesWrap'); gEnter.append('g').attr('class', 'legendWrap'); + gEnter.append('g').attr('class', 'x brush'); @@ -182,6 +190,16 @@ nv.models.historicalStockChart = function() { d3.transition(linesWrap).call(lines); + gBrush = g.select('.x.brush') + .attr('transform', 'translate(0,' + (availableHeight + margin.bottom + height2) + ')') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight3); + gBrush.selectAll(".resize").append("path").attr("d", resizePath); + + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); @@ -282,6 +300,94 @@ nv.models.historicalStockChart = function() { chart.update = function() { selection.transition().call(chart) }; chart.container = this; // I need a reference to the container in order to have outside code check if the chart is visible or not + + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == "e"), + x = e ? 1 : -1, + y = availableHeight3 / 3; + return "M" + (.5 * x) + "," + y + + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + + "V" + (2 * y - 6) + + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y) + + "Z" + + "M" + (2.5 * x) + "," + (y + 8) + + "V" + (2 * y - 8) + + "M" + (4.5 * x) + "," + (y + 8) + + "V" + (2 * y - 8); + } + + + + function onBrush() { + updateFocus(); + + //linesWrap.call(lines) + //var focusLinesWrap = g.select('.focus .linesWrap') + //g.select('.x.axis').call(xAxis); + //g.select('.y.axis').call(yAxis1); + } + + function updateFocus() { + //nv.log(brush.empty(), brush.extent(), x3(brush.extent()[0]), x3(brush.extent()[1])); + + var extent = brush.empty() ? x3.domain() : brush.extent(); + + var stocksWrap = g.select('.stocksWrap') + .datum( + [{ + key: dataStocks[0].key, + values: dataStocks[0].values.filter(function(d,i) { + return getX(d,i) > extent[0] && getX(d,i) < extent[1]; + }) + }] + ); + + + var barsWrap = g.select('.barsWrap') + .attr('transform', 'translate(0,' + (availableHeight + margin.bottom) + ')') + .datum( + [{ + key: dataBars[0].key, + values: dataBars[0].values.filter(function(d,i) { + return getX(d,i) > extent[0] && getX(d,i) < extent[1]; + }) + }] + ); + + d3.transition(barsWrap).call(bars); + d3.transition(stocksWrap).call(stocks); + + d3.transition(g.select('.x.axis')) + .call(xAxis); + d3.transition(g.select('.x2.axis')) + .call(xAxis2); + + d3.transition(g.select('.y1.axis')) + .call(yAxis1); + d3.transition(g.select('.y2.axis')) + .call(yAxis2); + + /* + var yDomain = brush.empty() ? y3.domain() : d3.extent(d3.merge(dataStocks.map(function(d) { return d.values })).filter(function(d) { + return lines.x()(d) >= brush.extent()[0] && lines.x()(d) <= brush.extent()[1]; + }), lines.y()); //This doesn't account for the 1 point before and the 1 point after the domain. Would fix, but likely need to change entire methodology here + + if (typeof yDomain[0] == 'undefined') yDomain = y3.domain(); //incase the brush doesn't cover a single point + + + x.domain(brush.empty() ? x3.domain() : brush.extent()); + y1.domain(yDomain); + */ + + //lines.xDomain(x.domain()); + //lines.yDomain(y1.domain()); + } + + + + }); return chart; diff --git a/src/models/ohlcBar.js b/src/models/ohlcBar.js index 61c686d..7c64424 100644 --- a/src/models/ohlcBar.js +++ b/src/models/ohlcBar.js @@ -100,11 +100,6 @@ nv.models.ohlcBar = function() { //.attr('x', 0 ) //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }) - .attr('d', function(d,i) { - var w = (availableWidth / data[0].values.length) * .9; - //nv.log(this, getOpen(d,i), getClose(d,i), y(getOpen(d,i)), y(getClose(d,i))); - return 'm0,0l0,' + (y(getOpen(d,i)) - y(getHigh(d,i))) + 'l' + (-w/2) + ',0l' + (w/2) + ',0l0,' + (y(getLow(d,i)) - y(getOpen(d,i))) +'l0,'+ (y(getClose(d,i)) - y(getLow(d,i))) +',l' + (w/2) + ',0l' + (-w/2) + ',0z'; - }) .on('mouseover', function(d,i) { d3.select(this).classed('hover', true); dispatch.elementMouseover({ @@ -155,6 +150,11 @@ nv.models.ohlcBar = function() { ticks .attr('class', function(d,i) { return getOpen(d,i) > getClose(d,i) ? 'tick negative' : 'tick positive'}) .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + //nv.log(this, getOpen(d,i), getClose(d,i), y(getOpen(d,i)), y(getClose(d,i))); + return 'm0,0l0,' + (y(getOpen(d,i)) - y(getHigh(d,i))) + 'l' + (-w/2) + ',0l' + (w/2) + ',0l0,' + (y(getLow(d,i)) - y(getOpen(d,i))) +'l0,'+ (y(getClose(d,i)) - y(getLow(d,i))) +',l' + (w/2) + ',0l' + (-w/2) + ',0z'; + }) //.attr('width', (availableWidth / data[0].values.length) * .9 )