|
r$ = React.createElement |
|
|
|
parseDate = d3.time.format("%d-%b-%y").parse |
|
|
|
D3LineGraph = () -> |
|
xScale = null |
|
yScale = null |
|
data = [] |
|
|
|
line = d3.svg.line() |
|
.x (d) -> xScale(d.x) |
|
.y (d) -> yScale(d.y) |
|
|
|
|
|
graph = (el) -> |
|
console.log data |
|
lineUpdate = el.selectAll(".line").data([data]) |
|
lineEnter = lineUpdate.enter().append("g").attr("class", "line") |
|
|
|
lineEnter.append("path") |
|
.attr("d", line) |
|
|
|
lineUpdate.exit().remove() |
|
|
|
lineUpdate.select("path") |
|
.attr("d", line) |
|
|
|
graph.xScale = (val) -> |
|
if arguments.length == 0 |
|
xScale |
|
else |
|
xScale = val |
|
line = d3.svg.line() |
|
.x (d) -> xScale(d.x) |
|
.y (d) -> yScale(d.y) |
|
@ |
|
|
|
graph.yScale = (val) -> |
|
if arguments.length == 0 |
|
yScale |
|
else |
|
yScale = val |
|
line = d3.svg.line() |
|
.x (d) -> xScale(d.x) |
|
.y (d) -> yScale(d.y) |
|
@ |
|
|
|
graph.data = (val) -> |
|
if arguments.length == 0 |
|
data |
|
else |
|
data = val |
|
@ |
|
|
|
graph |
|
|
|
App = React.createClass |
|
displayName: "App" |
|
|
|
getInitialState: -> |
|
width: 600 |
|
height: 400 |
|
margin: { top: 20, right: 40, bottom: 40, left: 60 } |
|
data: {} |
|
selected: {} |
|
|
|
componentDidMount: -> |
|
@handleResize() |
|
window.addEventListener("resize", @handleResize) |
|
|
|
componentWillUnmount: -> |
|
window.removeEventListener("resize", @handleResize) |
|
|
|
handleResize: -> |
|
@setState |
|
width: ReactDOM.findDOMNode(@).offsetWidth #- @state.margin.left - @state.margin.right |
|
height: ReactDOM.findDOMNode(@).offsetHeight #- @state.margin.top - @state.margin.bottom |
|
|
|
getSelected: -> |
|
_.chain(@state.selected) |
|
.pairs() |
|
.filter (d) -> d[1] |
|
.map (d) -> d[0] |
|
.value() |
|
|
|
parseData: -> |
|
_.chain(@getSelected()) |
|
.map (ticker) => |
|
[ |
|
ticker, |
|
(@state.data[ticker] ? []).map (row) -> |
|
x: parseDate(row.Date) |
|
y: +row.Close |
|
] |
|
.object() |
|
.value() |
|
|
|
xScale: -> |
|
width = @state.width - @state.margin.left - @state.margin.right |
|
selected = @getSelected() |
|
data = @parseData() |
|
min = d3.min(selected.map (s) => d3.min(data[s], (d) -> d.x)) |
|
max = d3.max(selected.map (s) => d3.max(data[s], (d) -> d.x)) |
|
|
|
d3.time.scale() |
|
.domain [min, max] |
|
.range [0, width] |
|
|
|
yScale: -> |
|
height = @state.height - @state.margin.top - @state.margin.bottom |
|
selected = @getSelected() |
|
data = @parseData() |
|
min = d3.min(selected.map (s) => d3.min(data[s], (d) -> d.y)) |
|
max = d3.max(selected.map (s) => d3.max(data[s], (d) -> d.y)) |
|
d3.scale.linear() |
|
.domain [min, max] |
|
.range [height, 0] |
|
|
|
handleChange: (e) -> |
|
newState = {} |
|
name = e.target.name |
|
value = e.target.checked |
|
newState[name] = value |
|
@setState selected: _.extend {}, @state.selected, newState |
|
|
|
if value && !@state.data[name]? |
|
console.log "z_#{name}.csv" |
|
d3.csv "z_#{name}.csv", (data) => |
|
console.log(name, value, data) |
|
newData = {} |
|
newData[name] = data |
|
@setState data: _.extend {}, @state.data, newData |
|
|
|
render: -> |
|
selected = @getSelected() |
|
data = @parseData() |
|
xScale = @xScale() |
|
yScale = @yScale() |
|
|
|
console.log data |
|
|
|
r$ "div", |
|
{} |
|
r$ "div", |
|
{} |
|
r$ "input", type: "checkbox", name: "amzn", checked: @state.selected.amzn, onChange: @handleChange |
|
"Amazon" |
|
r$ "div", |
|
{} |
|
r$ "input", type: "checkbox", name: "nflx", checked: @state.selected.nflx, onChange: @handleChange |
|
"Netflix" |
|
r$ "svg", |
|
className: "chart bar-chart" |
|
width: @state.width |
|
height: @state.height |
|
r$ "g", |
|
ref: "g-chart" |
|
className: "chart" |
|
transform: "translate(#{@state.margin.left}, #{@state.margin.top})" |
|
( selected.map (k) -> |
|
r$ D3Component, |
|
key: k |
|
className: "graph" |
|
chart: D3LineGraph |
|
xScale: xScale |
|
yScale: yScale |
|
data: data[k] |
|
)... |
|
r$ D3Component, |
|
className: "x axis" |
|
chart: d3.svg.axis, |
|
attrs: |
|
transform: "translate(0, #{@state.height - @state.margin.top - @state.margin.bottom})" |
|
scale: xScale |
|
orient: "bottom" |
|
r$ D3Component, |
|
className: "y axis" |
|
chart: d3.svg.axis |
|
key: "y-axis" |
|
scale: yScale |
|
orient: "left" |
|
|
|
D3Component = React.createClass |
|
displayName: "D3Component" |
|
|
|
propTypes: |
|
chart: React.PropTypes.func.isRequired |
|
|
|
componentWillMount: -> |
|
@chart = @props.chart() |
|
|
|
componentDidMount: -> |
|
if @updateChart(@chart) |
|
@chart @selectNode() |
|
|
|
componentDidUpdate: (prevProps, prevState) -> |
|
if @updateChart(@chart, prevProps) |
|
@chart @selectNode() |
|
|
|
componentWillUnmount: () -> |
|
_.chain(@props) |
|
.keys() |
|
.filter (key) -> key.indexOf("on") == 0 |
|
.each (key) => |
|
event_name = "#{key.charAt(2).toLowerCase()}#{key.substr(3)}" |
|
@chart.on event_name, null |
|
|
|
updateChart: (chart, prevProps={}) -> |
|
_.chain(@props) |
|
.omit "chart", "className", "attrs", "key" |
|
.pairs() |
|
.reject ([key, value]) -> prevProps[key] == value |
|
.each ([key, value]) -> |
|
if key.indexOf("on") == 0 |
|
event_name = "#{key.charAt(2).toLowerCase()}#{key.substr(3)}" |
|
chart.on event_name, value |
|
else if typeof chart[key] is 'function' |
|
chart[key] value |
|
else |
|
console.warn "#{key} is not an attribute of chart object; skipping" |
|
return |
|
|
|
.isEmpty() |
|
.negate() |
|
.value() |
|
|
|
selectNode: -> |
|
d3.select(ReactDOM.findDOMNode(@)) |
|
|
|
render: () -> |
|
r$ "g", |
|
_.extend {}, @props.attrs, className: @props.className |
|
|
|
ReactDOM.render r$(App), document.getElementById("app") |