Skip to content

Commit

Permalink
Import export colormaps (#329)
Browse files Browse the repository at this point in the history
* added exporting colormaps from vcs backend

* export json format now matches backend

* attempting to get import working

* add default param to saveColormap(); pass current_colormap_name as prop to ImportExportModal

* convert vcs-style colormap json object to old frontend style for importing; Create a new colormap in VCS before saving when importing a colormap from json

* removed unneeded backend code added during import/export development

* removed outdated "data" key from state object

* extracted convertToOldJson() into separate function
  • Loading branch information
William-Hill authored and downiec committed Nov 7, 2018
1 parent a073da3 commit d673bcf
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 46 deletions.
16 changes: 8 additions & 8 deletions backend/vcdat/app.py
Expand Up @@ -20,7 +20,7 @@ def wrap(*args, **kwargs):
return r
return wrap


app = Flask(__name__, static_url_path='')
_ = vcs.init()

Expand Down Expand Up @@ -77,12 +77,12 @@ def serve_resource_file(path):
mimetype = "image/svg"
else:
mimetype = "text/plain"
try:
try:
return Response(pkg_resources.resource_string(__name__, "resources/" + path), mimetype=mimetype)
except IOError:
return Response(status=404)


@app.route("/plotTemplate")
def plot_template():
tmpl = json.loads(request.args["tmpl"])
Expand All @@ -108,26 +108,26 @@ def plot_template():
# For certain templates the renWin can be None
if(canvas.backend.renWin):
# Only call render if renWin exists
canvas.backend.renWin.Render()
canvas.backend.renWin.Render()
canvas.png(tmp)
# create response from the tmp file, blank or otherwise
resp = send_file(tmp)
resp = send_file(tmp)
# Clean up file automatically after request
wr = weakref.ref(resp, lambda x: os.remove(tmp))
canvas.close()
# clean up temporary boxfill and template we created
del vcs.elements["boxfill"][g.name]
del vcs.elements["boxfill"][g.name]
del vcs.elements["template"][t.name]
return resp


@app.route("/getDefaultMethods")
@jsonresp
def get_default_methods():
default_gms = get_default_gms()
return json.dumps(default_gms)


@app.route("/getColormaps")
@jsonresp
def get_colormaps():
Expand Down
57 changes: 31 additions & 26 deletions frontend/src/js/components/modals/ColormapEditor/ColormapEditor.jsx
Expand Up @@ -25,7 +25,7 @@ class ColormapEditor extends Component {
selected_cells_end: -1,
current_colormap: this.props.colormaps[this.props.default_colormap].map(function(arr) {
return arr.slice()
}), // an array of arrays representing the current cells
}), // an array of arrays representing the current cells
}
}

Expand All @@ -44,7 +44,7 @@ class ColormapEditor extends Component {
applyColormap: PropTypes.func,
graphics_methods: PropTypes.object,
startTour: PropTypes.func,
};
};
}

handleChange(color) {
Expand Down Expand Up @@ -141,7 +141,7 @@ class ColormapEditor extends Component {
selected_cells_start: -1,
selected_cells_end: -1,
})
}
}
else{
return
}
Expand All @@ -151,12 +151,12 @@ class ColormapEditor extends Component {
}
}

createNewColormap(new_cm_name){
createNewColormap(new_cm_name, colormap=this.state.selected_colormap_name){
if(Object.keys(this.props.colormaps).indexOf(new_cm_name) >= 0){
toast.warn("A colormap with that name already exists. Please select a different name", {position: toast.POSITION.BOTTOM_CENTER})
}
else{
return this.createNewColormapInVcs(this.state.selected_colormap_name, new_cm_name).then((result)=>{
return this.createNewColormapInVcs(colormap, new_cm_name).then((result)=>{
if(result){
this.handleSelectColormap(new_cm_name)
this.setState({
Expand All @@ -169,11 +169,14 @@ class ColormapEditor extends Component {
}
}

saveColormap(name){
saveColormap(name, colormap=this.state.current_colormap){
if(name){
try{
return vcs.setcolormap(name, this.state.current_colormap).then(() => {
this.props.saveColormap(name, this.state.current_colormap)
return vcs.setcolormap(name, colormap).then(() => {
if (colormap != this.state.current_colormap){
this.setState({current_colormap: colormap})
}
this.props.saveColormap(name, colormap)
toast.success("Save Successful", { position: toast.POSITION.BOTTOM_CENTER });
PubSub.publish(PubSubEvents.colormap_update, name)
},
Expand All @@ -183,7 +186,7 @@ class ColormapEditor extends Component {
toast.error(error.data.exception, {position: toast.POSITION.BOTTOM_CENTER})
}
catch(e){
toast.error("Failed to save colormap", { position: toast.POSITION.BOTTOM_CENTER });
toast.error("Failed to save colormap", { position: toast.POSITION.BOTTOM_CENTER });
}
})
}
Expand Down Expand Up @@ -222,11 +225,11 @@ class ColormapEditor extends Component {
catch(e){
return false
}

}
return new Promise((resolve, reject) => {
try{
/* eslint-disable no-undef */
/* eslint-disable no-undef */
if(vcs){
vcs.getcolormapnames().then((names) => {
if(names.indexOf(colormap_name) >= 0){
Expand Down Expand Up @@ -270,7 +273,7 @@ class ColormapEditor extends Component {
}

createNewColormapInVcs(base_cm, name){
// create should copy the current colormap, save it into vcs,
// create should copy the current colormap, save it into vcs,
// add it to redux and set it as active in the widget, and close the modal
// cancel should close the modal
try{
Expand All @@ -295,7 +298,7 @@ class ColormapEditor extends Component {
toast.error("Failed to create colormap", {position: toast.POSITION.BOTTOM_CENTER})
}
return Promise.resolve(false)

}

blendColors(){
Expand All @@ -319,7 +322,7 @@ class ColormapEditor extends Component {
let blendedColormap = this.state.current_colormap.map(function(arr) {
return arr.slice(); // copy inner array of colors
});

for(let count = 1; currentCell < endCell; currentCell++, count++){
blendedColormap[currentCell][0] = startColor[0] + (redStep * count)
blendedColormap[currentCell][1] = startColor[1] + (greenStep * count)
Expand Down Expand Up @@ -361,7 +364,7 @@ class ColormapEditor extends Component {
</select>
</div>
<div>
<button
<button
id='btn-new-colormap'
title="Create a new copy of the selected colormap"
onClick={() => {this.handleOpenNewColormapModal()}}
Expand All @@ -379,36 +382,36 @@ class ColormapEditor extends Component {
</div>
</div>
<hr/>
<ColorPicker
<ColorPicker
color={this.state.currentColor}
onChange={(color) => {this.handleChange(color)}}
/>
<ColormapWidget
current_colormap={this.state.current_colormap}
color={this.state.currentColor}
color={this.state.currentColor}
handleCellClick={(start_cell, end_cell) => {this.handleCellClick(start_cell, end_cell)}}
selected_cells_start={this.state.selected_cells_start}
selected_cells_end= {this.state.selected_cells_end}
selected_cells_end= {this.state.selected_cells_end}
/>
</Modal.Body>
<Modal.Footer>
<Button
<Button
id='btn-blend'
style={{float: "left"}}
onClick={() => {this.blendColors()}}>
Blend
</Button>
<Button
<Button
style={{float: "left"}}
onClick={() => {this.resetColormap()}}>
Reset
</Button>
<Button
style={{float: "left"}}
disabled={apply_disabled}
title={ apply_disabled ?
"A cell must be selected to apply a colormap"
:
title={ apply_disabled ?
"A cell must be selected to apply a colormap"
:
"Apply the colormap shown to the currently selected cell"
}
onClick={() => {this.handleApplyColormap()}}>
Expand All @@ -420,7 +423,7 @@ class ColormapEditor extends Component {
</Modal.Footer>
</div>
</Modal>
<NewColormapModal
<NewColormapModal
show={this.state.show_new_colormap_modal}
close={() => this.setState({show_new_colormap_modal: false})}
newColormap={(name) => this.createNewColormap(name)}
Expand All @@ -429,7 +432,9 @@ class ColormapEditor extends Component {
show={this.state.show_import_export_modal}
close={()=>{this.closeImportExportModal()}}
current_colormap={this.state.current_colormap}
current_colormap_name={this.state.selected_colormap_name}
saveColormap={(name, cm) => this.saveColormap(name, cm)}
createNewColormap={(name, cm) => this.createNewColormap(name, cm)}
/>
</div>
)
Expand Down Expand Up @@ -470,7 +475,7 @@ const mapDispatchToProps = (dispatch) => {
}
}
return false

},
applyColormap: (graphics_method_parent, graphics_method, row, col, plot_index) =>{
dispatch(Actions.swapGraphicsMethodInPlot(graphics_method_parent, graphics_method, row, col, plot_index));
Expand All @@ -485,4 +490,4 @@ const mapDispatchToProps = (dispatch) => {
}

export default connect(mapStateToProps, mapDispatchToProps)(ColormapEditor);
export {ColormapEditor as PureColormapEditor}
export {ColormapEditor as PureColormapEditor}
Expand Up @@ -8,33 +8,48 @@ class ImportExportModal extends Component {
super(props);
this.state = {
name: "",
data: "",
importName: "",
importColormap: [],
colormapData: ""
}
}

static get propTypes() {
return {
static get propTypes() {
return {
show: PropTypes.bool.isRequired, // show the modal
close: PropTypes.func.isRequired, // close the modal
current_colormap: PropTypes.array,
current_colormap_name: PropTypes.string,
saveColormap: PropTypes.func,
};
createNewColormap: PropTypes.func
};
}

handleChange(e) {
let colormap = {
name: e.target.value,
colormap: this.props.current_colormap,
}
let data = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(colormap));

this.setState({
name: e.target.value,
data: data
colormapData: this.props.current_colormap

})
}

convertToOldJson(obj){
let old_json_format = {}
let name = Object.keys(obj["Cp"])[0]
old_json_format["name"] = name
old_json_format["colormap"] = []
let myColorValues = Object(obj["Cp"][name]["index"]["data"]);
for (let i=0 ; i < Object.keys(myColorValues).length; i++){
old_json_format["colormap"].push(myColorValues[String(i)])
}
return old_json_format
}

handleFileChange(e){
let reader = new window.FileReader()
function handleLoad(event){
Expand All @@ -47,7 +62,13 @@ class ImportExportModal extends Component {
console.error("Unable to import colormap. Please check that the file contains valid json.")
return
}

if (Object.keys(obj)[0] == "Cp"){
obj = this.convertToOldJson(obj);
}

let keys = Object.keys(obj)

if(keys.indexOf("name") === -1 || keys.indexOf("colormap") === -1){
console.error(
"Unable to import colormap. Please check that the file contains both 'name' and 'colormap' keys."
Expand All @@ -60,8 +81,29 @@ class ImportExportModal extends Component {
reader.readAsText(e.target.files[0])
}

exportColormap(){
const name = this.state.name.substring(0, this.state.name.indexOf( ".json" ));
let myObject = {"Cp":{}};
myObject["Cp"][name] = {"index": {"data": {}}}
for (var i = 0; i < this.state.colormapData.length; i++){
myObject["Cp"][name]["index"]["data"][i] = this.state.colormapData[i]
}

// Create hidden anchor tag for downloading the dynamically create JSON object
var element = document.createElement('a');
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(myObject));
element.setAttribute('href', dataStr);
element.setAttribute('download', this.state.name);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);

}

importColormap(){
this.props.saveColormap(this.state.importName, this.state.importColormap)
this.props.createNewColormap(this.state.importName, this.props.current_colormap_name)
this.props.saveColormap(this.state.importName, this.state.importColormap)
}

render(){
Expand All @@ -74,17 +116,17 @@ class ImportExportModal extends Component {
<Modal.Body>
<h4>Export Current Colormap</h4>
<div className="form-group form-inline">
<input
<input
placeholder="filename.json"
className="form-control"
type="text"
value={this.state.name}
onChange={(e)=>{this.handleChange(e)}}>
</input>
<Button
id="downloadAnchorElem"
className="btn btn-primary form-control"
href={this.state.data}
download={this.state.name}>
onClick={()=>{this.exportColormap()}}>
Download
</Button>
</div>
Expand All @@ -97,7 +139,7 @@ class ImportExportModal extends Component {
type="file"
onChange={(e)=>{this.handleFileChange(e)}}>
</input>
<Button
<Button
className="btn btn-primary form-control"
onClick={()=>{this.importColormap()}}>
Upload
Expand All @@ -113,4 +155,4 @@ class ImportExportModal extends Component {
}
}

export default ImportExportModal;
export default ImportExportModal;

0 comments on commit d673bcf

Please sign in to comment.