-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[explorev2] chart and controls #1251
Changes from 23 commits
2a5a3ed
7c77286
9931490
7175952
25d70d6
085b424
03b6316
c1aed1d
3e4375e
f3cf9a9
dc9a705
e29bca9
e5cc1c5
fa6a480
e036d95
dc1669a
6741c4e
dc9702b
4bacd74
65aa771
445b432
aa7502b
f0a7f10
84492bb
ed767f9
76eb092
142ec07
c716043
6f289a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
const { assign } = Object; | ||
|
||
const A11Y_BABU = '#00A699'; | ||
const AXIS_LINE_GRAY = '#484848'; | ||
const LABEL_TEXT_GRAY = '#767676'; | ||
const GRID_LINE_GRAY = '#DBDBDB'; | ||
|
||
// Colors | ||
const colors = [ | ||
'#ffffff', | ||
'#f0f0f0', | ||
'#d9d9d9', | ||
'#bdbdbd', | ||
'#969696', | ||
'#737373', | ||
'#525252', | ||
'#252525', | ||
'#000000', | ||
]; | ||
|
||
const charcoal = '#484848'; | ||
|
||
// Typography | ||
const sansSerif = '"Roboto", sans-serif'; | ||
const letterSpacing = 'normal'; | ||
const fontSize = 8; | ||
|
||
// Layout | ||
const baseProps = { | ||
width: 450, | ||
height: 300, | ||
padding: 50, | ||
colorScale: colors, | ||
}; | ||
|
||
// Labels | ||
const baseLabelStyles = { | ||
fontFamily: sansSerif, | ||
fontSize, | ||
letterSpacing, | ||
padding: 10, | ||
fill: charcoal, | ||
stroke: 'transparent', | ||
}; | ||
|
||
// Strokes | ||
const strokeLinecap = 'round'; | ||
const strokeLinejoin = 'round'; | ||
|
||
// Create the theme | ||
const theme = { | ||
area: assign({ | ||
style: { | ||
data: { | ||
fill: charcoal, | ||
}, | ||
labels: baseLabelStyles, | ||
}, | ||
}, baseProps), | ||
axis: assign({ | ||
style: { | ||
axis: { | ||
fill: 'none', | ||
stroke: AXIS_LINE_GRAY, | ||
strokeWidth: 1, | ||
strokeLinecap, | ||
strokeLinejoin, | ||
}, | ||
axisLabel: assign({}, baseLabelStyles, { | ||
padding: 25, | ||
}), | ||
grid: { | ||
fill: 'none', | ||
stroke: 'transparent', | ||
}, | ||
ticks: { | ||
fill: 'none', | ||
padding: 10, | ||
size: 1, | ||
stroke: 'transparent', | ||
}, | ||
tickLabels: baseLabelStyles, | ||
}, | ||
}, baseProps), | ||
bar: assign({ | ||
style: { | ||
data: { | ||
fill: A11Y_BABU, | ||
padding: 10, | ||
stroke: 'transparent', | ||
strokeWidth: 0, | ||
width: 8, | ||
}, | ||
labels: baseLabelStyles, | ||
}, | ||
}, baseProps), | ||
candlestick: assign({ | ||
style: { | ||
data: { | ||
stroke: A11Y_BABU, | ||
strokeWidth: 1, | ||
}, | ||
labels: assign({}, baseLabelStyles, { | ||
padding: 25, | ||
textAnchor: 'end', | ||
}), | ||
}, | ||
candleColors: { | ||
positive: '#ffffff', | ||
negative: charcoal, | ||
}, | ||
}, baseProps), | ||
chart: baseProps, | ||
errorbar: assign({ | ||
style: { | ||
data: { | ||
fill: 'none', | ||
stroke: charcoal, | ||
strokeWidth: 2, | ||
}, | ||
labels: assign({}, baseLabelStyles, { | ||
textAnchor: 'start', | ||
}), | ||
}, | ||
}, baseProps), | ||
group: assign({ | ||
colorScale: colors, | ||
}, baseProps), | ||
line: assign({ | ||
style: { | ||
data: { | ||
fill: 'none', | ||
stroke: A11Y_BABU, | ||
strokeWidth: 2, | ||
}, | ||
labels: assign({}, baseLabelStyles, { | ||
textAnchor: 'start', | ||
}), | ||
}, | ||
}, baseProps), | ||
pie: { | ||
style: { | ||
data: { | ||
padding: 10, | ||
stroke: 'none', | ||
strokeWidth: 1, | ||
}, | ||
labels: assign({}, baseLabelStyles, { | ||
padding: 200, | ||
textAnchor: 'middle', | ||
}), | ||
}, | ||
colorScale: colors, | ||
width: 400, | ||
height: 400, | ||
padding: 50, | ||
}, | ||
scatter: assign({ | ||
style: { | ||
data: { | ||
fill: charcoal, | ||
stroke: 'transparent', | ||
strokeWidth: 0, | ||
}, | ||
labels: assign({}, baseLabelStyles, { | ||
textAnchor: 'middle', | ||
}), | ||
}, | ||
}, baseProps), | ||
stack: assign({ | ||
colorScale: colors, | ||
}, baseProps), | ||
}; | ||
|
||
export default theme; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,56 @@ | ||
import React from 'react'; | ||
import { Panel } from 'react-bootstrap'; | ||
import TimeSeriesLineChart from './charts/TimeSeriesLineChart'; | ||
|
||
const ChartContainer = function () { | ||
return ( | ||
<Panel header="Chart title"> | ||
chart goes here | ||
</Panel> | ||
); | ||
}; | ||
export default ChartContainer; | ||
export default class ChartContainer extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
params: this.getParamsFromUrl(), | ||
data: props.viz.data, | ||
height: window.innerHeight, | ||
label1: 'Label 1', | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should you add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since |
||
} | ||
|
||
getParamsFromUrl() { | ||
const hash = window.location.search; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious if we've thought about using react router for url state management? I used it in dataportal and liked it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a good suggestion... let's keep it in mind as we continue the refactor |
||
const params = hash.split('?')[1].split('&'); | ||
const newParams = {}; | ||
params.forEach((p) => { | ||
const value = p.split('=')[1].replace(/\+/g, ' '); | ||
const key = p.split('=')[0]; | ||
newParams[key] = value; | ||
}); | ||
return newParams; | ||
} | ||
|
||
formatDates(values) { | ||
const newValues = values.map((val) => { | ||
return { | ||
x: moment(new Date(val.x)).format('MMM D'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI d3 also has really good date formatting utilities, not sure if it'd be more lightweight than moment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, i'm not sure either.. let's use moment for now and we can change to use d3 time utils in the future if that makes sense |
||
y: val.y, | ||
}; | ||
}); | ||
return newValues; | ||
} | ||
|
||
render() { | ||
return ( | ||
<div className="chart-container"> | ||
<Panel | ||
style={{ height: this.state.height }} | ||
header={ | ||
<div className="panel-title">{this.props.viz.form_data.slice_name}</div> | ||
} | ||
> | ||
<TimeSeriesLineChart | ||
data={this.state.data} | ||
label1="Percentage" | ||
height={this.state.height} | ||
/> | ||
</Panel> | ||
</div> | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,24 +3,45 @@ import ChartContainer from './ChartContainer'; | |
import ControlPanelsContainer from './ControlPanelsContainer'; | ||
import QueryAndSaveButtons from './QueryAndSaveButtons'; | ||
|
||
const ExploreViewContainer = function () { | ||
return ( | ||
<div className="container-fluid"> | ||
<div className="row"> | ||
<div className="col-sm-3"> | ||
<QueryAndSaveButtons | ||
canAdd="True" | ||
onQuery={() => { console.log('clicked query'); }} | ||
/> | ||
<br /><br /> | ||
<ControlPanelsContainer /> | ||
</div> | ||
<div className="col-sm-9"> | ||
<ChartContainer /> | ||
export default class ExploreViewContainer extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
height: this.getHeight(), | ||
}; | ||
} | ||
|
||
getHeight() { | ||
const navHeight = 90; | ||
return (window.innerHeight - navHeight) + 'px'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we use template literals anywhere or is this just a preference you have? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. template literals are nice, will change. mostly just old habit here :) |
||
} | ||
|
||
render() { | ||
return ( | ||
<div | ||
className="container-fluid" | ||
style={{ | ||
height: this.state.height, | ||
overflow: 'hidden', | ||
}} | ||
> | ||
<div className="row table-body"> | ||
<div className="table-cell col-sm-4"> | ||
<QueryAndSaveButtons | ||
canAdd="True" | ||
onQuery={() => {}} | ||
/> | ||
<br /><br /> | ||
<ControlPanelsContainer /> | ||
</div> | ||
<div className="table-cell col-sm-8"> | ||
<ChartContainer | ||
viz={this.props.data.viz} | ||
height={this.state.height} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
); | ||
} | ||
}; | ||
|
||
export default ExploreViewContainer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React, { PropTypes } from 'react'; | ||
import classnames from 'classnames'; | ||
|
||
const propTypes = { | ||
canAdd: PropTypes.string.isRequired, | ||
onQuery: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default function QueryAndSaveBtns({ canAdd, onQuery }) { | ||
const saveClasses = classnames('btn btn-default btn-sm', { | ||
'disabled disabledButton': canAdd !== 'True', | ||
}); | ||
|
||
return ( | ||
<div className="btn-group query-and-save"> | ||
<button type="button" className="btn btn-primary btn-sm" onClick={onQuery}> | ||
<i className="fa fa-bolt"></i> Query | ||
</button> | ||
<button | ||
type="button" | ||
className={saveClasses} | ||
data-target="#save_modal" | ||
data-toggle="modal" | ||
> | ||
<i className="fa fa-plus-circle"></i> Save as | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
QueryAndSaveBtns.propTypes = propTypes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import React, { PropTypes } from 'react'; | ||
|
||
const propTypes = { | ||
data: PropTypes.array.isRequired, | ||
keysToColorsMap: PropTypes.object.isRequired, | ||
}; | ||
|
||
export default class Legend extends React.Component { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not a pure function here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point, will simplify this |
||
legendItem(key) { | ||
return ( | ||
<li style={{ float: 'left' }} key={key}> | ||
<i | ||
className="fa fa-circle" | ||
style={{ color: this.props.keysToColorsMap[key] }} | ||
></i> | ||
<span>{key}</span> | ||
</li> | ||
); | ||
} | ||
|
||
render() { | ||
const legendEls = this.props.data.map((d) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventually it'd be cool to make the legend expandable in the case that there are many series, then it wouldn't dominate most of the real estate in a dashboard chart There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
return this.legendItem(d.key); | ||
}); | ||
return ( | ||
<ul className="list-unstyled list-inline"> | ||
{legendEls} | ||
</ul> | ||
); | ||
} | ||
} | ||
|
||
Legend.propTypes = propTypes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unrelated question, if we are not using
babel-polyfill
are we relying onObject.assign
coming from chrome / the browser?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we use babel and these presets so Object.assign is polyfilled.