Skip to content

Commit efb4dda

Browse files
authored
Merge pull request #1 from MercifulCode/fixing-multiple-contexts-for-new-plots
Handling async nature of plotly
2 parents 0129599 + 57cf739 commit efb4dda

File tree

4 files changed

+154
-99
lines changed

4 files changed

+154
-99
lines changed

dist/index.d.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,15 @@
11
/// <reference types="react" />
2+
import * as plotly from 'plotly.js';
23
import * as React from 'react';
34
export interface IPlotlyChartProps {
4-
config?: any;
5-
data: any[];
6-
layout?: any;
7-
onClick?: (data: {
8-
points: any;
9-
event: any;
10-
}) => any;
11-
onBeforeHover?: (data: {
12-
points: any;
13-
event: any;
14-
}) => any;
15-
onHover?: (data: {
16-
points: any;
17-
event: any;
18-
}) => any;
19-
onUnHover?: (data: {
20-
points: any;
21-
event: any;
22-
}) => any;
23-
onSelected?: (data: {
24-
points: any;
25-
event: any;
26-
}) => any;
5+
config?: plotly.Config;
6+
data: Partial<plotly.ScatterData>[];
7+
layout?: plotly.Layout;
8+
onClick?: (event: plotly.PlotMouseEvent) => void;
9+
onBeforeHover?: (event: plotly.PlotMouseEvent) => void;
10+
onHover?: (event: plotly.PlotMouseEvent) => void;
11+
onUnHover?: (event: plotly.PlotMouseEvent) => void;
12+
onSelected?: (event: plotly.PlotSelectionEvent) => void;
2713
}
2814
/***
2915
* Usage:
@@ -32,10 +18,10 @@ export interface IPlotlyChartProps {
3218
* onClick={({points, event}) => console.log(points, event)}>
3319
*/
3420
declare class PlotlyChart extends React.Component<IPlotlyChartProps, any> {
35-
container: any;
21+
container: plotly.PlotlyHTMLElement | null;
3622
attachListeners(): void;
3723
resize: () => void;
38-
draw: (props: IPlotlyChartProps) => void;
24+
draw: (props: IPlotlyChartProps) => Promise<void>;
3925
componentWillReceiveProps(nextProps: IPlotlyChartProps): void;
4026
componentDidMount(): void;
4127
componentWillUnmount(): void;

dist/index.js

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,41 @@ var __assign = (this && this.__assign) || Object.assign || function(t) {
1717
}
1818
return t;
1919
};
20+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
21+
return new (P || (P = Promise))(function (resolve, reject) {
22+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
23+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
24+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
25+
step((generator = generator.apply(thisArg, _arguments || [])).next());
26+
});
27+
};
28+
var __generator = (this && this.__generator) || function (thisArg, body) {
29+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
30+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
31+
function verb(n) { return function (v) { return step([n, v]); }; }
32+
function step(op) {
33+
if (f) throw new TypeError("Generator is already executing.");
34+
while (_) try {
35+
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
36+
if (y = 0, t) op = [0, t.value];
37+
switch (op[0]) {
38+
case 0: case 1: t = op; break;
39+
case 4: _.label++; return { value: op[1], done: false };
40+
case 5: _.label++; y = op[1]; op = [0]; continue;
41+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
42+
default:
43+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
44+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
45+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
46+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
47+
if (t[2]) _.ops.pop();
48+
_.trys.pop(); continue;
49+
}
50+
op = body.call(thisArg, _);
51+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
52+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
53+
}
54+
};
2055
var __rest = (this && this.__rest) || function (s, e) {
2156
var t = {};
2257
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -27,9 +62,8 @@ var __rest = (this && this.__rest) || function (s, e) {
2762
return t;
2863
};
2964
Object.defineProperty(exports, "__esModule", { value: true });
65+
var plotly = require("plotly.js");
3066
var React = require("react");
31-
var plotlyInstance = require("plotly.js/dist/plotly.js");
32-
var lodash_1 = require("lodash");
3367
/***
3468
* Usage:
3569
* <PlotlyChart data={toJS(this.model_data)}
@@ -42,23 +76,34 @@ var PlotlyChart = /** @class */ (function (_super) {
4276
var _this = _super !== null && _super.apply(this, arguments) || this;
4377
_this.container = null;
4478
_this.resize = function () {
45-
plotlyInstance.Plots.resize(_this.container);
46-
};
47-
_this.draw = function (props) {
48-
var data = props.data, layout = props.layout, config = props.config;
49-
// We clone the layout as plotly mutates it.
50-
plotlyInstance.newPlot(_this.container, data, lodash_1.cloneDeep(layout), config);
51-
_this.attachListeners();
79+
if (_this.container) {
80+
plotly.Plots.resize(_this.container);
81+
}
5282
};
83+
_this.draw = function (props) { return __awaiter(_this, void 0, void 0, function () {
84+
var data, layout, config, _a;
85+
return __generator(this, function (_b) {
86+
switch (_b.label) {
87+
case 0:
88+
data = props.data, layout = props.layout, config = props.config;
89+
if (!this.container) return [3 /*break*/, 2];
90+
// plotly.react will not destroy the old plot: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyreact
91+
_a = this;
92+
return [4 /*yield*/, plotly.react(this.container, data, Object.assign({}, layout), config)];
93+
case 1:
94+
// plotly.react will not destroy the old plot: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyreact
95+
_a.container = _b.sent();
96+
_b.label = 2;
97+
case 2: return [2 /*return*/];
98+
}
99+
});
100+
}); };
53101
return _this;
54102
}
55103
PlotlyChart.prototype.attachListeners = function () {
56104
if (this.props.onClick) {
57105
this.container.on('plotly_click', this.props.onClick);
58106
}
59-
if (this.props.onBeforeHover) {
60-
this.container.on('plotly_beforehover', this.props.onBeforeHover);
61-
}
62107
if (this.props.onHover) {
63108
this.container.on('plotly_hover', this.props.onHover);
64109
}
@@ -77,15 +122,30 @@ var PlotlyChart = /** @class */ (function (_super) {
77122
this.draw(this.props);
78123
};
79124
PlotlyChart.prototype.componentWillUnmount = function () {
80-
plotlyInstance.purge(this.container);
125+
if (this.container) {
126+
plotly.purge(this.container);
127+
}
81128
window.removeEventListener('resize', this.resize);
82129
};
83130
PlotlyChart.prototype.render = function () {
84131
var _this = this;
85-
var _a = this.props, data = _a.data, layout = _a.layout, config = _a.config, onClick = _a.onClick, onBeforeHover = _a.onBeforeHover, onHover = _a.onHover, onSelected = _a.onSelected, onUnHover = _a.onUnHover, other = __rest(_a, ["data", "layout", "config", "onClick", "onBeforeHover", "onHover", "onSelected", "onUnHover"]);
86-
return React.createElement("div", __assign({}, other, { ref: function (node) { return _this.container = node; } }));
132+
var _a = this.props, data = _a.data, layout = _a.layout, config = _a.config, onClick = _a.onClick, onHover = _a.onHover, onSelected = _a.onSelected, onUnHover = _a.onUnHover, other = __rest(_a, ["data", "layout", "config", "onClick", "onHover", "onSelected", "onUnHover"]);
133+
return (React.createElement("div", __assign({}, other, { ref: function (node) { return __awaiter(_this, void 0, void 0, function () {
134+
var _a;
135+
return __generator(this, function (_b) {
136+
switch (_b.label) {
137+
case 0:
138+
if (!(node && !this.container)) return [3 /*break*/, 2];
139+
_a = this;
140+
return [4 /*yield*/, plotly.newPlot(node, data, Object.assign({}, layout), config)];
141+
case 1:
142+
_a.container = _b.sent();
143+
_b.label = 2;
144+
case 2: return [2 /*return*/];
145+
}
146+
});
147+
}); } })));
87148
};
88149
return PlotlyChart;
89150
}(React.Component));
90-
;
91151
exports.default = PlotlyChart;

src/index.tsx

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1+
import * as plotly from 'plotly.js';
12
import * as React from 'react';
2-
import * as plotlyInstance from 'plotly.js/dist/plotly.js';
3-
import {cloneDeep} from 'lodash';
43

54
export interface IPlotlyChartProps {
6-
config?: any;
7-
data: any[];
8-
layout?: any;
9-
onClick?: (data: { points: any, event: any }) => any;
10-
onBeforeHover?: (data: { points: any, event: any }) => any;
11-
onHover?: (data: { points: any, event: any }) => any;
12-
onUnHover?: (data: { points: any, event: any }) => any;
13-
onSelected?: (data: { points: any, event: any }) => any;
5+
config?: plotly.Config;
6+
data: Partial<plotly.ScatterData>[];
7+
layout?: plotly.Layout;
8+
onClick?: (event: plotly.PlotMouseEvent) => void;
9+
onBeforeHover?: (event: plotly.PlotMouseEvent) => void;
10+
onHover?: (event: plotly.PlotMouseEvent) => void;
11+
onUnHover?: (event: plotly.PlotMouseEvent) => void;
12+
onSelected?: (event: plotly.PlotSelectionEvent) => void;
1413
}
1514

1615
/***
@@ -20,57 +19,67 @@ export interface IPlotlyChartProps {
2019
* onClick={({points, event}) => console.log(points, event)}>
2120
*/
2221
class PlotlyChart extends React.Component<IPlotlyChartProps, any> {
22+
public container: plotly.PlotlyHTMLElement | null = null;
2323

24-
container: any = null;
25-
26-
attachListeners() {
27-
if (this.props.onClick) {
28-
this.container.on('plotly_click', this.props.onClick);
29-
}
30-
if (this.props.onBeforeHover) {
31-
this.container.on('plotly_beforehover', this.props.onBeforeHover);
32-
}
33-
if (this.props.onHover) {
34-
this.container.on('plotly_hover', this.props.onHover);
35-
}
36-
if (this.props.onUnHover) {
37-
this.container.on('plotly_unhover', this.props.onUnHover);
38-
}
39-
if (this.props.onSelected) {
40-
this.container.on('plotly_selected', this.props.onSelected);
41-
}
42-
window.addEventListener('resize', this.resize);
24+
public attachListeners() {
25+
if (this.props.onClick) {
26+
this.container!.on('plotly_click', this.props.onClick);
4327
}
44-
45-
resize = () => {
46-
plotlyInstance.Plots.resize(this.container);
28+
if (this.props.onHover) {
29+
this.container!.on('plotly_hover', this.props.onHover);
4730
}
48-
49-
draw = (props: IPlotlyChartProps) => {
50-
const {data, layout, config} = props;
51-
// We clone the layout as plotly mutates it.
52-
plotlyInstance.newPlot(this.container, data, cloneDeep(layout), config);
53-
this.attachListeners();
31+
if (this.props.onUnHover) {
32+
this.container!.on('plotly_unhover', this.props.onUnHover);
5433
}
55-
56-
57-
componentWillReceiveProps(nextProps: IPlotlyChartProps) {
58-
this.draw(nextProps);
34+
if (this.props.onSelected) {
35+
this.container!.on('plotly_selected', this.props.onSelected);
5936
}
37+
window.addEventListener('resize', this.resize);
38+
}
6039

61-
componentDidMount() {
62-
this.draw(this.props);
40+
public resize = () => {
41+
if (this.container) {
42+
plotly.Plots.resize(this.container);
6343
}
44+
};
6445

65-
componentWillUnmount() {
66-
plotlyInstance.purge(this.container);
67-
window.removeEventListener('resize', this.resize);
46+
public draw = async (props: IPlotlyChartProps) => {
47+
const { data, layout, config } = props;
48+
if (this.container) {
49+
// plotly.react will not destroy the old plot: https://plot.ly/javascript/plotlyjs-function-reference/#plotlyreact
50+
this.container = await plotly.react(this.container, data, Object.assign({}, layout), config);
51+
this.attachListeners();
6852
}
53+
};
6954

70-
render() {
71-
const {data, layout, config, onClick, onBeforeHover, onHover, onSelected, onUnHover, ...other} = this.props;
72-
return <div {...other} ref={(node) => this.container = node}/>;
55+
public componentWillReceiveProps(nextProps: IPlotlyChartProps) {
56+
this.draw(nextProps);
57+
}
58+
59+
public componentDidMount() {
60+
this.draw(this.props);
61+
}
62+
63+
public componentWillUnmount() {
64+
if (this.container) {
65+
plotly.purge(this.container);
7366
}
74-
};
67+
window.removeEventListener('resize', this.resize);
68+
}
69+
70+
public render() {
71+
const { data, layout, config, onClick, onHover, onSelected, onUnHover, ...other } = this.props;
72+
return (
73+
<div
74+
{...other}
75+
ref={async node => {
76+
if (node && !this.container) {
77+
this.container = await plotly.newPlot(node, data as any, Object.assign({}, layout), config);
78+
}
79+
}}
80+
/>
81+
);
82+
}
83+
}
7584

76-
export default PlotlyChart;
85+
export default PlotlyChart;

tsconfig.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
"declaration": true,
44
"outDir": "dist",
55
"strictNullChecks": true,
6+
"lib": [
7+
"es2015",
8+
"es2016",
9+
"es2017",
10+
"dom"
11+
],
612
"module": "commonjs",
713
"target": "es5",
814
"jsx": "react"
915
},
1016
"include": [
1117
"./src/"
12-
],
13-
"lib": [
14-
"es2015",
15-
"es2016",
16-
"es2017",
17-
"dom"
1818
]
1919
}

0 commit comments

Comments
 (0)