Skip to content

Commit

Permalink
Re-render svg before print
Browse files Browse the repository at this point in the history
  • Loading branch information
joko3ono committed Nov 18, 2024
1 parent d59a7b1 commit 3447842
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 240 deletions.
16 changes: 13 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"jstree": "^3.3.8",
"react": "18",
"react-app-polyfill": "^3.0.0",
"react-detect-print": "^0.1.2",
"react-dom": "18",
"tailwindcss": "^3.4.3",
"underscore": "^1.8.3"
Expand Down
2 changes: 0 additions & 2 deletions public/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
@tailwind utilities;

@page {
size: A4;
margin: 0;
}
@media print {
html, body {
width: 255mm;
height: 297mm;
}
}

Expand Down
2 changes: 1 addition & 1 deletion public/css/app.min.css

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions public/js/collapse_preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ export default class CollapsePreferences {
}

toggleCollapse() {
let currentlyCollapsed = this.component.state.collapsed;
let currentlyCollapsed = false;

this.component.setState({ collapsed: !currentlyCollapsed });
if (this.isReactFunction) {
currentlyCollapsed = this.component.collapsed;
this.component.setCollapsed(!currentlyCollapsed);
} else {
currentlyCollapsed = this.component.state.collapsed;
this.component.setState({ collapsed: !currentlyCollapsed });
}

let collapsePreferences = JSON.parse(localStorage.getItem('collapsePreferences')) || [];

Expand All @@ -24,7 +30,11 @@ export default class CollapsePreferences {
}

renderCollapseIcon() {
return this.component.state.collapsed ? this.plusIcon() : this.minusIcon();
if (this.isReactFunction) {
return this.component.collapsed ? this.plusIcon() : this.minusIcon();
} else {
return this.component.state.collapsed ? this.plusIcon() : this.minusIcon();
}
}

minusIcon() {
Expand Down
175 changes: 86 additions & 89 deletions public/js/grapher.js
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">&nbsp;</span>{Graph.name(this.props)}
{collapsePreferences.renderCollapseIcon()}
<span className="print:hidden">&nbsp;</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">&nbsp;{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>
)
}
}
}
Loading

0 comments on commit 3447842

Please sign in to comment.