diff --git a/src/firefly/js/api/ApiHighlevelBuild.js b/src/firefly/js/api/ApiHighlevelBuild.js
index 28bc9ecf08..72f166880a 100644
--- a/src/firefly/js/api/ApiHighlevelBuild.js
+++ b/src/firefly/js/api/ApiHighlevelBuild.js
@@ -357,12 +357,12 @@ function makePlotId() {
//================================================================
function showXYPlot(llApi, targetDiv, params={}) {
- const {dispatchSetupTblTracking, dispatchTableFetch}= llApi.action;
+ const {dispatchTableFetch}= llApi.action;
const {TBL_RESULTS_ACTIVE} = llApi.action.type;
const {renderDOM} = llApi.util;
const {makeFileRequest, getActiveTableId, uniqueTblId} = llApi.util.table;
const {uniqueChartId, loadPlotDataForTbl} = llApi.util.chart;
- const {ChartsContainer, ChartsTableViewPanel}= llApi.ui;
+ const {ChartsTableViewPanel}= llApi.ui;
const {addActionListener} = llApi.util;
if ((typeof targetDiv).match(/string|HTMLDivElement/) === null) {
@@ -397,7 +397,6 @@ function showXYPlot(llApi, targetDiv, params={}) {
);
tblId = searchRequest.tbl_id;
dispatchTableFetch(searchRequest);
- dispatchSetupTblTracking(tblId);
}
const chartId = uniqueChartId(tblId||tblGroup);
diff --git a/src/firefly/js/tables/TablesCntlr.js b/src/firefly/js/tables/TablesCntlr.js
index 7c0c48b68b..c8345897b6 100644
--- a/src/firefly/js/tables/TablesCntlr.js
+++ b/src/firefly/js/tables/TablesCntlr.js
@@ -10,7 +10,6 @@ import shallowequal from 'shallowequal';
import {dataReducer} from './reducer/TableDataReducer.js';
import {uiReducer} from './reducer/TableUiReducer.js';
import {resultsReducer} from './reducer/TableResultsReducer.js';
-import {dispatchSetupTblTracking} from '../visualize/TableStatsCntlr.js';
import {dispatchAddSaga} from '../core/MasterSaga.js';
export const TABLE_SPACE_PATH = 'table_space';
@@ -48,8 +47,7 @@ export function tableSearch(action) {
var {request, options, tbl_group} = action.payload;
const {tbl_id} = request;
const title = get(request, 'META_INFO.title');
-
- dispatchSetupTblTracking(tbl_id);
+
dispatchTableFetch(request);
if (!TblUtil.getTableInGroup(tbl_id, tbl_group)) {
const {tbl_group, removable} = options || {};
diff --git a/src/firefly/js/visualize/ChartUtil.js b/src/firefly/js/visualize/ChartUtil.js
index 2acc013b1a..3c9f1f563b 100644
--- a/src/firefly/js/visualize/ChartUtil.js
+++ b/src/firefly/js/visualize/ChartUtil.js
@@ -18,6 +18,8 @@ import {logError} from '../util/WebUtil.js';
import {XYPLOT_DATA_KEY} from '../visualize/XYPlotCntlr.js';
import {HISTOGRAM_DATA_KEY} from '../visualize/HistogramCntlr';
+export const SCATTER = 'scatter';
+export const HISTOGRAM = 'histogram';
/**
* This method returns an object with the keys x,y,highlightedRow
@@ -63,20 +65,33 @@ function getExpressionValue(strExpr, tableModel, rowIdx) {
}
}
+
+export function getChartSpace(chartType) {
+ switch(chartType) {
+ case SCATTER:
+ return flux.getState()[XYPLOT_DATA_KEY];
+ case HISTOGRAM:
+ return flux.getState()[HISTOGRAM_DATA_KEY];
+ default:
+ logError(`Unknown chart type ${chartType}`);
+ return undefined;
+ }
+}
+
export function hasRelatedCharts(tblId, space) {
if (space) {
return Boolean(Object.keys(space).find((chartId) => {
return space[chartId].tblId === tblId;
}));
} else {
- return hasRelatedCharts(tblId, flux.getState()[XYPLOT_DATA_KEY]) ||
- hasRelatedCharts(tblId, flux.getState()[HISTOGRAM_DATA_KEY]);
+ return hasRelatedCharts(tblId, getChartSpace(SCATTER)) ||
+ hasRelatedCharts(tblId, getChartSpace(HISTOGRAM));
}
}
export function getTblIdForChartId(chartId) {
- return get(flux.getState()[XYPLOT_DATA_KEY], [chartId, 'tblId']) ||
- get(flux.getState()[HISTOGRAM_DATA_KEY], [chartId, 'tblId']);
+ return get(getChartSpace(SCATTER), [chartId, 'tblId']) ||
+ get(getChartSpace(HISTOGRAM), [chartId, 'tblId']);
}
export function numRelatedCharts(tblId) {
@@ -86,7 +101,7 @@ export function numRelatedCharts(tblId) {
keys.forEach( (key) => {
const space = flux.getState()[key];
for (c in space) {
- if (space[c].tblId === tblId) {
+ if (space.hasOwnProperty(c) && space[c].tblId === tblId) {
numRelated++;
}
}
diff --git a/src/firefly/js/visualize/ChartsCntlr.js b/src/firefly/js/visualize/ChartsCntlr.js
index 90172c0ce4..9d0333bcaa 100644
--- a/src/firefly/js/visualize/ChartsCntlr.js
+++ b/src/firefly/js/visualize/ChartsCntlr.js
@@ -2,13 +2,20 @@
* License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
*/
+import {has, get, isUndefined, omit} from 'lodash';
+
import {flux} from '../Firefly.js';
-import {updateSet} from '../util/WebUtil.js';
+import {updateSet, updateMerge} from '../util/WebUtil.js';
+
+import * as TablesCntlr from '../tables/TablesCntlr.js';
export const CHART_SPACE_PATH = 'charts';
export const UI_PREFIX = `${CHART_SPACE_PATH}.ui`;
export const CHART_UI_EXPANDED = `${UI_PREFIX}.expanded`;
+export const CHART_MOUNTED = `${UI_PREFIX}/mounted`;
+export const CHART_UNMOUNTED = `${UI_PREFIX}/unmounted`;
+export const DELETE = `${UI_PREFIX}/delete`;
/**
* request to put a chart into an expanded mode.
@@ -21,15 +28,132 @@ export function dispatchChartExpanded(chartId, tblId, chartType, optionsPopup) {
flux.process( {type: CHART_UI_EXPANDED, payload: {chartId, tblId, chartType, optionsPopup}});
}
+/*
+ * Dispatched when chart becomes visible (is rendered for the first time after being invisible)
+ * When chart is mounted, its data need to be in sync with the related table
+ * @param {string} tblId - table id (table data for this chart)
+ * @param {string} chartId - chart id
+ * @param {string} chartType - chart type, ex. scatter, histogram
+ */
+export function dispatchChartMounted(tblId, chartId, chartType, dispatcher= flux.process) {
+ dispatcher({type: CHART_MOUNTED, payload: {tblId, chartId, chartType}});
+}
+
+/*
+ * Dispatched when chart becomes invisible
+ * When chart is unmounted, its data synced with the related table only when it becomes mounted again
+ * @param {string} tblId - table id (table data for this chart)
+ * @param {string} chartId - chart id
+ * @param {string} chartType - chart type, ex. scatter, histogram
+ */
+export function dispatchChartUnmounted(tblId, chartId, chartType, dispatcher= flux.process) {
+ dispatcher({type: CHART_UNMOUNTED, payload: {tblId, chartId, chartType}});
+}
+
+/*
+ * Delete chart and related data
+ * @param {string} tblId - table id (table data for this chart)
+ * @param {string} chartId - chart id
+ * @param {string} chartType - chart type, ex. scatter, histogram
+ */
+export function dispatchDelete(tblId, chartId, chartType, dispatcher= flux.process) {
+ dispatcher({type: DELETE, payload: {tblId, chartId, chartType}});
+}
+
+const uniqueId = (chartId, chartType) => { return `${chartType}|${chartId}`; };
+const isChartType = (uChartId, chartType) => { return uChartId.startsWith(chartType); };
+
+const chartActions = [CHART_UI_EXPANDED,CHART_MOUNTED,CHART_UNMOUNTED,DELETE];
+
export function reducer(state={ui:{}}, action={}) {
- //var root = state.ui;
- switch (action.type) {
- case (CHART_UI_EXPANDED) :
- const {chartId, tblId, chartType, optionsPopup} = action.payload;
- //return updateSet(root, 'expanded', {chartId, tblId});
- return updateSet(state, 'ui.expanded', {chartId, tblId, chartType, optionsPopup});
- default:
- //return root;
- return state;
+ if (action.type === TablesCntlr.TABLE_REMOVE) {
+ const {tbl_id} = action.payload;
+ if (has(state, tbl_id)) {
+ return Object.assign({}, omit(state, [tbl_id]));
+ }
+ return state;
+ } else if (chartActions.indexOf(action.type) > -1) {
+ const {chartId, tblId, chartType} = action.payload;
+ const uChartId = uniqueId(chartId, chartType);
+ switch (action.type) {
+
+ case (CHART_UI_EXPANDED) :
+ const {optionsPopup} = action.payload;
+ //return updateSet(root, 'expanded', {chartId, tblId});
+ return updateSet(state, 'ui.expanded', {chartId, tblId, chartType, optionsPopup});
+ case (CHART_MOUNTED) :
+
+ if (!has(state, ['tbl', tblId])) {
+ return updateSet(state, ['tbl',tblId], {[uChartId]: {mounted: true}});
+ } else {
+ if (get(state, ['tbl', tblId, uChartId, 'mounted'])) {
+ //other version of the same chart is mounted
+ let n = get(state, ['tbl', tblId, uChartId, 'n']);
+ n = n ? Number(n) : 1;
+ return updateMerge(state, ['tbl', tblId, uChartId], {upToDate: true, n: n + 1});
+ } else {
+ return updateSet(state, ['tbl', tblId, uChartId], {mounted: true});
+ }
+ }
+ case (CHART_UNMOUNTED) :
+ if (has(state, ['tbl', tblId])) {
+ if (get(state, ['tbl', tblId, uChartId, 'mounted'])) {
+ let n = get(state, ['tbl', tblId, uChartId, 'n']);
+ n = n ? Number(n) : 1;
+ if (n > 1) {
+ //multiple versions of the same chart are mounted
+ return updateMerge(state, ['tbl', tblId, uChartId], {n: n - 1});
+ }
+ } else {
+ return updateSet(state, ['tbl', tblId, uChartId], {mounted: false});
+ }
+ }
+ return state;
+ case (DELETE) :
+ if (has(state, ['tbl', tblId, uChartId])) {
+ if (Object.keys(state['tbl'][tblId]).length > 1) {
+ return updateSet(state, ['tbl', tblId], omit(state['tbl'][tblId], [uChartId]));
+ } else {
+ return updateSet(state, 'tbl', omit(state['tbl'], [tblId]));
+ }
+ }
+ return state;
+ default:
+ return state;
+ }
+ } else {
+ return state;
}
+}
+
+
+export function getExpandedChartProps() {
+ return get(flux.getState(), [CHART_SPACE_PATH, 'ui', 'expanded']);
+}
+
+
+export function getNumRelatedCharts(tblId, mounted, chartType) {
+ let numRelated = 0;
+ const byTblSpace = get(flux.getState(), [CHART_SPACE_PATH, 'tbl']);
+ if (byTblSpace) {
+ if (isUndefined(tblId)) {
+ Object.keys(byTblSpace).forEach((tblId)=>{
+ numRelated += getNumRelatedCharts(tblId, mounted);
+ });
+ } else if (byTblSpace[tblId]) {
+ Object.keys(byTblSpace[tblId]).forEach((uChartId) => {
+ if (isUndefined(mounted)||byTblSpace[tblId][uChartId].mounted===mounted) {
+ if (isUndefined(chartType) || isChartType(uChartId, chartType)) {
+ numRelated++;
+ }
+ }
+ });
+ }
+ }
+ return numRelated;
+}
+
+export function isChartMounted(tblId, chartId, chartType) {
+ const uChartId = uniqueId(chartId, chartType);
+ return Boolean(get(flux.getState(), [CHART_SPACE_PATH, 'tbl', tblId, uChartId, 'mounted']));
}
\ No newline at end of file
diff --git a/src/firefly/js/visualize/ChartsContainer.jsx b/src/firefly/js/visualize/ChartsContainer.jsx
index 2ea0864b27..5500e7d756 100644
--- a/src/firefly/js/visualize/ChartsContainer.jsx
+++ b/src/firefly/js/visualize/ChartsContainer.jsx
@@ -8,17 +8,13 @@ import shallowequal from 'shallowequal';
import {flux} from '../Firefly.js';
-import {get} from 'lodash';
import {LO_VIEW, LO_MODE, dispatchSetLayoutMode, getExpandedMode} from '../core/LayoutCntlr.js';
import {CloseButton} from '../ui/CloseButton.jsx';
import {ChartsTableViewPanel} from '../visualize/ChartsTableViewPanel.jsx';
-import {CHART_SPACE_PATH} from '../visualize/ChartsCntlr.js';
+import {getExpandedChartProps} from '../visualize/ChartsCntlr.js';
-export function getExpandedChartProps() {
- return get(flux.getState(),[CHART_SPACE_PATH, 'ui', 'expanded']);
-}
function nextState(props) {
const {closeable, chartId, tblId, chartType, optionsPopup} = props;
@@ -34,16 +30,18 @@ export class ChartsContainer extends Component {
}
componentWillReceiveProps(np) {
- if (!this.isUnmounted && !shallowequal(this.props, np)) {
+ if (this.iAmMounted && !shallowequal(this.props, np)) {
this.setState(nextState(np));
}
}
componentDidMount() {
this.removeListener= flux.addListener(() => this.storeUpdate());
+ this.iAmMounted = true;
}
componentWillUnmount() {
+ this.iAmMounted = false;
this.removeListener && this.removeListener();
}
@@ -53,7 +51,7 @@ export class ChartsContainer extends Component {
storeUpdate() {
- if (!this.isUnmounted) {
+ if (this.iAmMounted) {
const expandedMode = this.props.expandedMode && getExpandedMode() === LO_VIEW.xyPlots;
if (expandedMode !== this.state.expandedMode) {
this.setState(nextState(this.props));
diff --git a/src/firefly/js/visualize/ChartsTableViewPanel.jsx b/src/firefly/js/visualize/ChartsTableViewPanel.jsx
index 0a2bdc80f9..d652dec1bb 100644
--- a/src/firefly/js/visualize/ChartsTableViewPanel.jsx
+++ b/src/firefly/js/visualize/ChartsTableViewPanel.jsx
@@ -19,11 +19,11 @@ import {FilterInfo} from '../tables/FilterInfo.js';
import * as TableStatsCntlr from '../visualize/TableStatsCntlr.js';
import * as HistogramCntlr from '../visualize/HistogramCntlr.js';
import * as XYPlotCntlr from '../visualize/XYPlotCntlr.js';
-import {dispatchChartExpanded} from '../visualize/ChartsCntlr.js';
+import {dispatchChartExpanded, dispatchDelete, dispatchChartMounted, dispatchChartUnmounted} from '../visualize/ChartsCntlr.js';
import {LO_MODE, LO_VIEW, dispatchSetLayoutMode} from '../core/LayoutCntlr.js';
-import {getHighlighted, getTblIdForChartId, numRelatedCharts} from './ChartUtil.js';
+import {SCATTER, HISTOGRAM, getHighlighted, getTblIdForChartId, numRelatedCharts} from './ChartUtil.js';
import XYPlotOptions from '../visualize/XYPlotOptions.jsx';
import {XYPlot} from '../visualize/XYPlot.jsx';
@@ -48,30 +48,16 @@ import CLEAR_FILTERS from 'html/images/icons-2014/24x24_FilterOff_Circle.png';
import LOADING from 'html/images/gxt/loading.gif';
-const SCATTER = 'scatter';
-const HISTOGRAM = 'histogram';
const OPTIONS_WIDTH = 330;
-const PREF_CHART_TYPE = 'pref.chartType';
-
const selectionBtnStyle = {verticalAlign: 'top', paddingLeft: 20, cursor: 'pointer'};
-function getChartType(props) {
- if (props.chartId !== props.tblId) {
- if (props.tblPlotData) { return SCATTER; }
- if (props.tblHistogramData) { return HISTOGRAM; }
- } else {
- return props.chartType;
- }
- //return (localStorage.getItem(PREF_CHART_TYPE) || SCATTER);
-}
class ChartsPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
- chartType: getChartType(props),
optionsShown: !props.chartId,
immediateResize: false
};
@@ -81,7 +67,7 @@ class ChartsPanel extends React.Component {
var widthPx = size.width;
var heightPx = size.height;
//console.log('width: '+widthPx+', height: '+heightPx);
- if (widthPx !== this.state.widthPx || heightPx != this.state.heightPx) {
+ if (widthPx !== this.state.widthPx || heightPx !== this.state.heightPx) {
this.setState({widthPx, heightPx, immediateResize: false});
}
}
@@ -110,7 +96,6 @@ class ChartsPanel extends React.Component {
this.selectionNotEmpty = this.selectionNotEmpty.bind(this);
this.renderSelectionButtons = this.renderSelectionButtons.bind(this);
this.renderToolbar = this.renderToolbar.bind(this);
- this.onChartTypeChange = this.onChartTypeChange.bind(this);
this.renderOptions = this.renderOptions.bind(this);
}
@@ -121,7 +106,7 @@ class ChartsPanel extends React.Component {
nextProps.expandedMode !== this.props.expandedMode ||
nextProps.deletable !== this.props.deletable;
if (!doUpdate) {
- const chartType = getChartType(nextProps);
+ const {chartType} = nextProps;
if (chartType === SCATTER) {
// scatter plot
doUpdate =
@@ -129,7 +114,7 @@ class ChartsPanel extends React.Component {
(nextProps.tableModel &&
(nextProps.tableModel.highlightedRow !== get(this.props, 'tableModel.highlightedRow') ||
nextProps.tableModel.selectInfo !== get(this.props, 'tableModel.selectInfo') ));
- } else {
+ } else if (chartType === HISTOGRAM){
// histogram
doUpdate = nextProps.tblHistogramData !== this.props.tblHistogramData;
}
@@ -139,6 +124,8 @@ class ChartsPanel extends React.Component {
componentDidMount() {
this.handlePopups();
+ const {tblId, chartId, chartType} = this.props;
+ dispatchChartMounted(tblId,chartId,chartType);
this.iAmMounted = true;
}
@@ -147,11 +134,18 @@ class ChartsPanel extends React.Component {
}
componentWillReceiveProps(nextProps) {
- this.setState({chartType: getChartType(nextProps)});
+ const {tblId, chartId, chartType} = nextProps;
+ if (!tblId || !chartId) { return; }
+ if (chartId !== this.props.chartId || chartType !== this.props.chartType || !this.props.tblId) {
+ dispatchChartUnmounted(this.props.tblId, this.props.chartId, this.props.chartType);
+ dispatchChartMounted(tblId,chartId,chartType);
+ }
}
componentWillUnmount() {
this.iAmMounted = false;
+ const {tblId, chartId, chartType} = this.props;
+ dispatchChartUnmounted(tblId, chartId, chartType);
if (isDialogVisible(popupId)) {
hideChartOptionsPopup();
}
@@ -159,9 +153,9 @@ class ChartsPanel extends React.Component {
handlePopups() {
if (this.props.optionsPopup) {
- const {optionsShown, chartType} = this.state;
+ const {optionsShown} = this.state;
if (optionsShown) {
- const {tableModel, tblStatsData, tblPlotData, tblHistogramData, chartId} = this.props;
+ const {tableModel, tblStatsData, tblPlotData, tblHistogramData, chartId, chartType} = this.props;
// show options popup
let popupTitle = 'Chart Options';
@@ -289,7 +283,7 @@ class ChartsPanel extends React.Component {
}
displaySelectionOptions() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
const selection = get(this.props, 'tblPlotData.xyPlotParams.selection');
return Boolean(selection);
}
@@ -298,7 +292,7 @@ class ChartsPanel extends React.Component {
}
displayZoomOriginal() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
const zoom = get(this.props, 'tblPlotData.xyPlotParams.zoom');
return Boolean(zoom);
}
@@ -307,26 +301,26 @@ class ChartsPanel extends React.Component {
}
addZoom() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
XYPlotCntlr.dispatchZoom(this.props.chartId, this.props.tblId, get(this.props, 'tblPlotData.xyPlotParams.selection'));
}
}
resetZoom() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
XYPlotCntlr.dispatchZoom(this.props.chartId, this.props.tblId);
}
}
displayUnselectAll () {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
const selectInfo = get(this.props, 'tableModel.selectInfo');
return selectInfo && (selectInfo.selectAll || selectInfo.exceptions.size>0);
}
}
addSelection() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
if (get(this.props, 'tblPlotData.xyPlotData.decimateKey')) {
showInfoPopup('Your data set is too large to select. You must filter it down first.',
`Can't Select`); // eslint-disable-line quotes
@@ -354,7 +348,7 @@ class ChartsPanel extends React.Component {
}
resetSelection() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
const {tblId, tableModel} = this.props;
if (tableModel) {
const selectInfoCls = SelectInfo.newInstance({rowCount: tableModel.totalRows});
@@ -370,7 +364,7 @@ class ChartsPanel extends React.Component {
}
addFilter() {
- if (this.state.chartType === SCATTER) {
+ if (this.props.chartType === SCATTER) {
const {tblPlotData, tableModel} = this.props;
const selection = get(tblPlotData, 'xyPlotParams.selection');
const xCol = get(tblPlotData, 'xyPlotParams.x.columnOrExpr');
@@ -463,8 +457,7 @@ class ChartsPanel extends React.Component {
}
renderToolbar() {
- const {expandable, expandedMode, tblId, chartId, optionsPopup, deletable} = this.props;
- const chartType = this.state.chartType;
+ const {expandable, expandedMode, tblId, chartId, chartType, optionsPopup, deletable} = this.props;
return (
![]()
}
{ expandable && !expandedMode &&
- isBoolean(deletable) ? deletable : numRelatedCharts(tblId) > 1 && // when deletable is undefined, use related charts criterion
+ (isBoolean(deletable) ? deletable : numRelatedCharts(tblId) > 1) && // when deletable is undefined, use related charts criterion

{
- if (chartType === SCATTER) {
- XYPlotCntlr.dispatchDelete(chartId);
- } else if (chartType === HISTOGRAM) {
- HistogramCntlr.dispatchDelete(chartId);
- }
- }}
+ onClick={() => {dispatchDelete(tblId, chartId, chartType);}}
/>}
@@ -502,22 +489,9 @@ class ChartsPanel extends React.Component {
}
- onChartTypeChange(ev) {
- // the value of the group is the value of the selected option
- var val = ev.target.value;
- var checked = ev.target.checked;
-
- if (checked) {
- if (val !== this.state.chartType) {
- localStorage.setItem(PREF_CHART_TYPE, val);
- this.setState({chartType : val});
- }
- }
- }
-
renderOptions() {
- const {optionsShown, chartType, heightPx} = this.state;
- const { tableModel, tblStatsData, tblPlotData, tblHistogramData, optionsPopup, chartId} = this.props;
+ const {optionsShown, heightPx} = this.state;
+ const { tableModel, tblStatsData, tblPlotData, tblHistogramData, optionsPopup, chartId, chartType} = this.props;
if (optionsShown && !optionsPopup) {
return (
@@ -531,7 +505,7 @@ class ChartsPanel extends React.Component {
render() {
- var {tblStatsData} = this.props;
+ var {tblStatsData, chartType} = this.props;
if (!(tblStatsData && tblStatsData.isColStatsReady) ) {
return (

);
} else {
- var {widthPx, heightPx, chartType} = this.state;
+ var {widthPx, heightPx} = this.state;
const knownSize = widthPx && heightPx;
if (chartType === SCATTER && !this.props.tblPlotData ||
@@ -575,6 +549,7 @@ ChartsPanel.propTypes = {
tableModel : PropTypes.object,
tblStatsData : PropTypes.object,
chartId: PropTypes.string,
+ chartType: PropTypes.oneOf(['scatter', 'histogram']),
tblPlotData : PropTypes.object,
tblHistogramData : PropTypes.object,
width : PropTypes.string,
@@ -653,12 +628,12 @@ export class OptionsWrapper extends React.Component {
}
shouldComponentUpdate(nProps) {
- return nProps.chartId != this.props.chartId ||
+ return nProps.chartId !== this.props.chartId ||
get(nProps, 'tblPlotData.xyPlotParams') !== get(this.props, 'tblPlotData.xyPlotParams') ||
get(nProps, 'tblHistogramData.histogramParams') !== get(this.props, 'tblHistogramData.histogramParams') ||
get(nProps, 'tableModel.tbl_id') !== get(this.props, 'tableModel.tbl_id') ||
get(nProps, 'tblStatsData.isColStatsReady') !== get(this.props, 'tblStatsData.isColStatsReady') ||
- nProps.chartType != this.props.chartType;
+ nProps.chartType !== this.props.chartType;
}
// componentDidUpdate(prevProps, prevState) {
diff --git a/src/firefly/js/visualize/HistogramCntlr.js b/src/firefly/js/visualize/HistogramCntlr.js
index c093d26733..7796c6a618 100644
--- a/src/firefly/js/visualize/HistogramCntlr.js
+++ b/src/firefly/js/visualize/HistogramCntlr.js
@@ -3,11 +3,12 @@
*/
import {flux} from '../Firefly.js';
-import {has, omit} from 'lodash';
+import {get, has, omit} from 'lodash';
import {updateSet, updateMerge} from '../util/WebUtil.js';
import {doFetchTable, getTblById, isFullyLoaded, makeTblRequest} from '../tables/TableUtil.js';
import * as TablesCntlr from '../tables/TablesCntlr.js';
+import {DELETE} from './ChartsCntlr.js';
/*
Possible structure of store:
@@ -16,6 +17,7 @@ import * as TablesCntlr from '../tables/TablesCntlr.js';
{
// tblHistogramData
tblId: string // table id
+ tblSource: string // source of the table
isColDataReady: boolean
histogramData: [[numInBin: int, min: double, max: double]*]
histogramParams: {
@@ -35,7 +37,6 @@ import * as TablesCntlr from '../tables/TablesCntlr.js';
export const HISTOGRAM_DATA_KEY = 'histogram';
export const LOAD_COL_DATA = `${HISTOGRAM_DATA_KEY}/LOAD_COL_DATA`;
export const UPDATE_COL_DATA = `${HISTOGRAM_DATA_KEY}/UPDATE_COL_DATA`;
-export const DELETE = `${HISTOGRAM_DATA_KEY}/DELETE`;
/*
@@ -48,14 +49,6 @@ export const dispatchLoadColData = function(chartId, histogramParams, tblId, dis
dispatcher({type: LOAD_COL_DATA, payload: {chartId, histogramParams, tblId}});
};
-/*
- * Delete chart and related data
- * @param {String} chartId - chart id
- */
-export function dispatchDelete(chartId) {
- flux.process({type: DELETE, payload: {chartId}});
-}
-
/*
* Get column histogram data
* @param {string} chartId - chart id
@@ -73,23 +66,51 @@ const dispatchUpdateColData = function(chartId, isColDataReady, histogramData, h
*/
export const loadColData = function(rawAction) {
return (dispatch) => {
- dispatch({ type : LOAD_COL_DATA, payload : rawAction.payload });
+
const {chartId, histogramParams, tblId} = rawAction.payload;
- if (isFullyLoaded(tblId) && histogramParams) {
- const searchRequest = getTblById(tblId)['request'];
- fetchColData(dispatch, searchRequest, histogramParams, chartId);
- }
+ const tblSource = get(getTblById(tblId), 'tableMeta.source');
+
+ const chartModel = get(flux.getState(), [HISTOGRAM_DATA_KEY, chartId]);
+ let serverCallNeeded = !chartModel || !chartModel.tblSource || chartModel.tblSource !== tblSource;
+
+ if (serverCallNeeded || chartModel.histogramParams !== histogramParams) {
+ // when server call parameters do not change but chart parameters change,
+ // we do need to update parameters, but we can reuse the old chart data
+ serverCallNeeded = serverCallNeeded || serverParamsChanged(chartModel.histogramParams, histogramParams);
+ dispatch({type: LOAD_COL_DATA, payload: {...rawAction.payload, tblSource, serverCallNeeded}});
+ if (serverCallNeeded) {
+ fetchColData(dispatch, tblId, histogramParams, chartId);
+ }
+ }
};
};
-function getInitState() {
- return {};
-}
+function serverParamsChanged(oldParams, newParams) {
+ if (oldParams === newParams) { return false; }
+ if (!oldParams || !newParams) { return true; }
+ const newServerParams = getServerCallParameters(newParams);
+ const oldServerParams = getServerCallParameters(oldParams);
+ return newServerParams.some((p, i) => {
+ return p !== oldServerParams[i];
+ });
+}
+function getServerCallParameters(histogramParams) {
+ if (!histogramParams) { return []; }
+
+ const serverParams = [];
+ serverParams.push(histogramParams.columnOrExpr);
+ serverParams.push(histogramParams.x && histogramParams.x.includes('log'));
+ serverParams.push(histogramParams.numBins);
+ serverParams.push(histogramParams.falsePositiveRate);
+ //serverParams.push(histogramParams.minCutoff);
+ //serverParams.push(histogramParams.maxCutoff);
+ return serverParams;
+}
-export function reducer(state=getInitState(), action={}) {
+export function reducer(state={}, action={}) {
switch (action.type) {
case (TablesCntlr.TABLE_REMOVE) :
{
@@ -105,20 +126,29 @@ export function reducer(state=getInitState(), action={}) {
}
case (DELETE) :
{
- const chartId = action.payload.chartId;
- return has(state, chartId) ? Object.assign({}, omit(state, [chartId])) : state;
+ const {chartId, chartType} = action.payload;
+ if (chartType === 'histogram' && has(state, chartId)) {
+ return Object.assign({}, omit(state, [chartId]));
+ }
+ return state;
}
case (LOAD_COL_DATA) :
{
- const {chartId, histogramParams, tblId} = action.payload;
- return updateSet(state, chartId, {tblId, isColDataReady: false, histogramParams});
+ const {chartId, tblId, histogramParams, tblSource, serverCallNeeded} = action.payload;
+ if (serverCallNeeded) {
+ return updateSet(state, chartId, {tblId, isColDataReady: false, tblSource, histogramParams});
+ } else {
+ // only histogram parameters changed
+ return updateSet(state, [chartId, 'histogramParams'], histogramParams);
+ }
}
case (UPDATE_COL_DATA) :
{
- const {chartId, isColDataReady, histogramData, histogramParams} = action.payload;
+ const {chartId, isColDataReady, tblSource, histogramData, histogramParams} = action.payload;
if (state[chartId].histogramParams === histogramParams) {
return updateMerge(state, chartId, {
isColDataReady,
+ tblSource,
histogramData
});
} else {
@@ -143,13 +173,20 @@ function updateColData(data) {
* fetches active table statistics data
* set isColStatsReady to true once done.
* @param {function} dispatch
- * @param {Object} activeTableServerRequest table search request to obtain source table
+ * @param {Object} tblId table id of the source table
* @param {Object} histogramParams object, which contains histogram parameters
* @param {string} chartId - chart id
*/
-function fetchColData(dispatch, activeTableServerRequest, histogramParams, chartId) {
+function fetchColData(dispatch, tblId, histogramParams, chartId) {
+
+ if (!isFullyLoaded(tblId) || !histogramParams) {
+ return;
+ }
+
+ const activeTableModel = getTblById(tblId);
+ const activeTableServerRequest = activeTableModel['request'];
+ const tblSource = get(activeTableModel, 'tableMeta.source');
- //const {tbl_id} = activeTableServerRequest;
const sreq = Object.assign({}, omit(activeTableServerRequest, ['tbl_id', 'META_INFO']),
{'startIdx' : 0, 'pageSize' : 1000000});
@@ -200,6 +237,7 @@ function fetchColData(dispatch, activeTableServerRequest, histogramParams, chart
{
chartId,
isColDataReady : true,
+ tblSource,
histogramParams,
histogramData
}));
diff --git a/src/firefly/js/visualize/TableStatsCntlr.js b/src/firefly/js/visualize/TableStatsCntlr.js
index 059f6d88da..47795d195b 100644
--- a/src/firefly/js/visualize/TableStatsCntlr.js
+++ b/src/firefly/js/visualize/TableStatsCntlr.js
@@ -21,18 +21,10 @@ import * as TablesCntlr from '../tables/TablesCntlr.js';
export const TBLSTATS_DATA_KEY = 'tblstats';
-export const SETUP_TBL_TRACKING = `${TBLSTATS_DATA_KEY}/SETUP_TBL_TRACKING`;
+
export const LOAD_TBL_STATS = `${TBLSTATS_DATA_KEY}/LOAD_TBL_STATS`;
export const UPDATE_TBL_STATS = `${TBLSTATS_DATA_KEY}/UPDATE_TBL_STATS`;
-/*
- * Set up store, which will reflect the data relevant to the given table
- * @param {string} tblId - table id
- * @param {function} dispatcher only for special dispatching uses such as remote
- */
-export function dispatchSetupTblTracking(tblId, dispatcher= flux.process) {
- dispatcher({type: SETUP_TBL_TRACKING, payload: {tblId}});
-}
/*
* Get the number of points, min and max values, units and description for each table column
@@ -74,14 +66,6 @@ function getInitState() {
export function reducer(state=getInitState(), action={}) {
switch (action.type) {
- case (SETUP_TBL_TRACKING) :
- {
- const {tblId} = action.payload;
- if (!state[tblId]) {
- return updateSet(state, tblId, {isColStatsReady: false});
- }
- return state;
- }
case (TablesCntlr.TABLE_REMOVE) :
{
const {tbl_id} = action.payload;
diff --git a/src/firefly/js/visualize/XYPlot.jsx b/src/firefly/js/visualize/XYPlot.jsx
index 587aeccd9f..84fa0cccc3 100644
--- a/src/firefly/js/visualize/XYPlot.jsx
+++ b/src/firefly/js/visualize/XYPlot.jsx
@@ -181,9 +181,26 @@ const getDeciSymbolSize = function(chart, decimateKeyStr) {
return {xUnitPx, yUnitPx};
};
-/*
- * Get data series
- */
+const calculateChartSize = function(widthPx, heightPx, props) {
+ const {params} = props;
+ let chartWidth = undefined, chartHeight = undefined;
+ if (params.xyRatio) {
+ if (params.stretch === 'fit') {
+ chartHeight = Number(heightPx) - 2;
+ chartWidth = Number(params.xyRatio) * Number(chartHeight) + 20;
+ if (chartWidth > Number(widthPx)) {
+ chartHeight -= 15; // to accommodate scroll bar
+ }
+ } else {
+ chartWidth = Number(widthPx) - 15;
+ chartHeight = Number(widthPx) / Number(params.xyRatio);
+ }
+ } else {
+ chartWidth = Number(widthPx);
+ chartHeight = Number(heightPx);
+ }
+ return {chartWidth, chartHeight};
+};
export class XYPlot extends React.Component {
@@ -191,7 +208,6 @@ export class XYPlot extends React.Component {
super(props);
this.updateSelectionRect = this.updateSelectionRect.bind(this);
this.adjustPlotDisplay = this.adjustPlotDisplay.bind(this);
- this.calculateChartSize=this.calculateChartSize.bind(this);
this.debouncedResize = this.debouncedResize.bind(this);
this.onSelectionEvent = this.onSelectionEvent.bind(this);
this.shouldAnimate = this.shouldAnimate.bind(this);
@@ -243,10 +259,21 @@ export class XYPlot extends React.Component {
const newXOptions = getXAxisOptions(newParams);
const newYOptions = getYAxisOptions(newParams);
if (!shallowequal(getXAxisOptions(params), newXOptions)) {
- Object.assign(xoptions, newXOptions);
+ Object.assign(xoptions, {
+ title: {text: newXOptions.xTitle},
+ gridLineWidth: newXOptions.xGrid ? 1 : 0,
+ reversed: newXOptions.xReversed,
+ opposite: newYOptions.yReversed,
+ type: newXOptions.xLog ? 'logarithmic' : 'linear'
+ });
}
if (!shallowequal(getYAxisOptions(params), newYOptions)) {
- Object.assign(yoptions, newYOptions);
+ Object.assign(yoptions, {
+ title: {text: newYOptions.yTitle},
+ gridLineWidth: newYOptions.yGrid ? 1 : 0,
+ reversed: newYOptions.yReversed,
+ type: newYOptions.yLog ? 'logarithmic' : 'linear'
+ });
}
if (!shallowequal(params.zoom, newParams.zoom)) {
const {xMin, xMax, yMin, yMax} = getZoomSelection(newParams);
@@ -269,8 +296,9 @@ export class XYPlot extends React.Component {
}
// size change
- if (newWidth !== width || newHeight !== height) {
- const {chartWidth, chartHeight} = this.calculateChartSize(newWidth, newHeight);
+ if (newWidth !== width || newHeight !== height ||
+ newParams.xyRatio !== params.xyRatio ||newParams.stretch != params.stretch) {
+ const {chartWidth, chartHeight} = calculateChartSize(newWidth, newHeight, nextProps);
chart.setSize(chartWidth, chartHeight, false);
if (this.pendingResize) {
@@ -295,27 +323,6 @@ export class XYPlot extends React.Component {
this.adjustPlotDisplay();
}
- calculateChartSize(widthPx, heightPx) {
- const {params} = this.props;
- let chartWidth = undefined, chartHeight = undefined;
- if (params.xyRatio) {
- if (params.stretch === 'fit') {
- chartHeight = Number(heightPx) - 2;
- chartWidth = Number(params.xyRatio) * Number(chartHeight) + 20;
- if (chartWidth > Number(widthPx)) {
- chartHeight -= 15; // to accommodate scroll bar
- }
- } else {
- chartWidth = Number(widthPx) - 15;
- chartHeight = Number(widthPx) / Number(params.xyRatio);
- }
- } else {
- chartWidth = Number(widthPx);
- chartHeight = Number(heightPx);
- }
- return {chartWidth, chartHeight};
- }
-
debouncedResize() {
return debounce(() => {
const chart = this.refs.chart && this.refs.chart.getChart();
@@ -515,7 +522,7 @@ export class XYPlot extends React.Component {
const {data, params, width, height, onSelection, desc} = this.props;
const onSelectionEvent = this.onSelectionEvent;
- const {chartWidth, chartHeight} = this.calculateChartSize(width, height);
+ const {chartWidth, chartHeight} = calculateChartSize(width, height, this.props);
const {xTitle, xGrid, xReversed, xLog} = getXAxisOptions(params);
const {yTitle, yGrid, yReversed, yLog} = getYAxisOptions(params);
diff --git a/src/firefly/js/visualize/XYPlotCntlr.js b/src/firefly/js/visualize/XYPlotCntlr.js
index 6c4a751d93..fbd620921e 100644
--- a/src/firefly/js/visualize/XYPlotCntlr.js
+++ b/src/firefly/js/visualize/XYPlotCntlr.js
@@ -8,6 +8,7 @@ import {get, has, omit, omitBy, isUndefined, isString} from 'lodash';
import {doFetchTable, getTblById, isFullyLoaded} from '../tables/TableUtil.js';
import * as TablesCntlr from '../tables/TablesCntlr.js';
+import {DELETE} from './ChartsCntlr.js';
import {serializeDecimateInfo} from '../tables/Decimate.js';
import {logError} from '../util/WebUtil.js';
import {getDefaultXYPlotParams} from '../visualize/ChartUtil.js';
@@ -15,7 +16,6 @@ import {getDefaultXYPlotParams} from '../visualize/ChartUtil.js';
export const XYPLOT_DATA_KEY = 'xyplot';
export const LOAD_PLOT_DATA = `${XYPLOT_DATA_KEY}/LOAD_COL_DATA`;
export const UPDATE_PLOT_DATA = `${XYPLOT_DATA_KEY}/UPDATE_COL_DATA`;
-export const DELETE = `${XYPLOT_DATA_KEY}/DELETE`;
export const SET_SELECTION = `${XYPLOT_DATA_KEY}/SET_SELECTION`;
const SET_ZOOM = `${XYPLOT_DATA_KEY}/SET_ZOOM`;
const RESET_ZOOM = `${XYPLOT_DATA_KEY}/RESET_ZOOM`;
@@ -27,6 +27,7 @@ const RESET_ZOOM = `${XYPLOT_DATA_KEY}/RESET_ZOOM`;
{
// tblXYPlotData
tblId: string // table id
+ tblSource: string // source of the table
isPlotDataReady: boolean
decimatedUnzoomed: boolean // tells that unzoomed data are decimated
xyPlotData: {
@@ -74,14 +75,6 @@ export function dispatchLoadPlotData(chartId, xyPlotParams, tblId, dispatcher= f
dispatcher({type: LOAD_PLOT_DATA, payload: {chartId: (chartId||tblId), xyPlotParams, tblId}});
}
-/*
- * Delete chart and related data
- * @param {String} chartId - chart id
- */
-export function dispatchDelete(chartId) {
- flux.process({type: DELETE, payload: {chartId}});
-}
-
/*
* Set selection to give user choice of actions on selection (zoom, filter, or select points)
* @param {String} chartId - chart id
@@ -143,11 +136,36 @@ function dispatchResetZoom(chartId) {
export function loadPlotData (rawAction) {
return (dispatch) => {
const {chartId, xyPlotParams, tblId} = rawAction.payload;
- dispatch({ type : LOAD_PLOT_DATA, payload : rawAction.payload });
- fetchPlotData(dispatch, tblId, xyPlotParams, chartId);
+ const tblSource = get(getTblById(tblId), 'tableMeta.source');
+
+ const chartModel = get(flux.getState(), [XYPLOT_DATA_KEY, chartId]);
+ let serverCallNeeded = !chartModel || !chartModel.tblSource || chartModel.tblSource !== tblSource;
+
+ if (serverCallNeeded || chartModel.xyPlotParams !== xyPlotParams) {
+ // when server call parameters do not change but chart parameters change,
+ // we do need to update parameters, but we can reuse the old chart data
+ serverCallNeeded = serverCallNeeded || serverParamsChanged(chartModel.xyPlotParams, xyPlotParams);
+
+ dispatch({ type : LOAD_PLOT_DATA, payload : {...rawAction.payload, tblSource, serverCallNeeded}});
+
+ if (serverCallNeeded) {
+ fetchPlotData(dispatch, tblId, xyPlotParams, chartId);
+ }
+ }
};
}
+function serverParamsChanged(oldParams, newParams) {
+ if (oldParams === newParams) { return false; }
+ if (!oldParams || !newParams) { return true; }
+
+ const newServerParams = getServerCallParameters(newParams);
+ const oldServerParams = getServerCallParameters(oldParams);
+ return newServerParams.some((p, i) => {
+ return p !== oldServerParams[i];
+ });
+}
+
/**
* The data is an object with
* chartId - string, chart id,
@@ -178,22 +196,36 @@ export function reducer(state={}, action={}) {
}
case (DELETE) :
{
- const chartId = action.payload.chartId;
- return has(state, chartId) ? Object.assign({}, omit(state, [chartId])) : state;
+ const {chartId, chartType} = action.payload;
+ if (chartType === 'scatter' && has(state, chartId)) {
+ return Object.assign({}, omit(state, [chartId]));
+ }
+ return state;
}
case (LOAD_PLOT_DATA) :
{
- const {chartId, xyPlotParams, tblId} = action.payload;
- return updateSet(state, chartId,
- { tblId, isPlotDataReady: false, xyPlotParams, decimatedUnzoomed: get(state, [chartId,'decimatedUnzoomed'])});
+ const {chartId, xyPlotParams, tblId, tblSource, serverCallNeeded} = action.payload;
+ if (serverCallNeeded) {
+ return updateSet(state, chartId,
+ {
+ tblId,
+ isPlotDataReady: false,
+ tblSource,
+ xyPlotParams,
+ decimatedUnzoomed: get(state, [chartId, 'decimatedUnzoomed'])
+ });
+ } else {
+ // only plot parameters changed
+ return updateSet(state, [chartId, 'xyPlotParams'], xyPlotParams);
+ }
}
case (UPDATE_PLOT_DATA) :
{
- const {isPlotDataReady, decimatedUnzoomed, xyPlotParams, xyPlotData, chartId, newParams} = action.payload;
+ const {chartId, isPlotDataReady, tblSource, decimatedUnzoomed, xyPlotParams, xyPlotData, newParams} = action.payload;
if (!state[chartId].xyPlotParams || state[chartId].xyPlotParams === xyPlotParams) {
const decimatedUnzoomedNext = isUndefined(decimatedUnzoomed) ? state[chartId].decimatedUnzoomed : decimatedUnzoomed;
return updateMerge(state, chartId,
- {isPlotDataReady, decimatedUnzoomed: decimatedUnzoomedNext, xyPlotData, xyPlotParams: newParams});
+ {isPlotDataReady, tblSource, decimatedUnzoomed: decimatedUnzoomedNext, xyPlotData, xyPlotParams: newParams});
}
return state;
}
@@ -231,13 +263,31 @@ export function reducer(state={}, action={}) {
}
}
+function getServerCallParameters(xyPlotParams) {
+ if (!xyPlotParams) { return []; }
+
+ if (xyPlotParams.zoom) {
+ var {xMin, xMax, yMin, yMax} = xyPlotParams.zoom;
+ }
+
+ let maxBins = 10000;
+ let xyRatio = xyPlotParams.xyRatio || 1.0;
+ if (xyPlotParams.nbins) {
+ const {x, y} = xyPlotParams.nbins;
+ maxBins = x*y;
+ xyRatio = x/y;
+ }
+ // order should match the order of the parameters in serializeDecimateInfo
+ return [xyPlotParams.x.columnOrExpr, xyPlotParams.y.columnOrExpr, maxBins, xyRatio, xMin, xMax, yMin, yMax];
+}
+
/**
* fetches xy plot data
* set isColStatsReady to true once done.
* @param dispatch
- * @param activeTableServerRequest table search request to obtain source table
+ * @param tblId table search request to obtain source table
* @param xyPlotParams object, which contains xy plot parameters
* @param {string} chartId - chart id
*/
@@ -245,29 +295,18 @@ function fetchPlotData(dispatch, tblId, xyPlotParams, chartId) {
if (!tblId || !isFullyLoaded(tblId)) {return; }
- const activeTableServerRequest = getTblById(tblId)['request'];
+ const activeTableModel = getTblById(tblId);
+ const activeTableServerRequest = activeTableModel['request'];
+ const tblSource = get(activeTableModel, 'tableMeta.source');
if (!xyPlotParams) { xyPlotParams = getDefaultXYPlotParams(tblId); }
- let limits = [];
- if (xyPlotParams.zoom) {
- const {xMin, xMax, yMin, yMax} = xyPlotParams.zoom;
- limits = [xMin, xMax, yMin, yMax];
- }
-
- let maxBins = 10000;
- let xyRatio = xyPlotParams.xyRatio || 1.0;
- if (xyPlotParams.nbins) {
- const {x, y} = xyPlotParams.nbins;
- maxBins = x*y;
- xyRatio = x/y;
- }
const req = Object.assign({}, omit(activeTableServerRequest, ['tbl_id', 'META_INFO']), {
'startIdx' : 0,
'pageSize' : 1000000,
//'inclCols' : `${xyPlotParams.x.columnOrExpr},${xyPlotParams.y.columnOrExpr}`, // ignored if 'decimate' is present
- 'decimate' : serializeDecimateInfo(xyPlotParams.x.columnOrExpr, xyPlotParams.y.columnOrExpr, maxBins, xyRatio, ...limits)
+ 'decimate' : serializeDecimateInfo(...getServerCallParameters(xyPlotParams))
});
req.tbl_id = `xy-${chartId}`;
@@ -297,6 +336,7 @@ function fetchPlotData(dispatch, tblId, xyPlotParams, chartId) {
dispatch(updatePlotData(
{
isPlotDataReady : true,
+ tblSource,
// when zoomed, we don't know if the unzoomed data are decimated or not
decimatedUnzoomed: Boolean(tableMeta['decimate_key']) || (xyPlotParams.zoom ? undefined : false),
xyPlotParams,
diff --git a/src/firefly/js/visualize/saga/ChartsSync.js b/src/firefly/js/visualize/saga/ChartsSync.js
index ccc1b07092..0e9ddd41ee 100644
--- a/src/firefly/js/visualize/saga/ChartsSync.js
+++ b/src/firefly/js/visualize/saga/ChartsSync.js
@@ -3,17 +3,19 @@
*/
import {take} from 'redux-saga/effects';
-import {has} from 'lodash';
+import {get} from 'lodash';
import {flux} from '../../Firefly.js';
+import {logError} from '../../util/WebUtil.js';
import * as TablesCntlr from '../../tables/TablesCntlr.js';
import * as TableStatsCntlr from '../TableStatsCntlr.js';
import * as TableUtil from '../../tables/TableUtil.js';
import * as XYPlotCntlr from '../XYPlotCntlr.js';
+import * as ChartsCntlr from '../ChartsCntlr.js';
import * as HistogramCntlr from '../HistogramCntlr.js';
-import {hasRelatedCharts, getDefaultXYPlotParams} from '../ChartUtil.js';
+import {SCATTER, HISTOGRAM, getChartSpace, hasRelatedCharts, getDefaultXYPlotParams} from '../ChartUtil.js';
/**
* this saga handles chart related side effects
@@ -22,49 +24,59 @@ export function* syncCharts() {
var tableStatsState, xyPlotState, histogramState;
while (true) {
- const action= yield take([TableStatsCntlr.SETUP_TBL_TRACKING, TablesCntlr.TABLE_NEW_LOADED, TablesCntlr.TABLE_SORT]);
+ const action= yield take([ChartsCntlr.CHART_MOUNTED, TablesCntlr.TABLE_NEW_LOADED, TablesCntlr.TABLE_SORT]);
+ if (!ChartsCntlr.getNumRelatedCharts()) { continue; }
const request= action.payload.request;
switch (action.type) {
- case TableStatsCntlr.SETUP_TBL_TRACKING:
- const {tblId} = action.payload;
+ case ChartsCntlr.CHART_MOUNTED:
+ const {tblId, chartId, chartType} = action.payload;
if (TableUtil.isFullyLoaded(tblId)) {
- TableStatsCntlr.dispatchLoadTblStats(TableUtil.getTblById(tblId)['request']);
- if (!hasRelatedCharts(tblId)) {
- // default chart is xy plot of coordinate columns or first two numeric columns
- const defaultParams = getDefaultXYPlotParams(tblId);
- if (defaultParams) {
- XYPlotCntlr.dispatchLoadPlotData(tblId, defaultParams, tblId);
+ if (ChartsCntlr.getNumRelatedCharts(tblId)===1) {
+ TableStatsCntlr.dispatchLoadTblStats(TableUtil.getTblById(tblId)['request']);
+ if (!hasRelatedCharts(tblId)) {
+ // default chart is xy plot of coordinate columns or first two numeric columns
+ const defaultParams = getDefaultXYPlotParams(tblId);
+ if (defaultParams) {
+ XYPlotCntlr.dispatchLoadPlotData(tblId, defaultParams, tblId);
+ }
}
}
+ updateChartDataIfNeeded(tblId, chartId, chartType);
}
+
break;
case TablesCntlr.TABLE_SORT:
case TablesCntlr.TABLE_NEW_LOADED:
const {tbl_id} = action.payload;
- let hasRelated = false;
- xyPlotState = flux.getState()[XYPlotCntlr.XYPLOT_DATA_KEY];
- Object.keys(xyPlotState).forEach((cid) => {
- if (xyPlotState[cid].tblId === tbl_id) {
- const xyPlotParams = xyPlotState[cid].xyPlotParams;
- XYPlotCntlr.dispatchLoadPlotData(cid, xyPlotParams, tbl_id);
- hasRelated = true;
- }
- });
-
- // table statistics and histogram data do not change on table sort
- if (action.type !== TablesCntlr.TABLE_SORT) {
- histogramState = flux.getState()[HistogramCntlr.HISTOGRAM_DATA_KEY];
- Object.keys(histogramState).forEach((cid) => {
- if (histogramState[cid].tblId === tbl_id) {
- const histogramParams = histogramState[cid].histogramParams;
- HistogramCntlr.dispatchLoadColData(cid, histogramParams, tbl_id);
+ // check if there are any mounted charts related to this table
+ if (ChartsCntlr.getNumRelatedCharts(tbl_id, true) > 0) {
+ let hasRelated = false;
+ xyPlotState = getChartSpace(SCATTER);
+ Object.keys(xyPlotState).forEach((cid) => {
+ if (xyPlotState[cid].tblId === tbl_id) {
hasRelated = true;
+ if (ChartsCntlr.isChartMounted(tbl_id, cid, SCATTER)) {
+ const xyPlotParams = xyPlotState[cid].xyPlotParams;
+ XYPlotCntlr.dispatchLoadPlotData(cid, xyPlotParams, tbl_id);
+ }
}
});
- tableStatsState = flux.getState()[TableStatsCntlr.TBLSTATS_DATA_KEY];
- if (has(tableStatsState, tbl_id)) {
+ // table statistics and histogram data do not change on table sort
+ if (action.type !== TablesCntlr.TABLE_SORT) {
+ histogramState = getChartSpace(HISTOGRAM);
+ Object.keys(histogramState).forEach((cid) => {
+ if (histogramState[cid].tblId === tbl_id) {
+ hasRelated = true;
+ if (ChartsCntlr.isChartMounted(tbl_id, cid, 'histogram')) {
+ const histogramParams = histogramState[cid].histogramParams;
+ HistogramCntlr.dispatchLoadColData(cid, histogramParams, tbl_id);
+ }
+ }
+ });
+
+ tableStatsState = flux.getState()[TableStatsCntlr.TBLSTATS_DATA_KEY];
TableStatsCntlr.dispatchLoadTblStats(request);
if (!hasRelated) {
// default chart is xy plot of coordinate columns or first two numeric columns
@@ -75,7 +87,6 @@ export function* syncCharts() {
}
}
}
-
break;
}
}
@@ -83,3 +94,26 @@ export function* syncCharts() {
+function updateChartDataIfNeeded(tblId, chartId, chartType) {
+ const tblSource = get(TableUtil.getTblById(tblId), 'tableMeta.source');
+ const chartSpace = getChartSpace(chartType);
+ const chartTblSource = get(chartSpace,[chartId,'tblSource']);
+ if (tblSource && (!chartTblSource || chartTblSource !== tblSource)) {
+ switch(chartType) {
+ case SCATTER:
+ const xyPlotParams = get(chartSpace, [chartId, 'xyPlotParams']);
+ if (xyPlotParams) {
+ XYPlotCntlr.dispatchLoadPlotData(chartId, xyPlotParams, tblId);
+ }
+ break;
+ case HISTOGRAM:
+ const histogramParams = get(chartSpace, [chartId, 'histogramParams']);
+ if (histogramParams) {
+ HistogramCntlr.dispatchLoadColData(chartId, histogramParams, tblId);
+ }
+ break;
+ default:
+ logError(`Unknown chart type ${chartType}`);
+ }
+ }
+}