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

plotly / angular-plotly.js / 1e437b93-527c-4d7d-bd6d-6f986c37632d

18 Aug 2023 11:19AM UTC coverage: 79.341% (+0.1%) from 79.217%
1e437b93-527c-4d7d-bd6d-6f986c37632d

push

circleci

andrefarzat
Updating circleci/browser-tools

64 of 86 branches covered (74.42%)

Branch coverage included in aggregate %.

201 of 248 relevant lines covered (81.05%)

15.64 hits per line

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

91.26
/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 { PlotlyThemeLoaderService, PlotlyTheme } from './plotly.theme-loader.service';
24
import { Plotly } from './plotly.interface';
25

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

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

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

44
    @Input() data?: Plotly.Data[];
45
    @Input() layout?: Partial<Plotly.Layout>;
46
    @Input() config?: Partial<Plotly.Config>;
47
    @Input() frames?: Partial<Plotly.Config>[];
48
    @Input() style?: { [key: string]: string };
49
    @Input() theme: PlotlyTheme = "none";
13✔
50

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

57
    @Input() updateOnLayoutChange = true;
13✔
58
    @Input() updateOnDataChange = true;
13✔
59
    @Input() updateOnlyWithRevision = false;
13✔
60

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

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

112

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

118
    constructor(
119
        public plotly: PlotlyService,
13✔
120
        public themeLoader: PlotlyThemeLoaderService,
13✔
121
        public iterableDiffers: IterableDiffers,
13✔
122
        public keyValueDiffers: KeyValueDiffers,
13✔
123
    ) { }
124

125
    ngOnInit(): void {
126
        this.createPlot().then(() => {
13✔
127
            const figure = this.createFigure();
13✔
128
            this.initialized.emit(figure);
13✔
129
        });
130

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

137
        // if (this.theme != 'none') this.loadTheme();
138
    }
139

140
    ngOnDestroy(): void {
141
        if (typeof this.resizeHandler === 'function') {
14✔
142
            this.getWindow().removeEventListener('resize', this.resizeHandler as any);
2✔
143
            this.resizeHandler = undefined;
2✔
144
        }
145

146
        if (this.plotlyInstance) {
14✔
147
            const figure = this.createFigure();
13✔
148
            this.purge.emit(figure);
13✔
149
            PlotlyService.remove(this.plotlyInstance);
13✔
150
        }
151
    }
152

153
    ngOnChanges(changes: SimpleChanges): void {
154
        let shouldUpdate = false;
4✔
155

156
        const revision: SimpleChange = changes['revision'];
4✔
157
        if (revision && !revision.isFirstChange()) {
4✔
158
            shouldUpdate = true;
2✔
159
        }
160

161
        const debug: SimpleChange = changes['debug'];
4✔
162
        if (debug && !debug.isFirstChange()) {
4✔
163
            shouldUpdate = true;
1✔
164
        }
165

166
        if (shouldUpdate) {
4✔
167
            this.updatePlot();
3✔
168
        }
169

170
        this.updateWindowResizeHandler();
4✔
171
    }
172

173
    ngDoCheck(): boolean | void {
174
        if (this.updateOnlyWithRevision) {
30!
175
            return false;
×
176
        }
177

178
        let shouldUpdate = false;
30✔
179

180
        if (this.updateOnLayoutChange) {
30✔
181
            if (this.layoutDiffer) {
30✔
182
                const layoutHasDiff = this.layoutDiffer.diff(this.layout!);
3✔
183
                if (layoutHasDiff) {
3✔
184
                    shouldUpdate = true;
2✔
185
                }
186
            } else if (this.layout) {
27✔
187
                this.layoutDiffer = this.keyValueDiffers.find(this.layout).create();
1✔
188
            } else {
189
                this.layoutDiffer = undefined;
26✔
190
            }
191
        }
192

193
        if (this.updateOnDataChange) {
30✔
194
            if (this.dataDiffer) {
30✔
195
                const dataHasDiff = this.dataDiffer.diff(this.data);
3✔
196
                if (dataHasDiff) {
3✔
197
                    shouldUpdate = true;
2✔
198
                }
199
            } else if (Array.isArray(this.data)) {
27✔
200
                this.dataDiffer = this.iterableDiffers.find(this.data).create(this.dataDifferTrackBy);
1✔
201
            } else {
202
                this.dataDiffer = undefined;
26✔
203
            }
204
        }
205

206
        if (shouldUpdate && this.plotlyInstance) {
30✔
207
            this.updatePlot();
4✔
208
        }
209
    }
210

211
    getData(): Plotly.Data[] {
212
        return this.data ?? [];
18✔
213
    }
214

215
    getWindow(): any {
216
        return window;
20✔
217
    }
218

219
    getClassName(): string {
220
        let classes = [this.defaultClassName];
47✔
221

222
        if (Array.isArray(this.className)) {
47✔
223
            classes = classes.concat(this.className);
3✔
224
        } else if (this.className) {
44✔
225
            classes.push(this.className);
3✔
226
        }
227

228
        return classes.join(' ');
47✔
229
    }
230

231
    createPlot(): Promise<void> {
232
        return this.plotly.newPlot(this.plotEl.nativeElement, this.getData(), this.layout, this.config, this.frames).then(plotlyInstance => {
17✔
233
            this.plotlyInstance = plotlyInstance;
17✔
234
            this.getWindow().gd = this.debug ? plotlyInstance : undefined;
17✔
235

236
            this.eventNames.forEach(name => {
17✔
237
                const eventName = `plotly_${name.toLowerCase()}`;
544✔
238
                const event = (this as any)[name] as EventEmitter<void>;
544✔
239

240
                plotlyInstance.on(eventName, (data: any) => event.emit(data));
544✔
241
            });
242

243
            plotlyInstance.on('plotly_click', (data: any) => {
17✔
244
                this.plotlyClick.emit(data);
×
245
            });
246

247
            this.updateWindowResizeHandler();
17✔
248
        }, err => {
249
            console.error('Error while plotting:', err);
×
250
            this.error.emit(err);
×
251
        });
252
    }
253

254
    createFigure(): Plotly.Figure {
255
        const p: any = this.plotlyInstance;
27✔
256
        const figure: Plotly.Figure = {
27✔
257
            data: p.data,
258
            layout: p.layout,
259
            frames: p._transitionData ? p._transitionData._frames : null
27!
260
        };
261

262
        return figure;
27✔
263
    }
264

265
    updatePlot(): Promise<any> {
266
        if (!this.plotlyInstance) {
2✔
267
            const error = new Error(`Plotly component wasn't initialized`);
1✔
268
            this.error.emit(error);
1✔
269
            throw error;
1✔
270
        }
271

272
        const layout = { ...this.layout };
1✔
273

274
        return this.plotly.update(this.plotlyInstance, this.getData(), layout, this.config, this.frames).then(() => {
1✔
275
            const figure = this.createFigure();
1✔
276
            this.update.emit(figure);
1✔
277
        }, err => {
278
            console.error('Error while updating plot:', err);
×
279
            this.error.emit(err);
×
280
        });
281
    }
282

283
    updateWindowResizeHandler(): void {
284
        if (this.useResizeHandler) {
23✔
285
            if (this.resizeHandler === undefined) {
5✔
286
                this.resizeHandler = () => this.plotly.resize(this.plotlyInstance!);
3✔
287
                this.getWindow().addEventListener('resize', this.resizeHandler as any);
3✔
288
            }
289
        } else {
290
            if (typeof this.resizeHandler === 'function') {
18✔
291
                this.getWindow().removeEventListener('resize', this.resizeHandler as any);
1✔
292
                this.resizeHandler = undefined;
1✔
293
            }
294
        }
295
    }
296

297
    dataDifferTrackBy(_: number, item: any): unknown {
298
        const obj = Object.assign({}, item, { uid: '' });
3✔
299
        return JSON.stringify(obj);
3✔
300
    }
301

302
    loadTheme() {
303
        if (this.layout !== undefined) {
×
304
            const msg = 'You fulfill both `theme` and `layout` properties. This will overwrite the `layout` data with the `theme` data.';
×
305
            console.warn(msg);
×
306
        }
307

308
        this.themeLoader.load(this.theme).then(theme => this.layout = theme);
×
309
    }
310
}
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