Skip to content

Commit 634ace1

Browse files
authored
DM-10275: merge download plotly image as PNG; pr #373
download plotly image as PNG
2 parents a0d8ac1 + 3c2c0bf commit 634ace1

File tree

8 files changed

+56
-23
lines changed

8 files changed

+56
-23
lines changed

src/firefly/js/charts/ChartUtil.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@
77
* Utilities related to charts
88
* Created by tatianag on 3/17/16.
99
*/
10-
import {uniqueId, isUndefined, omitBy} from 'lodash';
10+
import {get, uniqueId, isUndefined, omitBy} from 'lodash';
1111

12+
import {getAppOptions} from '../core/AppDataCntlr.js';
1213
import {getTblById, getColumnIdx, getCellValue} from '../tables/TableUtil.js';
1314
import {Expression} from '../util/expr/Expression.js';
1415
import {logError} from '../util/WebUtil.js';
1516

1617
export const SCATTER = 'scatter';
1718
export const HISTOGRAM = 'histogram';
1819

20+
export function isPlotly() {
21+
return get(getAppOptions(), 'charts.chartEngine')==='plotly';
22+
}
1923

2024
/**
2125
* This method returns an object with the keys x,y,highlightedRow
@@ -24,7 +28,7 @@ export const HISTOGRAM = 'histogram';
2428
* @param {string} tblId
2529
* @returns {{x: number, y: number, rowIdx}}
2630
*/
27-
export const getHighlighted = function(xyPlotParams, tblId) {
31+
export function getHighlighted(xyPlotParams, tblId) {
2832

2933
const tableModel = getTblById(tblId);
3034
if (tableModel && xyPlotParams) {
@@ -46,7 +50,7 @@ export const getHighlighted = function(xyPlotParams, tblId) {
4650
});
4751
return highlighted;
4852
}
49-
};
53+
}
5054

5155
/**
5256
* This method returns the value of the column cell or an expression from multiple column cells in a given row

src/firefly/js/charts/chartTypes/HistogramTblView.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {HelpIcon} from '../../ui/HelpIcon.jsx';
1111
import {ToolbarButton} from '../../ui/ToolbarButton.jsx';
1212

1313
import * as ChartsCntlr from '../ChartsCntlr.js';
14+
import {isPlotly} from '../ChartUtil.js';
15+
import {downloadChart} from '../ui/PlotlyWrapper.jsx';
1416
import {HistogramOptions} from '../ui/HistogramOptions.jsx';
1517
import {Histogram} from '../ui/Histogram.jsx';
1618
import {getChartProperties, updateOnStoreChange, FilterEditorWrapper} from './TblView.jsx';
@@ -19,6 +21,7 @@ import OUTLINE_EXPAND from 'html/images/icons-2014/24x24_ExpandArrowsWhiteOutlin
1921
import SETTINGS from 'html/images/icons-2014/24x24_GearsNEW.png';
2022
import CLEAR_FILTERS from 'html/images/icons-2014/24x24_FilterOff_Circle.png';
2123
import FILTER from 'html/images/icons-2014/24x24_Filter.png';
24+
import SAVE from 'html/images/icons-2014/24x24_Save.png';
2225
import LOADING from 'html/images/gxt/loading.gif';
2326

2427
export const HISTOGRAM_TBLVIEW = {
@@ -33,7 +36,7 @@ export const HISTOGRAM_TBLVIEW = {
3336

3437

3538
function Chart(props) {
36-
const {chartId, tblId, chartData, widthPx, heightPx, eventCallback} = props;
39+
const {chartId, tblId, chartData, widthPx, heightPx} = props;
3740
if (!TblUtil.isFullyLoaded(tblId) || !chartData || !heightPx || !widthPx) {
3841
return (<div/>);
3942
}
@@ -72,7 +75,6 @@ function Chart(props) {
7275
xAxis={xAxis}
7376
yAxis={yAxis}
7477
xUnit={unit}
75-
eventCallback={eventCallback}
7678
chartId={chartId}
7779

7880
/>
@@ -133,6 +135,7 @@ Options.propTypes = {
133135
optionsKey: PropTypes.string
134136
};
135137

138+
136139
function Toolbar({chartId, expandable, expandedMode, toggleOptions}) {
137140
const {tableModel, help_id} = getChartProperties(chartId);
138141
return (
@@ -150,6 +153,10 @@ function Toolbar({chartId, expandable, expandedMode, toggleOptions}) {
150153
visible={true}
151154
badgeCount={TblUtil.getFilterCount(tableModel)}
152155
onClick={() => toggleOptions('filters')}/>
156+
{isPlotly() && <img className='PanelToolbar__button'
157+
title='Download the chart as a PNG image'
158+
src={SAVE}
159+
onClick={() => downloadChart(chartId)}/>}
153160
<img className='PanelToolbar__button'
154161
title='Chart options and tools'
155162
src={SETTINGS}

src/firefly/js/charts/chartTypes/ScatterTblView.jsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {HelpIcon} from '../../ui/HelpIcon.jsx';
1515
import {ToolbarButton} from '../../ui/ToolbarButton.jsx';
1616

1717
import * as ChartsCntlr from '../ChartsCntlr.js';
18-
import {getHighlighted} from '../ChartUtil.js';
18+
import {getHighlighted, isPlotly} from '../ChartUtil.js';
19+
import {downloadChart} from '../ui/PlotlyWrapper.jsx';
1920
import {setXYSelection, setZoom} from '../dataTypes/XYColsCDT.js';
2021

2122
import {showInfoPopup} from '../../ui/PopupUtil.jsx';
@@ -33,6 +34,7 @@ import ZOOM_ORIGINAL from 'html/images/icons-2014/Zoom1x-24x24-tmp.png';
3334
import UNSELECT_ROWS from 'html/images/icons-2014/24x24_CheckmarkOff_Circle.png';
3435
import CLEAR_FILTERS from 'html/images/icons-2014/24x24_FilterOff_Circle.png';
3536
import FILTER from 'html/images/icons-2014/24x24_Filter.png';
37+
import SAVE from 'html/images/icons-2014/24x24_Save.png';
3638
import LOADING from 'html/images/gxt/loading.gif';
3739

3840

@@ -83,7 +85,8 @@ class ChartComp extends React.Component {
8385
const sInfo = tableModel && tableModel.selectInfo;
8486

8587
return (
86-
<XYPlot data={xyPlotData}
88+
<XYPlot chartId={chartId}
89+
data={xyPlotData}
8790
desc=''
8891
width={widthPx}
8992
height={heightPx}
@@ -191,6 +194,10 @@ function Toolbar({chartId, expandable, expandedMode, toggleOptions}) {
191194
visible={true}
192195
badgeCount={TblUtil.getFilterCount(tableModel)}
193196
onClick={() => toggleOptions('filters')}/>
197+
{isPlotly() && <img className='PanelToolbar__button'
198+
title='Download the chart as a PNG image'
199+
src={SAVE}
200+
onClick={() => downloadChart(chartId)}/>}
194201
<img className='PanelToolbar__button'
195202
title='Chart options and tools'
196203
src={SETTINGS}

src/firefly/js/charts/ui/Histogram.jsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
33
*/
44

5-
import {get} from 'lodash';
6-
import {getAppOptions} from '../../core/AppDataCntlr.js';
75
import {HistogramPlotly} from './HistogramPlotly.jsx';
86
import {HistogramHighcharts} from './HistogramHighcharts.jsx';
7+
import {isPlotly} from '../ChartUtil.js';
98
import React from 'react';
109

1110
export function Histogram(props) {
12-
if (get(getAppOptions(), 'charts.chartEngine') !== 'plotly') {
13-
return <HistogramHighcharts {...props}/>;
14-
} else {
15-
return <HistogramPlotly {...props} />;
16-
}
11+
const HistogramInstance = isPlotly() ? HistogramPlotly : HistogramHighcharts;
12+
return <HistogramInstance {...props}/>;
1713
}
1814

src/firefly/js/charts/ui/HistogramPlotly.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export class HistogramPlotly extends React.Component {
284284
const {plotlyData, plotlyDivStyle, plotlyLayout}= this.chartingInfo;
285285

286286
return (
287-
<PlotlyWrapper data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
287+
<PlotlyWrapper chartId={this.props.chartId} data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
288288
dataUpdate={dataUpdate}
289289
layoutUpdate={layoutUpdate}
290290
divUpdateCB={(div) => this.chart= div}
@@ -302,6 +302,7 @@ HistogramPlotly.defaultProps = {
302302

303303

304304
HistogramPlotly.propTypes = {
305+
chartId: PropTypes.string,
305306
series: PropTypes.arrayOf(PropTypes.object), // array of objects with data, binColor, and name properties
306307
xAxis: PropTypes.object,
307308
yAxis: PropTypes.object,

src/firefly/js/charts/ui/PlotlyWrapper.jsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@
33
*/
44

55
import React, {Component, PropTypes} from 'react';
6+
import {get} from 'lodash';
67
import {getPlotLy} from '../PlotlyConfig.js';
8+
import {logError} from '../../util/WebUtil.js';
79
import Enum from 'enum';
810

911
const PLOTLY_BASE_ID= 'plotly-plot';
1012
var counter= 0;
1113

12-
13-
1414
export const RenderType= new Enum([ 'RESIZE', 'UPDATE', 'RESTYLE', 'RELAYOUT', 'RESTYLE_AND_RELAYOUT', 'NEW_PLOT'],
1515
{ ignoreCase: true });
1616

1717

18-
1918
const defaultConfig= {
2019
displaylogo: false,
2120
modeBarButtonsToRemove :[
@@ -25,6 +24,21 @@ const defaultConfig= {
2524
]
2625
};
2726

27+
export function downloadChart(chartId) {
28+
getPlotLy().then( (Plotly) => {
29+
const chartDiv = document.getElementById(chartId);
30+
if (chartId && chartDiv) {
31+
const filename = get(chartDiv, 'layout.title') || chartId;
32+
Plotly.downloadImage(chartDiv, {
33+
format: 'png',
34+
filename
35+
});
36+
} else {
37+
logError(`Image download has failed for chart id ${chartId}`);
38+
}
39+
});
40+
}
41+
2842
export class PlotlyWrapper extends Component {
2943

3044
constructor(props) {
@@ -117,12 +131,13 @@ export class PlotlyWrapper extends Component {
117131
}
118132

119133
render() {
120-
const {style}= this.props;
134+
const {chartId, style}= this.props;
121135
// note: wrapper div is the target for the simulated click event
122136
// when the original click event is lost and plotly_click is emitted instead
137+
// chart image download relies on div id matching chartId
123138
return (
124139
<div>
125-
<div id={this.id} style={style} ref={this.refUpdate}/>
140+
<div id={chartId || this.id} style={style} ref={this.refUpdate}/>
126141
</div>
127142
);
128143
}
@@ -131,6 +146,7 @@ export class PlotlyWrapper extends Component {
131146

132147

133148
PlotlyWrapper.propTypes = {
149+
chartId: PropTypes.string,
134150
data: PropTypes.arrayOf(PropTypes.object),
135151
style :PropTypes.object,
136152
layout: PropTypes.object,

src/firefly/js/charts/ui/XYPlot.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import {get, omit} from 'lodash';
55
import shallowequal from 'shallowequal';
66
import React, {PropTypes} from 'react';
7-
import {getAppOptions} from '../../core/AppDataCntlr.js';
7+
import {isPlotly} from '../ChartUtil.js';
88
import {XYPlotHighcharts} from './XYPlotHighcharts.jsx';
99
import {XYPlotPlotly} from './XYPlotPlotly.jsx';
1010
import {plotParamsShape, plotDataShape} from './XYPlotPropTypes.js';
@@ -221,13 +221,14 @@ export class XYPlot extends React.Component {
221221
</div>
222222
);
223223
} else {
224-
const XYPlotInstance= get(getAppOptions(), 'charts.chartEngine')==='plotly' ? XYPlotPlotly : XYPlotHighcharts;
224+
const XYPlotInstance= isPlotly() ? XYPlotPlotly : XYPlotHighcharts;
225225
return (<XYPlotInstance {...this.props}/>);
226226
}
227227
}
228228
}
229229

230230
XYPlot.propTypes = {
231+
chartId: PropTypes.string,
231232
data: plotDataShape,
232233
width: PropTypes.number,
233234
height: PropTypes.number,

src/firefly/js/charts/ui/XYPlotPlotly.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ export class XYPlotPlotly extends React.Component {
786786

787787
return (
788788
<div style={{float: 'left'}}>
789-
<PlotlyWrapper data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
789+
<PlotlyWrapper chartId={this.props.chartId} data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
790790
dataUpdateTraces={dataUpdateTraces}
791791
dataUpdate={dataUpdate}
792792
layoutUpdate={layoutUpdate}
@@ -799,6 +799,7 @@ export class XYPlotPlotly extends React.Component {
799799
}
800800

801801
XYPlotPlotly.propTypes = {
802+
chartId: PropTypes.string,
802803
data: plotDataShape,
803804
width: PropTypes.number,
804805
height: PropTypes.number,

0 commit comments

Comments
 (0)