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

plotly / angular-plotly.js / 0de81391-b879-4fe6-a7fa-cdecd0fc6db5

pending completion
0de81391-b879-4fe6-a7fa-cdecd0fc6db5

push

circleci

andrefarzat
Creating the PlotlyThemeLoaderService

62 of 73 branches covered (84.93%)

Branch coverage included in aggregate %.

18 of 18 new or added lines in 3 files covered. (100.0%)

208 of 239 relevant lines covered (87.03%)

18.47 hits per line

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

92.35
/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';
14✔
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";
14✔
50

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

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

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

67
    @Output() afterExport = new EventEmitter();
14✔
68
    @Output() afterPlot = new EventEmitter();
14✔
69
    @Output() animated = new EventEmitter();
14✔
70
    @Output() animatingFrame = new EventEmitter();
14✔
71
    @Output() animationInterrupted = new EventEmitter();
14✔
72
    @Output() autoSize = new EventEmitter();
14✔
73
    @Output() beforeExport = new EventEmitter();
14✔
74
    @Output() beforeHover = new EventEmitter();
14✔
75
    @Output() buttonClicked = new EventEmitter();
14✔
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();
14✔
81
    @Output() plotlyClick = new EventEmitter();
14✔
82
    @Output() clickAnnotation = new EventEmitter();
14✔
83
    @Output() deselect = new EventEmitter();
14✔
84
    @Output() doubleClick = new EventEmitter();
14✔
85
    @Output() framework = new EventEmitter();
14✔
86
    @Output() hover = new EventEmitter();
14✔
87
    @Output() legendClick = new EventEmitter();
14✔
88
    @Output() legendDoubleClick = new EventEmitter();
14✔
89
    /**
90
     * @deprecated DEPRECATED: Event react is not list as an plotly.js event
91
     */
92
    @Output() react = new EventEmitter();
14✔
93
    @Output() relayout = new EventEmitter();
14✔
94
    @Output() relayouting = new EventEmitter();
14✔
95
    @Output() restyle = new EventEmitter();
14✔
96
    @Output() redraw = new EventEmitter();
14✔
97
    @Output() selected = new EventEmitter();
14✔
98
    @Output() selecting = new EventEmitter();
14✔
99
    @Output() sliderChange = new EventEmitter();
14✔
100
    @Output() sliderEnd = new EventEmitter();
14✔
101
    @Output() sliderStart = new EventEmitter();
14✔
102
    @Output() sunburstclick = new EventEmitter();
14✔
103
    @Output() transitioning = new EventEmitter();
14✔
104
    @Output() transitionInterrupted = new EventEmitter();
14✔
105
    @Output() unhover = new EventEmitter();
14✔
106
    /**
107
     * @deprecated DEPRECATED: Event treemapclick is not list as an plotly.js event
108
     */
109
    @Output() treemapclick = new EventEmitter();
14✔
110
    @Output() webglcontextlost = new EventEmitter();
14✔
111

112

113
    public eventNames = ['afterExport', 'afterPlot', 'animated', 'animatingFrame', 'animationInterrupted', 'autoSize',
14✔
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,
14✔
120
        public themeLoader: PlotlyThemeLoaderService,
14✔
121
        public iterableDiffers: IterableDiffers,
14✔
122
        public keyValueDiffers: KeyValueDiffers,
14✔
123
    ) { }
124

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

131
        if (this.click.observers.length > 0) {
16!
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();
16✔
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
        const figure = this.createFigure();
14✔
147
        this.purge.emit(figure);
14✔
148
        PlotlyService.remove(this.plotlyInstance!);
14✔
149
    }
150

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

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

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

164
        if (shouldUpdate) {
4✔
165
            this.updatePlot();
3✔
166
        }
167

168
        this.updateWindowResizeHandler();
4✔
169
    }
170

171
    ngDoCheck(): boolean | void {
172
        if (this.updateOnlyWithRevision) {
31!
173
            return false;
×
174
        }
175

176
        let shouldUpdate = false;
31✔
177

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

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

204
        if (shouldUpdate && this.plotlyInstance) {
31✔
205
            this.updatePlot();
4✔
206
        }
207
    }
208

209
    getData(): Plotly.Data[] {
210
        return this.data ?? [];
21✔
211
    }
212

213
    getWindow(): any {
214
        return window;
23✔
215
    }
216

217
    getClassName(): string {
218
        let classes = [this.defaultClassName];
49✔
219

220
        if (Array.isArray(this.className)) {
49✔
221
            classes = classes.concat(this.className);
3✔
222
        } else if (this.className) {
46✔
223
            classes.push(this.className);
3✔
224
        }
225

226
        return classes.join(' ');
49✔
227
    }
228

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

234
            this.eventNames.forEach(name => {
20✔
235
                const eventName = `plotly_${name.toLowerCase()}`;
640✔
236
                const event = (this as any)[name] as EventEmitter<void>;
640✔
237

238
                plotlyInstance.on(eventName, (data: any) => event.emit(data));
640✔
239
            });
240

241
            plotlyInstance.on('plotly_click', (data: any) => {
20✔
242
                this.plotlyClick.emit(data);
×
243
            });
244

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

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

260
        return figure;
31✔
261
    }
262

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

270
        const layout = { ...this.layout };
1✔
271

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

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

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

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

306
        this.themeLoader.load(this.theme).then(theme => this.layout = theme);
1✔
307
    }
308
}
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