Skip to content

Commit

Permalink
Quick updates (#331)
Browse files Browse the repository at this point in the history
* Removed 'required' from hasError and handleError proptypes to prevent error, updated setup.sh to install python2, fixed an error about unreachable code in Colormaps.js

* Testing save_screenshot function.

* Adding save plot functionality.

* Testing screenshot function

* Added save plot functionailty and connections.

* Added save plot functionality fully implemented. Modified export modal to allow user to specify screenshot dimensions as well as export type.

* Minor cleanup of code. Removed tabs from export modal since they aren't needed. Removed corresponding test for tab values from ExportModalTest.jsx

* Updated README file so that users will see link to user documentation only. Removed comment about being early development phase.

* Updated README file text.

* Updated README.md

* Updated plot export function so that it works with different settings and multiple layers. Minor changes to some help-tutorial text.

* Added data mock data store and few test cases for ExportModal.jsx test. Tests passing.
  • Loading branch information
downiec committed Nov 15, 2018
1 parent 9f9df61 commit 9e7e401
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 57 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
[![Build Status](https://travis-ci.org/CDAT/vcdat.svg?branch=master)](https://travis-ci.org/CDAT/vcdat)
[![Coverage Status](https://coveralls.io/repos/github/CDAT/vcdat/badge.svg?branch=master)](https://coveralls.io/github/CDAT/vcdat?branch=master)

#### New to vCDAT? Check out the documentation for [Users](https://cdat.github.io/vcdat/docs/html/user_install.html) and [Developers](https://cdat.github.io/vcdat/docs/html/dev_install.html)

_This project is in the early stages of development. As such please be aware that there may be some bugs and not all features will be available._
#### New to vCDAT? Check out the [Documentation](https://cdat.github.io/vcdat/docs/html/user_install.html).

vCDAT is a desktop application that provides the graphical frontend for the CDAT package. It uses CDAT's VCS and CDMS modules to render high quality visualizations within a browser.

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/components/Canvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class Canvas extends Component {
}
return dataSpec;
});
console.log("plotting", dataSpecs, this.props.plotGMs[index], plot.template);
console.log("Plotting",index, dataSpecs, this.props.plotGMs[index], plot.template);
return this.canvas.plot(dataSpecs, this.props.plotGMs[index], plot.template).then(
success => {
return;
Expand Down
31 changes: 29 additions & 2 deletions frontend/src/js/components/modals/ExportModal.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Modal, Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import SavePlot from './SavePlot/SavePlot.jsx'

class ExportModal extends Component {
Expand All @@ -23,6 +24,7 @@ class ExportModal extends Component {
this.handleDimensionUpdate = this.handleDimensionUpdate.bind(this);
this.handleDimensionChange = this.handleDimensionChange.bind(this);
this.handleDimensionExit = this.handleDimensionExit.bind(this);
this.plots = null;
}

handleChangeExt(type){
Expand Down Expand Up @@ -54,6 +56,11 @@ class ExportModal extends Component {
}

render(){

if(this.props.cells && this.props.row >= 0 && this.props.col >= 0){
this.plots = this.props.cells[this.props.row][this.props.col].plots;
}

return(
<Modal show={this.props.show} onHide={this.props.close} bsSize="large">
<Modal.Header closeButton>
Expand Down Expand Up @@ -126,6 +133,7 @@ class ExportModal extends Component {
exportType={this.state.exportType}
handleChangeExt={this.handleChangeExt}
handleDimensionUpdate={this.handleDimensionUpdate}
plots={this.plots}
/>
</Modal.Body>
<Modal.Footer>
Expand All @@ -136,10 +144,29 @@ class ExportModal extends Component {
}
}

const mapStateToProps = (state) => {
// Prepare parameters to pass to save plot component
// format of `sheet_row_col`. Ex: "0_0_0"
let sheet_row_col = state.present.sheets_model.selected_cell_id.split("_").map(
function (str_val) { return Number(str_val) }
);
let row = sheet_row_col[1];
let col = sheet_row_col[2];
return {
cells: state.present.sheets_model.sheets[state.present.sheets_model.cur_sheet_index].cells,
row: row,
col: col
}
}

ExportModal.propTypes = {
selected_cell_id: PropTypes.string,
show: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
// Added for getting plot information
sheet_row_col: PropTypes.array,
cells: PropTypes.any,
row: PropTypes.number,
col: PropTypes.number
}

export default ExportModal
export default connect(mapStateToProps, null)(ExportModal)
219 changes: 171 additions & 48 deletions frontend/src/js/components/modals/SavePlot/SavePlot.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { toast } from 'react-toastify'
import FileSaver from 'file-saver'
import _ from "lodash";
import './SavePlot.scss'

class SavePlot extends Component{
Expand All @@ -13,9 +14,15 @@ class SavePlot extends Component{
img_url: "",
}
this.savePlot = this.savePlot.bind(this);
this.canvasDiv = null;
this.plotAll = this.plotAll.bind(this);
this.prepareCanvas = this.prepareCanvas.bind(this);
this.exportDimensions = this.props.exportDimensions;
this.exportType = this.props.exportType;
this.canvasDiv = null;
this.canvas = null;
this.error = false;
this.ready = false; // Whether canvas is ready for export
this.notify = false; // If user clicked save before plots were finished, notify when ready.
}

/* istanbul ignore next */
Expand All @@ -24,20 +31,139 @@ class SavePlot extends Component{
let elements = document.querySelectorAll(`.cell-stack-top > #canvas_${this.props.selected_cell_id} > canvas`);
if(elements && elements.length > 0){
this.canvasDiv = elements[0];

// Update default dimensions to match current window size
this.props.handleDimensionUpdate([this.canvasDiv.width,this.canvasDiv.height]);

// Create plot preview thumbnail
this.canvasDiv.toBlob((blob) => {
this.setState({img_url: URL.createObjectURL(blob)})
});

// Initialize canvas object
this.canvas = vcs.init(this.canvasDiv);

this.prepareCanvas();
}
}

prepareCanvas() {
// Plot on the canvas for future saving and set ready flag when done
this.plotAll().then(
success=>{
this.ready=true;
this.error = false;
if(this.notify) {
this.notify = false;
toast.success("Export ready!", {position: toast.POSITION.BOTTOM_CENTER});
}},
error=>{
toast.warn("Plotting error occurred.", {position: toast.POSITION.BOTTOM_CENTER});
this.error = true;
console.log("Plotting error: ",error);

}
);
}

// Plots on the canvas all the layers
async plotAll() {

if(this.props.plots){
for (let [index, plot] of this.props.plots.entries()) {
await this.plot(plot, index);
}
console.log("Plots finished!");
}
else{
console.log("Plots weren't loaded.");
}
}

// This was directly taken from Canvas.jsx
plot(plot, index) {
if (plot.variables.length > 0) {
var variables = this.props.plotVariables[index];
var dataSpecs = variables.map(function(variable) {
var dataSpec;
if (variable.json) {
dataSpec = {
uri: variable.path,
variable: variable.cdms_var_name,
json: variable.json
};
} else {
dataSpec = {
uri: variable.path,
variable: variable.cdms_var_name
};
}

var subRegion = {};
variable.dimension.filter(dimension => dimension.values).forEach(dimension => {
subRegion[dimension.axisName] = dimension.values.range;
});
if (!_.isEmpty(subRegion)) {
dataSpec["operations"] = [{ subRegion }];
}
if (!_.isEmpty(variable.transforms)) {
if (!dataSpec["operations"]) {
dataSpec["operations"] = [];
}
dataSpec["operations"].push({ transform: variable.transforms });
}
var axis_order = variable.dimension.map(dimension => variable.axisList.indexOf(dimension.axisName));
if (axis_order.some((order, index) => order !== index)) {
dataSpec["axis_order"] = axis_order;
}
return dataSpec;
});
console.log("Plot " + index + " for export.", dataSpecs, this.props.plotGMs[index], plot.template);
return this.canvas.plot(dataSpecs, this.props.plotGMs[index], plot.template).then(
success => {
console.log("Plot " + index + " complete.");
return;
},
error => {
this.canvas.close();
this.error = true;
delete this.canvas;
this.canvas = vcs.init(this.div);
if (error.data) {
console.warn("Error while creating save plot: ", error);
toast.error(error.data.exception, { position: toast.POSITION.BOTTOM_CENTER });
} else {
console.warn("Unknown error while saving plot: ", error);
toast.error("Error while saving plot.", { position: toast.POSITION.BOTTOM_CENTER });
}
}
);
}
}

/* istanbul ignore next */
savePlot(){

if(this.canvasDiv){

if(!this.canvas) {
console.log("Canvas object is empty.");
return;
}

// Cancel export if canvas plots aren't ready
if(!this.ready) {

if(this.error){
// If an error had occurred, try plotting again
this.prepareCanvas();
}

this.notify = true; // Set notify flag, so user will be notified when plot is ready to save
toast.warn("Performing plotting operation again. Will notify when ready to save.", {position: toast.POSITION.BOTTOM_CENTER});
return;
}

// Validate screenshot name
let fileName = this.state.name;

Expand Down Expand Up @@ -75,45 +201,23 @@ class SavePlot extends Component{
return;
}

// Prepare parameters
// format of `sheet_row_col`. Ex: "0_0_0"
let sheet_row_col = this.props.selected_cell_id.split("_").map(function (str_val) { return Number(str_val) });
let sheet = sheet_row_col[0];
let row = sheet_row_col[1];
let col = sheet_row_col[2];

// Get info about the plot from redux store props
let plotInfo = this.props.sheets_model.sheets[sheet].cells[row][col].plots[0];

let variable = {
uri: this.props.variables[plotInfo.variables[0]].path,
variable: this.props.variables[plotInfo.variables[0]].cdms_var_name,
};

let graphicMethod = this.props.graphics[plotInfo.graphics_method_parent][plotInfo.graphics_method];

// Initialize canvas object and plot
let canvas = vcs.init(this.canvasDiv);

canvas.plot(variable, graphicMethod, plotInfo.template).then((info) => {
canvas.screenshot(ext, true, false, fileName, this.props.exportDimensions[0], this.props.exportDimensions[1]).then((result, msg) => {
// Create screenshot and save
this.canvas.screenshot(ext, true, false, fileName, this.props.exportDimensions[0], this.props.exportDimensions[1]).then((result, msg) => {
if(msg){
console.log(msg);
if(result.success){
const { blob, type } = result;
console.log(type + " file was saved.");
FileSaver.saveAs(blob, this.state.name);
toast.success("Plot saved!", {position: toast.POSITION.BOTTOM_CENTER});
this.setState({name:""});
} else {
console.log(result.msg);
}
}).catch((err) => {
console.log(err);
toast.error("Error occurred when saving plot.", {position: toast.POSITION.BOTTOM_CENTER});
});
}

if(result.success){
const { blob, type } = result;
FileSaver.saveAs(blob, this.state.name);
toast.success("Plot saved!", {position: toast.POSITION.BOTTOM_CENTER});
this.setState({name:""});
} else {
console.log(result.msg);
}
}).catch((err) => {
console.log(err);
toast.error("Error occurred when plotting.", {position: toast.POSITION.BOTTOM_CENTER});
toast.error("Error occurred when saving plot.", {position: toast.POSITION.BOTTOM_CENTER});
});
}
else{
Expand Down Expand Up @@ -149,13 +253,32 @@ class SavePlot extends Component{
}
}

const mapStateToProps = (state) => {
const mapStateToProps = (state, ownProps) => {

// When GMs are loaded, use this function to extract them from the state
var get_gm_for_plot = plot => {
return state.present.graphics_methods[plot.graphics_method_parent][plot.graphics_method];
};

var get_vars_for_plot = plot => {
return plot.variables.map(variable => {
return state.present.variables[variable];
});
};

return {
selected_cell_id: state.present.sheets_model.selected_cell_id,
sheets_model: state.present.sheets_model,
variables: state.present.variables,
graphics: state.present.graphics_methods
if(ownProps.plots){
return {
selected_cell_id: state.present.sheets_model.selected_cell_id,
plotVariables: ownProps.plots.map(get_vars_for_plot),
plotGMs: ownProps.plots.map(get_gm_for_plot)
}
}
else {
return {
selected_cell_id: state.present.sheets_model.selected_cell_id,
plotVariables: null,
plotGMs: null
}
}
}

Expand All @@ -168,10 +291,10 @@ SavePlot.propTypes = {
handleDimensionUpdate: PropTypes.func,
handleChangeExt: PropTypes.func,
exportType: PropTypes.string,
plots: PropTypes.any,
plotVariables: PropTypes.array,
plotGMs: PropTypes.array,
onSave: PropTypes.func,
variables: PropTypes.any,
graphics: PropTypes.any,
sheets_model: PropTypes.any
}

export default connect(mapStateToProps, null)(SavePlot)
export default connect(mapStateToProps)(SavePlot);
4 changes: 2 additions & 2 deletions frontend/src/js/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const JOYRIDE_STEPS = [
text: "The following tour will help guide you through the basic features of vCDAT. ".concat(
'Click "Next" to continue the tour. ',
'<div class="contact-us-link">Found a bug or need help? ',
'<a href="https://github.com/CDAT/vcdat/wiki/Contact-Us" target="_blank">Contact Us</a></div>'
'<a href="https://cdat.github.io/vcdat/docs/html/user_contact-us.html" target="_blank">Contact Us</a></div>'
),
selector: ".joyride",
position: "top",
Expand Down Expand Up @@ -107,7 +107,7 @@ const JOYRIDE_STEPS = [
'</br><span style="color: green;">Add Plot</span> will add an additional plot to a cell. ',
"Use this as an overlay or as an in-cell side-by-side comparison.",
'</br><span style="color: red;">Clear Cell</span> will reset the cell back to the default. ',
"This can be undone if you accidentally click it with the undo button.",
"This can be undone with the undo button, if you accidentally click the 'clear cell' button.",
'</br><span style="color: blue;">Colormap Editor</span> will open a window for creating, editing, and applying colormaps.',
'</br><span style="color: purple;">Export</span> allows you to export/save the plot.',
'</br><span style="color: orange;">Calculator</span> allows you to derive new variables using',
Expand Down
Loading

0 comments on commit 9e7e401

Please sign in to comment.