-
-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
252 additions
and
240 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,132 +1,129 @@ | ||
import _ from 'underscore'; | ||
import React, { createRef } from 'react'; | ||
import React, { createRef, useState, useEffect, useCallback } from 'react'; | ||
|
||
import './svgExporter'; // create handlers for SVG and PNG download buttons | ||
import CollapsePreferences from './collapse_preferences'; | ||
|
||
// Each instance of Grapher is added to this object once the component has been | ||
// mounted. This is so that grapher can be iterated over and redrawn on window | ||
// resize event. | ||
var Graphers = {}; | ||
import useDetectPrint from "react-detect-print"; | ||
|
||
// Grapher is a function that takes a Graph class and returns a React component. | ||
// This React component provides HTML boilerplate to add heading, to make the | ||
// graphs collapsible, to redraw graphs when window is resized, and SVG and PNG | ||
// export buttons and functionality. | ||
export default function Grapher(Graph) { | ||
return class extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.name = Graph.name(this.props); | ||
this.collapsePreferences = new CollapsePreferences(this); | ||
let isCollapsed = this.collapsePreferences.preferenceStoredAsCollapsed(); | ||
this.state = { collapsed: Graph.canCollapse() && (this.props.collapsed || isCollapsed) }; | ||
this.svgContainerRef = createRef(); | ||
} | ||
return function Component(props) { | ||
const alwaysShowName = Graph.alwaysShowName === undefined ? false : Graph.alwaysShowName(); | ||
const printing = useDetectPrint(); | ||
const name = Graph.name(props); | ||
const [width, setWidth] = useState(window.innerWidth); | ||
const [collapsed, setCollapsed] = useState(false); | ||
const svgContainerRef = createRef(); | ||
let graph = null; | ||
|
||
graphId() { | ||
return Graph.graphId(this.props); | ||
} | ||
const graphId = () => "testing" | ||
|
||
render() { | ||
// Do not render when Graph.name() is null | ||
if (Graph.name(this.props) === null) { | ||
return null; | ||
} else { | ||
var printCss = this.state.collapsed ? ' print:hidden' : ''; | ||
var cssClasses = Graph.className() + ' grapher' + printCss; | ||
return ( | ||
<div className={cssClasses}> | ||
{this.header()} | ||
{this.svgContainerJSX()} | ||
</div> | ||
); | ||
} | ||
const graphLinksJSX = () => { | ||
return ( | ||
<div className="hit-links float-right text-right text-blue-300 h-4 print:hidden"> | ||
<a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-svg"> | ||
<i className="fa fa-download" /> SVG | ||
</a> | ||
<span className="line px-1">|</span> | ||
<a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-png"> | ||
<i className="fa fa-download" /> PNG | ||
</a> | ||
</div> | ||
); | ||
} | ||
|
||
header() { | ||
const header = () => { | ||
if(Graph.canCollapse()) { | ||
return <div className="grapher-header pr-px"> | ||
<h4 | ||
className="inline-block pl-px m-0 caption cursor-pointer text-sm" | ||
onClick={() => this.collapsePreferences.toggleCollapse()} | ||
onClick={() => collapsePreferences.toggleCollapse()} | ||
> | ||
{this.collapsePreferences.renderCollapseIcon()} | ||
<span className="print:hidden"> </span>{Graph.name(this.props)} | ||
{collapsePreferences.renderCollapseIcon()} | ||
<span className="print:hidden"> </span>{Graph.name(props)} | ||
</h4> | ||
{!this.state.collapsed && this.graphLinksJSX()} | ||
{!collapsed && graphLinksJSX()} | ||
</div>; | ||
} else if (alwaysShowName) { | ||
return <div className="grapher-histogram-header" style={{ position: 'relative' }}> | ||
<h4 className="caption"> {Graph.name(props)}</h4> | ||
<div className="pull-right" style={{ position: 'absolute', top: 0, right: 0 }}> | ||
{!collapsed && graphLinksJSX()} | ||
</div> | ||
</div>; | ||
} else { | ||
return <div className="pr-px"> | ||
{!this.state.collapsed && this.graphLinksJSX()} | ||
{!collapsed && graphLinksJSX()} | ||
</div>; | ||
} | ||
} | ||
|
||
graphLinksJSX() { | ||
return ( | ||
<div className="hit-links float-right text-right text-blue-300 h-4 print:hidden"> | ||
<a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-svg"> | ||
<i className="fa fa-download" /> SVG | ||
</a> | ||
<span className="line px-1">|</span> | ||
<a href="#" className="btn-link text-sm text-seqblue hover:text-seqorange cursor-pointer export-to-png"> | ||
<i className="fa fa-download" /> PNG | ||
</a> | ||
</div> | ||
); | ||
} | ||
|
||
svgContainerJSX() { | ||
const svgContainerJSX = () => { | ||
var cssClasses = Graph.className() + ' svg-container hidden'; | ||
if (!this.state.collapsed) cssClasses += ' !block'; | ||
if (!collapsed) cssClasses += ' !block'; | ||
return ( | ||
<div | ||
ref={this.svgContainerRef} | ||
id={this.graphId()} | ||
ref={svgContainerRef} | ||
id={graphId()} | ||
className={cssClasses} | ||
></div> | ||
); | ||
} | ||
|
||
componentDidMount() { | ||
Graphers[this.graphId()] = this; | ||
|
||
// Draw visualisation for the first time. Visualisations are | ||
// redrawn when browser window is resized. | ||
this.draw(); | ||
} | ||
|
||
componentDidUpdate() { | ||
// Re-draw visualisation when the component change state. | ||
this.draw(); | ||
} | ||
svgContainer() { | ||
return $(this.svgContainerRef.current); | ||
const svgContainer = () => { | ||
return $(svgContainerRef.current); | ||
} | ||
|
||
draw() { | ||
const draw = (printing = false) => { | ||
let graphWidth = 'auto'; | ||
if (printing) graphWidth = '900'; | ||
// Clean slate. | ||
this.svgContainer().empty(); | ||
this.graph = null; | ||
svgContainer().empty(); | ||
graph = null; | ||
|
||
// Draw if uncollapsed. | ||
if (this.state.collapsed) { | ||
return; | ||
} | ||
this.graph = new Graph(this.svgContainer(), this.props); | ||
this.svgContainer() | ||
if (collapsed) return; | ||
|
||
svgContainer().width(graphWidth); | ||
graph = new Graph(svgContainer(), props); | ||
svgContainer() | ||
.find('svg') | ||
.attr('data-name', Graph.dataName(this.props)); | ||
.attr('data-name', Graph.dataName(props)); | ||
} | ||
}; | ||
} | ||
|
||
// Redraw if window resized. | ||
$(window).resize( | ||
_.debounce(function () { | ||
_.each(Graphers, (grapher) => { | ||
grapher.draw(); | ||
}); | ||
}, 125) | ||
); | ||
useEffect(() => { | ||
// Attach a debounced listener to handle window resize events | ||
// Updates the width state with the current window width, throttled to run at most once every 125ms | ||
const handleResize = _.debounce(() => setWidth(window.innerWidth), 125); | ||
window.addEventListener("resize", handleResize); | ||
|
||
const isCollapsed = collapsePreferences.preferenceStoredAsCollapsed(); | ||
setCollapsed(Graph.canCollapse() && (props.collapsed || isCollapsed)) | ||
draw(); | ||
|
||
return () => window.removeEventListener("resize", handleResize) | ||
}, []) | ||
|
||
useEffect(() => { | ||
draw(printing); | ||
}, [printing, width]) | ||
|
||
const collapsePreferences = new CollapsePreferences({name: name, collapsed: collapsed, setCollapsed: setCollapsed}, true); | ||
|
||
if (Graph.name(props) === null) { | ||
return(null); | ||
} else { | ||
const printCss = collapsed ? 'print:hidden' : ''; | ||
const cssClasses = Graph.className() + ' grapher' + printCss; | ||
return ( | ||
<div className={cssClasses}> | ||
{header()} | ||
{svgContainerJSX()} | ||
</div> | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.