Move legendComponent ref out of Dygraph and introduce annotationMode

pull/10616/head
Andrew Watkins 2018-01-19 16:14:24 -08:00
parent b36ab14b24
commit 68f24e64d4
9 changed files with 106 additions and 68 deletions

View File

@ -6,6 +6,7 @@ import Annotation from 'src/shared/components/Annotation'
import AnnotationWindow from 'src/shared/components/AnnotationWindow'
import {
addAnnotation,
updateAnnotation,
deleteAnnotation,
} from 'src/shared/actions/annotations'
@ -22,7 +23,7 @@ class Annotations extends Component {
render() {
const {dygraph} = this.state
const {handleUpdateAnnotation, handleDeleteAnnotation} = this.props
const {mode, handleUpdateAnnotation, handleDeleteAnnotation} = this.props
if (!dygraph) {
return null
@ -35,6 +36,7 @@ class Annotations extends Component {
{annotations.map(a =>
<Annotation
key={a.id}
mode={mode}
annotation={a}
dygraph={dygraph}
annotations={annotations}
@ -52,13 +54,15 @@ class Annotations extends Component {
}
}
const {arrayOf, func, shape} = PropTypes
const {arrayOf, func, shape, string} = PropTypes
Annotations.propTypes = {
annotations: arrayOf(shape({})),
mode: string,
annotationsRef: func,
handleDeleteAnnotation: func.isRequired,
handleUpdateAnnotation: func.isRequired,
handleAddAnnotation: func.isRequired,
}
const mapStateToProps = ({annotations}) => ({
@ -66,6 +70,7 @@ const mapStateToProps = ({annotations}) => ({
})
const mapDispatchToProps = dispatch => ({
handleAddAnnotation: bindActionCreators(addAnnotation, dispatch),
handleUpdateAnnotation: bindActionCreators(updateAnnotation, dispatch),
handleDeleteAnnotation: bindActionCreators(deleteAnnotation, dispatch),
})

View File

@ -49,7 +49,6 @@ export default class Dygraph extends Component {
logscale: y.scale === LOG,
colors: this.getLineColors(),
series: this.hashColorDygraphSeries(),
highlightCallback: this.highlightCallback,
unhighlightCallback: this.unhighlightCallback,
plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})],
axes: {
@ -122,6 +121,7 @@ export default class Dygraph extends Component {
const {labels, axes: {y, y2}, options, isBarGraph} = this.props
const dygraph = this.dygraph
if (!dygraph) {
throw new Error(
'Dygraph not configured in time; this should not be possible!'
@ -152,7 +152,6 @@ export default class Dygraph extends Component {
colors: this.getLineColors(),
series: this.hashColorDygraphSeries(),
plotter: isBarGraph ? barPlotter : null,
legendFormatter: this.legendComponent.legendFormatter,
drawCallback: graph => {
this.annotationsRef.setState({dygraph: graph})
},
@ -262,10 +261,6 @@ export default class Dygraph extends Component {
return buildDefaultYLabel(queryConfig)
}
handleLegendRef = el => (this.legendNodeRef = el)
handleLegendComponentRef = ref => (this.legendComponent = ref)
resize = () => {
this.dygraph.resizeElements_()
this.dygraph.predraw_()
@ -293,49 +288,30 @@ export default class Dygraph extends Component {
crosshair.plugin.deselect()
}
unhighlightCallback = e => {
const {
top,
bottom,
left,
right,
} = this.legendNodeRef.getBoundingClientRect()
const mouseY = e.clientY
const mouseX = e.clientX
const mouseBuffer = 5
const mouseInLegendY = mouseY <= bottom && mouseY >= top - mouseBuffer
const mouseInLegendX = mouseX <= right && mouseX >= left
const isMouseHoveringLegend = mouseInLegendY && mouseInLegendX
if (!isMouseHoveringLegend) {
this.setState({isHidden: true})
}
}
highlightCallback = ({pageX}) => {
this.legendComponent.setState({pageX})
handleShowLegend = () => {
this.setState({isHidden: false})
}
handleAnnotationsRef = ref => (this.annotationsRef = ref)
render() {
const {annotationMode} = this.props
const {isHidden} = this.state
return (
<div className="dygraph-child" onMouseLeave={this.deselectCrosshair}>
<Annotations annotationsRef={this.handleAnnotationsRef} />
<DygraphLegend
dygraph={this.dygraph}
graph={this.graphRef}
isHidden={isHidden}
legendNode={this.legendNodeRef}
onHide={this.handleHideLegend}
legendNodeRef={this.handleLegendRef}
legendComponent={this.handleLegendComponentRef}
<Annotations
mode={annotationMode}
annotationsRef={this.handleAnnotationsRef}
/>
{this.dygraph &&
annotationMode !== 'adding' &&
<DygraphLegend
isHidden={isHidden}
dygraph={this.dygraph}
onHide={this.handleHideLegend}
onShow={this.handleShowLegend}
/>}
<div
ref={r => {
this.graphRef = r
@ -403,4 +379,5 @@ Dygraph.propTypes = {
setResolution: func,
dygraphRef: func,
onZoom: func,
annotationMode: string,
}

View File

@ -26,7 +26,11 @@ class DygraphLegend extends Component {
}
componentDidMount() {
this.props.legendComponent(this)
this.props.dygraph.updateOptions({
legendFormatter: this.legendFormatter,
highlightCallback: this.highlightCallback,
unhighlightCallback: this.unhighlightCallback,
})
}
componentWillUnmount() {
@ -36,6 +40,12 @@ class DygraphLegend extends Component {
) {
this.setState({filterText: ''})
}
this.props.dygraph.updateOptions({
legendFormatter: () => {},
highlightCallback: () => {},
unhighlightCallback: () => {},
})
}
handleToggleFilter = () => {
@ -69,6 +79,32 @@ class DygraphLegend extends Component {
this.setState({sortType, isAscending: !this.state.isAscending})
}
unhighlightCallback = e => {
const {
top,
bottom,
left,
right,
} = this.legendNodeRef.getBoundingClientRect()
const mouseY = e.clientY
const mouseX = e.clientX
const mouseBuffer = 5
const mouseInLegendY = mouseY <= bottom && mouseY >= top - mouseBuffer
const mouseInLegendX = mouseX <= right && mouseX >= left
const isMouseHoveringLegend = mouseInLegendY && mouseInLegendX
if (!isMouseHoveringLegend) {
this.props.onHide(e)
}
}
highlightCallback = ({pageX}) => {
this.setState({pageX})
this.props.onShow()
}
legendFormatter = legend => {
if (!legend.x) {
return ''
@ -90,7 +126,7 @@ class DygraphLegend extends Component {
}
render() {
const {graph, onHide, isHidden, legendNodeRef, legendNode} = this.props
const {dygraph, onHide, isHidden} = this.props
const {
pageX,
@ -111,7 +147,7 @@ class DygraphLegend extends Component {
const ordered = isAscending ? sorted : sorted.reverse()
const filtered = ordered.filter(s => s.label.match(filterText))
const hidden = isHidden ? 'hidden' : ''
const style = makeLegendStyles(graph, legendNode, pageX)
const style = makeLegendStyles(dygraph.graphDiv, this.legendNodeRef, pageX)
const renderSortAlpha = (
<div
@ -146,7 +182,7 @@ class DygraphLegend extends Component {
return (
<div
className={`dygraph-legend ${hidden}`}
ref={legendNodeRef}
ref={el => (this.legendNodeRef = el)}
onMouseLeave={onHide}
style={style}
>
@ -211,13 +247,10 @@ class DygraphLegend extends Component {
const {bool, func, shape} = PropTypes
DygraphLegend.propTypes = {
legendComponent: func,
legendNode: shape({}),
dygraph: shape({}),
graph: shape({}),
onHide: func.isRequired,
onShow: func.isRequired,
isHidden: bool.isRequired,
legendNodeRef: func.isRequired,
}
export default DygraphLegend

View File

@ -16,11 +16,8 @@ const getSource = (cell, source, sources, defaultSource) => {
}
class LayoutState extends Component {
constructor(props) {
super(props)
this.state = {
celldata: [],
}
state = {
celldata: [],
}
grabDataForDownload = celldata => {
@ -28,11 +25,10 @@ class LayoutState extends Component {
}
render() {
const {celldata} = this.state
return (
<Layout
{...this.props}
celldata={celldata}
{...this.state}
grabDataForDownload={this.grabDataForDownload}
/>
)
@ -47,6 +43,7 @@ const Layout = (
source,
sources,
onZoom,
celldata,
templates,
timeRange,
isEditable,
@ -56,10 +53,11 @@ const Layout = (
onDeleteCell,
synchronizer,
resizeCoords,
annotationMode,
onCancelEditCell,
onStartAddAnnotation,
onSummonOverlayTechnologies,
grabDataForDownload,
celldata,
},
{source: defaultSource}
) =>
@ -70,22 +68,24 @@ const Layout = (
onEditCell={onEditCell}
onDeleteCell={onDeleteCell}
onCancelEditCell={onCancelEditCell}
onStartAddAnnotation={onStartAddAnnotation}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
>
{cell.isWidget
? <WidgetCell cell={cell} timeRange={timeRange} source={source} />
: <RefreshingGraph
colors={colors}
axes={axes}
type={type}
cellHeight={h}
colors={colors}
onZoom={onZoom}
sources={sources}
timeRange={timeRange}
templates={templates}
autoRefresh={autoRefresh}
manualRefresh={manualRefresh}
synchronizer={synchronizer}
manualRefresh={manualRefresh}
annotationMode={annotationMode}
grabDataForDownload={grabDataForDownload}
resizeCoords={resizeCoords}
queries={buildQueriesForLayouts(

View File

@ -10,10 +10,6 @@ import {dashboardtoCSV} from 'shared/parsing/resultsToCSV'
import download from 'src/external/download.js'
class LayoutCell extends Component {
constructor(props) {
super(props)
}
handleDeleteCell = cell => () => {
this.props.onDeleteCell(cell)
}
@ -34,7 +30,13 @@ class LayoutCell extends Component {
}
render() {
const {cell, children, isEditable, celldata} = this.props
const {
cell,
children,
isEditable,
celldata,
onStartAddAnnotation,
} = this.props
const queries = _.get(cell, ['queries'], [])
@ -50,6 +52,7 @@ class LayoutCell extends Component {
onEdit={this.handleSummonOverlay}
handleClickOutside={this.closeMenu}
onCSVDownload={this.handleCSVDownload}
onStartAddAnnotation={onStartAddAnnotation}
/>
</Authorized>
<LayoutCellHeader cellName={cell.name} isEditable={isEditable} />
@ -88,6 +91,7 @@ LayoutCell.propTypes = {
isEditable: bool,
onCancelEditCell: func,
celldata: arrayOf(shape()),
onStartAddAnnotation: func.isRequired,
}
export default LayoutCell

View File

@ -23,6 +23,7 @@ class LayoutCellMenu extends Component {
isEditable,
dataExists,
onCSVDownload,
onStartAddAnnotation,
} = this.props
return (
@ -42,7 +43,10 @@ class LayoutCellMenu extends Component {
<div className="dash-graph-context--buttons">
<MenuTooltipButton
icon="pencil"
menuOptions={[{text: 'Queries', action: onEdit(cell)}]}
menuOptions={[
{text: 'Queries', action: onEdit(cell)},
{text: 'Add Annotation', action: onStartAddAnnotation},
]}
informParent={this.handleToggleSubMenu}
/>
{dataExists &&
@ -75,6 +79,7 @@ LayoutCellMenu.propTypes = {
dataExists: bool,
onCSVDownload: func,
queries: arrayOf(shape()),
onStartAddAnnotation: func.isRequired,
}
export default LayoutCellMenu

View File

@ -26,9 +26,14 @@ class LayoutRenderer extends Component {
this.state = {
rowHeight: this.calculateRowHeight(),
resizeCoords: null,
annotationMode: null,
}
}
handleStartAddAnnotation = () => {
this.setState({annotationMode: 'adding'})
}
handleLayoutChange = layout => {
if (!this.props.onPositionChange) {
return
@ -84,7 +89,7 @@ class LayoutRenderer extends Component {
onSummonOverlayTechnologies,
} = this.props
const {rowHeight, resizeCoords} = this.state
const {rowHeight, resizeCoords, annotationMode} = this.state
const isDashboard = !!this.props.onPositionChange
return (
@ -134,7 +139,9 @@ class LayoutRenderer extends Component {
onDeleteCell={onDeleteCell}
synchronizer={synchronizer}
manualRefresh={manualRefresh}
annotationMode={annotationMode}
onCancelEditCell={onCancelEditCell}
onStartAddAnnotation={this.handleStartAddAnnotation}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/>
</Authorized>

View File

@ -49,7 +49,9 @@ class LineGraph extends Component {
resizeCoords,
synchronizer,
isRefreshing,
setResolution,
isGraphFilled,
annotationMode,
showSingleStat,
displayOptions,
underlayCallback,
@ -99,18 +101,19 @@ class LineGraph extends Component {
onZoom={onZoom}
labels={labels}
queries={queries}
options={options}
timeRange={timeRange}
isBarGraph={isBarGraph}
timeSeries={timeSeries}
ruleValues={ruleValues}
synchronizer={synchronizer}
resizeCoords={resizeCoords}
overrideLineColors={lineColors}
dygraphSeries={dygraphSeries}
setResolution={this.props.setResolution}
setResolution={setResolution}
overrideLineColors={lineColors}
containerStyle={containerStyle}
annotationMode={annotationMode}
isGraphFilled={showSingleStat ? false : isGraphFilled}
options={options}
/>
{showSingleStat
? <SingleStat data={data} cellHeight={cellHeight} />
@ -177,6 +180,7 @@ LineGraph.propTypes = {
resizeCoords: shape(),
queries: arrayOf(shape({}).isRequired).isRequired,
data: arrayOf(shape({}).isRequired).isRequired,
annotationMode: string,
}
export default LineGraph

View File

@ -22,6 +22,7 @@ const RefreshingGraph = ({
cellHeight,
autoRefresh,
resizerTopHeight,
annotationMode,
manualRefresh, // when changed, re-mounts the component
synchronizer,
resizeCoords,
@ -85,6 +86,7 @@ const RefreshingGraph = ({
isBarGraph={type === 'bar'}
synchronizer={synchronizer}
resizeCoords={resizeCoords}
annotationMode={annotationMode}
displayOptions={displayOptions}
editQueryStatus={editQueryStatus}
grabDataForDownload={grabDataForDownload}
@ -96,6 +98,7 @@ const RefreshingGraph = ({
const {arrayOf, func, number, shape, string} = PropTypes
RefreshingGraph.propTypes = {
annotationMode: string,
timeRange: shape({
lower: string.isRequired,
}),