Move legendComponent ref out of Dygraph and introduce annotationMode
parent
b36ab14b24
commit
68f24e64d4
|
@ -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),
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue