• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plotly / angular-plotly.js / 1022905e-4cf0-49d3-86e5-1343e574671f

pending completion
1022905e-4cf0-49d3-86e5-1343e574671f

push

circleci

andrefarzat
Merge branch 'master' into angular-15

61 of 71 branches covered (85.92%)

Branch coverage included in aggregate %.

196 of 224 relevant lines covered (87.5%)

16.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.68
/projects/plotly/src/lib/plotly.component.ts
1
/* eslint-disable @angular-eslint/no-conflicting-lifecycle */
2

3
import {
4
    Component,
5
    ElementRef,
6
    EventEmitter,
7
    Input,
8
    OnDestroy,
9
    OnChanges,
10
    OnInit,
11
    Output,
12
    SimpleChange,
13
    SimpleChanges,
14
    ViewChild,
15
    DoCheck,
16
    IterableDiffer,
17
    IterableDiffers,
18
    KeyValueDiffer,
19
    KeyValueDiffers,
20
} from '@angular/core';
21

22
import { PlotlyService } from './plotly.service';
23
import { Plotly } from './plotly.interface';
24

25
// @dynamic
26
@Component({
27
    selector: 'plotly-plot',
28
    template: `<div #plot [attr.id]="divId" [ngClass]="getClassName()" [ngStyle]="style">
29
      <ng-content></ng-content>
30
    </div>`,
31
    providers: [PlotlyService],
32
})
33
export class PlotlyComponent implements OnInit, OnChanges, OnDestroy, DoCheck {
1✔
34
    protected defaultClassName = 'js-plotly-plot';
12✔
35

36
    public plotlyInstance?: Plotly.PlotlyHTMLElement;
37
    public resizeHandler?: (instance: Plotly.PlotlyHTMLElement) => void;
38
    public layoutDiffer?: KeyValueDiffer<string, any>;
39
    public dataDiffer?: IterableDiffer<Plotly.Data>;
40

41
    @ViewChild('plot', { static: true }) plotEl!: ElementRef;
42

43
    @Input() data?: Plotly.Data[];
44
    @Input() layout?: Partial<Plotly.Layout>;
45
    @Input() config?: Partial<Plotly.Config>;
46
    @Input() frames?: Partial<Plotly.Config>[];
47
    @Input() style?: { [key: string]: string };
48

49
    @Input() divId?: string;
50
    @Input() revision = 0;
12✔
51
    @Input() className?: string | string[];
52
    @Input() debug = false;
12✔
53
    @Input() useResizeHandler = false;
12✔
54

55
    @Input() updateOnLayoutChange = true;
12✔
56
    @Input() updateOnDataChange = true;
12✔
57
    @Input() updateOnlyWithRevision = false;
12✔
58

59
    @Output() initialized = new EventEmitter<Plotly.Figure>();
12✔
60
    @Output() update = new EventEmitter<Plotly.Figure>();
12✔
61
    @Output() purge = new EventEmitter<Plotly.Figure>();
12✔
62
    // eslint-disable-next-line @angular-eslint/no-output-native
63
    @Output() error = new EventEmitter<Error>();
12✔
64

65
    @Output() afterExport = new EventEmitter();
12✔
66
    @Output() afterPlot = new EventEmitter();
12✔
67
    @Output() animated = new EventEmitter();
12✔
68
    @Output() animatingFrame = new EventEmitter();
12✔
69
    @Output() animationInterrupted = new EventEmitter();
12✔
70
    @Output() autoSize = new EventEmitter();
12✔
71
    @Output() beforeExport = new EventEmitter();
12✔
72
    @Output() beforeHover = new EventEmitter();
12✔
73
    @Output() buttonClicked = new EventEmitter();
12✔
74
    /**
75
     * @deprecated DEPRECATED: Reconsider using `(plotlyClick)` instead of `(click)` to avoid event conflict. Please check https://github.com/plotly/angular-plotly.js#FAQ
76
     */
77
    // eslint-disable-next-line @angular-eslint/no-output-native
78
    @Output() click = new EventEmitter();
12✔
79
    @Output() plotlyClick = new EventEmitter();
12✔
80
    @Output() clickAnnotation = new EventEmitter();
12✔
81
    @Output() deselect = new EventEmitter();
12✔
82
    @Output() doubleClick = new EventEmitter();
12✔
83
    @Output() framework = new EventEmitter();
12✔
84
    @Output() hover = new EventEmitter();
12✔
85
    @Output() legendClick = new EventEmitter();
12✔
86
    @Output() legendDoubleClick = new EventEmitter();
12✔
87
    /**
88
     * @deprecated DEPRECATED: Event react is not list as an plotly.js event
89
     */
90
    @Output() react = new EventEmitter();
12✔
91
    @Output() relayout = new EventEmitter();
12✔
92
    @Output() relayouting = new EventEmitter();
12✔
93
    @Output() restyle = new EventEmitter();
12✔
94
    @Output() redraw = new EventEmitter();
12✔
95
    @Output() selected = new EventEmitter();
12✔
96
    @Output() selecting = new EventEmitter();
12✔
97
    @Output() sliderChange = new EventEmitter();
12✔
98
    @Output() sliderEnd = new EventEmitter();
12✔
99
    @Output() sliderStart = new EventEmitter();
12✔
100
    @Output() sunburstclick = new EventEmitter();
12✔
101
    @Output() transitioning = new EventEmitter();
12✔
102
    @Output() transitionInterrupted = new EventEmitter();
12✔
103
    @Output() unhover = new EventEmitter();
12✔
104
    /**
105
     * @deprecated DEPRECATED: Event treemapclick is not list as an plotly.js event
106
     */
107
    @Output() treemapclick = new EventEmitter();
12✔
108
    @Output() webglcontextlost = new EventEmitter();
12✔
109

110

111
    public eventNames = ['afterExport', 'afterPlot', 'animated', 'animatingFrame', 'animationInterrupted', 'autoSize',
12✔
112
        'beforeExport', 'beforeHover', 'buttonClicked', 'clickAnnotation', 'deselect', 'doubleClick', 'framework', 'hover',
113
        'legendClick', 'legendDoubleClick', 'react', 'relayout', 'relayouting',  'restyle', 'redraw', 'selected', 'selecting', 'sliderChange',
114
        'sliderEnd', 'sliderStart', 'sunburstclick', 'transitioning', 'transitionInterrupted', 'unhover', 'treemapclick', 'webglcontextlost'];
115

116
    constructor(
117
        public plotly: PlotlyService,
12✔
118
        public iterableDiffers: IterableDiffers,
12✔
119
        public keyValueDiffers: KeyValueDiffers,
12✔
120
    ) { }
121

122
    ngOnInit(): void {
123
        this.createPlot().then(() => {
12✔
124
            const figure = this.createFigure();
12✔
125
            this.initialized.emit(figure);
12✔
126
        });
127

128
        if (this.click.observers.length > 0) {
12!
129
            const msg = 'DEPRECATED: Reconsider using `(plotlyClick)` instead of `(click)` to avoid event conflict. '
×
130
                + 'Please check https://github.com/plotly/angular-plotly.js#FAQ';
131
            console.error(msg);
×
132
        }
133
    }
134

135
    ngOnDestroy(): void {
136
        if (typeof this.resizeHandler === 'function') {
12✔
137
            this.getWindow().removeEventListener('resize', this.resizeHandler as any);
2✔
138
            this.resizeHandler = undefined;
2✔
139
        }
140

141
        const figure = this.createFigure();
12✔
142
        this.purge.emit(figure);
12✔
143
        PlotlyService.remove(this.plotlyInstance!);
12✔
144
    }
145

146
    ngOnChanges(changes: SimpleChanges): void {
147
        let shouldUpdate = false;
4✔
148

149
        const revision: SimpleChange = changes['revision'];
4✔
150
        if (revision && !revision.isFirstChange()) {
4✔
151
            shouldUpdate = true;
2✔
152
        }
153

154
        const debug: SimpleChange = changes['debug'];
4✔
155
        if (debug && !debug.isFirstChange()) {
4✔
156
            shouldUpdate = true;
1✔
157
        }
158

159
        if (shouldUpdate) {
4✔
160
            this.updatePlot();
3✔
161
        }
162

163
        this.updateWindowResizeHandler();
4✔
164
    }
165

166
    ngDoCheck(): boolean | void {
167
        if (this.updateOnlyWithRevision) {
29!
168
            return false;
×
169
        }
170

171
        let shouldUpdate = false;
29✔
172

173
        if (this.updateOnLayoutChange) {
29✔
174
            if (this.layoutDiffer) {
29✔
175
                const layoutHasDiff = this.layoutDiffer.diff(this.layout!);
3✔
176
                if (layoutHasDiff) {
3✔
177
                    shouldUpdate = true;
2✔
178
                }
179
            } else if (this.layout) {
26✔
180
                this.layoutDiffer = this.keyValueDiffers.find(this.layout).create();
1✔
181
            } else {
182
                this.layoutDiffer = undefined;
25✔
183
            }
184
        }
185

186
        if (this.updateOnDataChange) {
29✔
187
            if (this.dataDiffer) {
29✔
188
                const dataHasDiff = this.dataDiffer.diff(this.data);
3✔
189
                if (dataHasDiff) {
3✔
190
                    shouldUpdate = true;
2✔
191
                }
192
            } else if (Array.isArray(this.data)) {
26✔
193
                this.dataDiffer = this.iterableDiffers.find(this.data).create(this.dataDifferTrackBy);
1✔
194
            } else {
195
                this.dataDiffer = undefined;
25✔
196
            }
197
        }
198

199
        if (shouldUpdate && this.plotlyInstance) {
29✔
200
            this.updatePlot();
4✔
201
        }
202
    }
203

204
    getData(): Plotly.Data[] {
205
        return this.data ?? [];
17✔
206
    }
207

208
    getWindow(): any {
209
        return window;
19✔
210
    }
211

212
    getClassName(): string {
213
        let classes = [this.defaultClassName];
45✔
214

215
        if (Array.isArray(this.className)) {
45✔
216
            classes = classes.concat(this.className);
3✔
217
        } else if (this.className) {
42✔
218
            classes.push(this.className);
3✔
219
        }
220

221
        return classes.join(' ');
45✔
222
    }
223

224
    createPlot(): Promise<void> {
225
        return this.plotly.newPlot(this.plotEl.nativeElement, this.getData(), this.layout, this.config, this.frames).then(plotlyInstance => {
16✔
226
            this.plotlyInstance = plotlyInstance;
16✔
227
            this.getWindow().gd = this.debug ? plotlyInstance : undefined;
16✔
228

229
            this.eventNames.forEach(name => {
16✔
230
                const eventName = `plotly_${name.toLowerCase()}`;
512✔
231
                const event = (this as any)[name] as EventEmitter<void>;
512✔
232

233
                plotlyInstance.on(eventName, (data: any) => event.emit(data));
512✔
234
            });
235

236
            plotlyInstance.on('plotly_click', (data: any) => {
16✔
237
                this.plotlyClick.emit(data);
×
238
            });
239

240
            this.updateWindowResizeHandler();
16✔
241
        }, err => {
242
            console.error('Error while plotting:', err);
×
243
            this.error.emit(err);
×
244
        });
245
    }
246

247
    createFigure(): Plotly.Figure {
248
        const p: any = this.plotlyInstance;
25✔
249
        const figure: Plotly.Figure = {
25✔
250
            data: p.data,
251
            layout: p.layout,
252
            frames: p._transitionData ? p._transitionData._frames : null
25!
253
        };
254

255
        return figure;
25✔
256
    }
257

258
    updatePlot(): Promise<any> {
259
        if (!this.plotlyInstance) {
2✔
260
            const error = new Error(`Plotly component wasn't initialized`);
1✔
261
            this.error.emit(error);
1✔
262
            throw error;
1✔
263
        }
264

265
        const layout = { ...this.layout };
1✔
266

267
        return this.plotly.update(this.plotlyInstance, this.getData(), layout, this.config, this.frames).then(() => {
1✔
268
            const figure = this.createFigure();
1✔
269
            this.update.emit(figure);
1✔
270
        }, err => {
271
            console.error('Error while updating plot:', err);
×
272
            this.error.emit(err);
×
273
        });
274
    }
275

276
    updateWindowResizeHandler(): void {
277
        if (this.useResizeHandler) {
22✔
278
            if (this.resizeHandler === undefined) {
5✔
279
                this.resizeHandler = () => this.plotly.resize(this.plotlyInstance!);
3✔
280
                this.getWindow().addEventListener('resize', this.resizeHandler as any);
3✔
281
            }
282
        } else {
283
            if (typeof this.resizeHandler === 'function') {
17✔
284
                this.getWindow().removeEventListener('resize', this.resizeHandler as any);
1✔
285
                this.resizeHandler = undefined;
1✔
286
            }
287
        }
288
    }
289

290
    dataDifferTrackBy(_: number, item: any): unknown {
291
        const obj = Object.assign({}, item, { uid: '' });
3✔
292
        return JSON.stringify(obj);
3✔
293
    }
294
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc