Skip to content

DM-9944 XYPlot with plotly library #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 19, 2017
5 changes: 5 additions & 0 deletions src/firefly/html/demo/ffapi-highlevel-charttest.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ <h2>
<br/><br/>
<div id="xyplotHere" style="width: 800px; height: 550px; border: solid 1px;"></div>

<script type="text/javascript">
if (!window.firefly) window.firefly= {};
window.firefly.options= {charts: {chartEngine: 'plotly'}};
</script>

<script type="text/javascript">
{
onFireflyLoaded= function(firefly) {
Expand Down
6 changes: 6 additions & 0 deletions src/firefly/html/demo/ffapi-highlevel-test.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@
</div>
</div>

<script type="text/javascript">
if (!window.firefly) window.firefly= {};
window.firefly.options= {charts: {chartEngine: 'plotly'}};
</script>


<script type="text/javascript">
{
function loadMoving() {
Expand Down
4 changes: 3 additions & 1 deletion src/firefly/js/FFEntryPoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,7 @@ if (app.template) {
viewer = Templates[props.template];
}

firefly.bootstrap(app.options, viewer, props);
const options= get(window, 'firefly.app.options') || get(window, 'firefly.options');

firefly.bootstrap(options, viewer, props);

4 changes: 3 additions & 1 deletion src/firefly/js/charts/PlotlyConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// NOTE - Remember to uncomment both the import and the register

import {loadScript} from '../util/WebUtil.js';
import {getRootURL} from '../util/BrowserUtil.js';
/*
import Plotly from 'plotly.js/lib/core';
import bar from 'plotly.js/lib/bar';
Expand Down Expand Up @@ -86,9 +87,10 @@ function initPlotLyRetriever(loadNow) {
return loadedPlotly ? Promise.resolve(loadedPlotly) : Promise.reject(Error(LOAD_ERR_MSG));
}

const script= `${getRootURL()}/${PLOTLY_SCRIPT}`;
if (!plotlyLoadBegin) {
plotlyLoadBegin= true;
loadScript(PLOTLY_SCRIPT).then( () => {
loadScript(script).then( () => {
loadedPlotly= window.Plotly;
waitingResolvers.forEach( (r) => r(loadedPlotly));
waitingResolvers= undefined;
Expand Down
11 changes: 8 additions & 3 deletions src/firefly/js/charts/chartTypes/HistogramTblView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
*/

import {get, set} from 'lodash';
import {get, isEmpty, set} from 'lodash';
import React, {PropTypes} from 'react';
import * as TblUtil from '../../tables/TableUtil.js';

Expand Down Expand Up @@ -42,8 +42,10 @@ function Chart(props) {

if (isDataReady) {
var logs;
const xAxis = histogramParams.xAxis || {reversed: false, opposite: false};
const yAxis = histogramParams.yAxis || {reversed: false, opposite: false};
let xAxis = histogramParams.xAxis || {};
let yAxis = histogramParams.yAxis || {};


if (histogramParams) {
var logvals = '';
if (histogramParams.x.includes('log')) { logvals += 'x';}
Expand All @@ -57,6 +59,9 @@ function Chart(props) {
if (histogramParams.y.includes('opposite')) { set(yAxis, 'opposite', true); }
}

if (isEmpty(xAxis)) { xAxis = undefined;}
if (isEmpty(yAxis)) { yAxis = undefined;}

return (
<Histogram data={histogramData}
desc={histogramParams.columnOrExpr}
Expand Down
10 changes: 8 additions & 2 deletions src/firefly/js/charts/dataTypes/XYColsCDT.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,18 @@ function fetchXYLargeTable(dispatch, chartId, chartDataElementId) {
const colNames = decimateKey ? ['x', 'y', 'rowIdx', 'weight'] : ['x', 'y', 'rowIdx'];

// change row data from [ [val] ] to [ {cname:val} ] and make them numeric
const rows = tableModel.tableData.data.map((row) => {
const getARow = (row) => {
return colNames.reduce( (arow, name, cidx) => {
arow[name] = parseFloat(row[cidx]);
return arow;
}, {});
});
};
const getDecimatedARow = (row) => {
const arow = getARow(row);
arow.decimate_key = row[4]; // 4th column in decimated table is decimate_key
return arow;
};
const rows = tableModel.tableData.data.map(decimateKey ? getDecimatedARow : getARow);

xyPlotData = omitBy({
rows,
Expand Down
8 changes: 3 additions & 5 deletions src/firefly/js/charts/ui/ChartPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class ChartPanelView extends Component {
}

render() {
const {chartId, chartData, deletable, expandable, expandedMode, Chart, Options, Toolbar, showToolbar, showChart, eventCallback} = this.props;
const {chartId, chartData, deletable, expandable, expandedMode, Chart, Options, Toolbar, showToolbar, showChart} = this.props;

if (!chartData) {
return (<div/>);
Expand Down Expand Up @@ -190,8 +190,7 @@ ChartPanelView.propTypes = {
showChart: PropTypes.bool,
Chart: PropTypes.func,
Options: PropTypes.func,
Toolbar: PropTypes.func,
eventCallback: PropTypes.object
Toolbar: PropTypes.func
};

ChartPanelView.defaultProps = {
Expand Down Expand Up @@ -292,6 +291,5 @@ ChartPanel.propTypes = {
expandedMode: PropTypes.bool,
deletable: PropTypes.bool,
showToolbar: PropTypes.bool,
showChart: PropTypes.bool,
eventCallback: PropTypes.object
showChart: PropTypes.bool
};
43 changes: 13 additions & 30 deletions src/firefly/js/charts/ui/HistogramPlotly.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class HistogramPlotly extends React.Component {
}

regenData(props) {
var {data, series, xAxis, yAxis, logs, desc, binColor, xUnit} = props;
var {data, series, xAxis, yAxis, logs, desc, binColor, xUnit, width, height} = props;
if (!data) {
data = series && series.data;
}
Expand Down Expand Up @@ -153,6 +153,8 @@ export class HistogramPlotly extends React.Component {
},
plotlyData,
plotlyLayout: {
height,
width,
hovermode: 'x',
xaxis: {
title: `${desc} ` + (xUnit ? `(${xUnit})` : ''),
Expand Down Expand Up @@ -192,7 +194,7 @@ export class HistogramPlotly extends React.Component {
}

shouldComponentUpdate(nextProps, ns) {
const bUpdate = sCompare(nextProps, ns);
const bUpdate = sCompare(this, nextProps, ns);

if (bUpdate && nextProps !== this.props) {
this.chartingInfo = this.regenData(nextProps);
Expand Down Expand Up @@ -274,46 +276,28 @@ export class HistogramPlotly extends React.Component {
this.setState({leftMargin, rightMargin, layoutUpdate: {margin}});
}
}

// add event handler to 'plotly_click' in case mouse click happens when the mouse is hovering on the bar
const {eventCallback} = this.props;
if (eventCallback) {
Object.keys(eventCallback).forEach((eventKey) => {
if (eventKey === 'mousedown') {
chart.on('plotly_click', eventCallback[eventKey]);
}
});
}
}

render() {
this.error = undefined;
const {dataUpdate, layoutUpdate, config} = this.state;
const {plotlyData, plotlyDivStyle, plotlyLayout}= this.chartingInfo;
const {width, height, eventCallback} = this.props;

//add click event handler to div in case the mouse click happens when the mouse is located on non-bar area
return (
<div style={{width: width? width: undefined,
height: height? height: undefined}}
onClick={get(eventCallback, 'mousedown')}>
<PlotlyWrapper data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
dataUpdate={dataUpdate}
layoutUpdate={layoutUpdate}
divUpdateCB={(div) => this.chart= div}
config={config}
newPlotCB={this.afterRedraw}
/>
</div>
<PlotlyWrapper data={plotlyData} layout={plotlyLayout} style={plotlyDivStyle}
dataUpdate={dataUpdate}
layoutUpdate={layoutUpdate}
divUpdateCB={(div) => this.chart= div}
config={config}
newPlotCB={this.afterRedraw}
/>
);
}
}

HistogramPlotly.defaultProps = {
desc: 'Sample Distribution',
binColor: '#d1d1d1',
xAxis: {},
yAxis: {}
binColor: '#d1d1d1'
};


Expand All @@ -331,6 +315,5 @@ HistogramPlotly.propTypes = {
if (props[propName] && !/^#[0-9a-f]{6}/.test(props[propName])) {
return new Error(`Invalid bin color in ${componentName}, should be hex with exactly 7 characters long.`);
}
},
eventCallback: PropTypes.object
}
};
4 changes: 2 additions & 2 deletions src/firefly/js/charts/ui/MultiChartViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ export class MultiChartViewer extends Component {
};

const makeItemViewer = (chartId) => (
<div className={chartId === activeItemId ? 'ChartPanel ChartPanel--active' : 'ChartPanel'} onMouseDown={()=>onChartSelect(chartId)}>
<ChartPanel key={chartId} showToolbar={false} chartId={chartId} eventCallback={{'mousedown': ()=>onChartSelect(chartId)}}/>;
<div className={chartId === activeItemId ? 'ChartPanel ChartPanel--active' : 'ChartPanel'} onClick={()=>onChartSelect(chartId)}>
<ChartPanel key={chartId} showToolbar={false} chartId={chartId}/>;
</div>
);

Expand Down
63 changes: 37 additions & 26 deletions src/firefly/js/charts/ui/PlotlyWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/

import React, {Component, PropTypes} from 'react';
// import Plotly, {getPlotLy} from '../PlotlyConfig.js';
import {getPlotLy} from '../PlotlyConfig.js';
import Enum from 'enum';

Expand All @@ -26,7 +25,7 @@ const defaultConfig= {
]
};

export class PlotlyWrapper extends React.Component {
export class PlotlyWrapper extends Component {

constructor(props) {
super(props);
Expand Down Expand Up @@ -61,29 +60,37 @@ export class PlotlyWrapper extends React.Component {

draw() {
const {data,layout, config= defaultConfig, newPlotCB, dataUpdate, layoutUpdate, dataUpdateTraces}= this.props;
const {renderType}= this;
const renderType = this.renderType;
getPlotLy().then( (Plotly) => {
switch (renderType) {
case RenderType.RESTYLE:
Plotly.restyle(this.div, dataUpdate, dataUpdateTraces);
break;
case RenderType.RELAYOUT:
Plotly.relayout(this.div, layoutUpdate);
break;
case RenderType.RESTYLE_AND_RELAYOUT:
Plotly.restyle(this.div, dataUpdate, dataUpdateTraces);
Plotly.relayout(this.div, layoutUpdate);
break;
case RenderType.RESIZE:
Plotly.Plots.resize(this.div);
break;
case RenderType.UPDATE:
Plotly.update(this.div, data, layout);
break;
case RenderType.NEW_PLOT:
Plotly.newPlot(this.div, data, layout, config);
if (newPlotCB) newPlotCB(this.div,Plotly);
break;
if (this.div) { // make sure the div is still there
switch (renderType) {
case RenderType.RESTYLE:
Plotly.restyle(this.div, dataUpdate, dataUpdateTraces);
break;
case RenderType.RELAYOUT:
Plotly.relayout(this.div, layoutUpdate);
break;
case RenderType.RESTYLE_AND_RELAYOUT:
Plotly.restyle(this.div, dataUpdate, dataUpdateTraces);
Plotly.relayout(this.div, layoutUpdate);
break;
case RenderType.RESIZE:
Plotly.Plots.resize(this.div);
break;
case RenderType.UPDATE:
Plotly.update(this.div, data, layout);
break;
case RenderType.NEW_PLOT:
Plotly.newPlot(this.div, data, layout, config);
if (this.div.on) {
const chart = this.div;
chart.on('plotly_click', () => chart.parentElement.click());
}
if (newPlotCB) {
newPlotCB(this.div, Plotly);
}
break;
}
}
} ).catch( (e) => {
console.log('Plotly not loaded',e);
Expand Down Expand Up @@ -111,8 +118,12 @@ export class PlotlyWrapper extends React.Component {

render() {
const {style}= this.props;
// note: wrapper div is the target for the simulated click event
// when the original click event is lost and plotly_click is emitted instead
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glad you documented the wrapper div

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the density, when you hover over the active point, it loses the depth (3rd) line.

It's not exactly a bug. We point the point from the highlighted row, which might not be present in the decimated data. Though I can still find a bin to which the highlighted point belongs and add "represents".

return (
<div id={this.id} style={style} ref={this.refUpdate}></div>
<div>
<div id={this.id} style={style} ref={this.refUpdate}/>
</div>
);
}
}
Expand All @@ -128,5 +139,5 @@ PlotlyWrapper.propTypes = {
dataUpdateTraces: PropTypes.number,
layoutUpdate: PropTypes.object,
divUpdateCB: PropTypes.func,
newPlotCB: PropTypes.func,
newPlotCB: PropTypes.func
};
Loading