diff --git a/FAQ.md b/FAQ.md index b0f2287..0870113 100644 --- a/FAQ.md +++ b/FAQ.md @@ -11,3 +11,32 @@ It's possible disable some plotly events returning `false` in its event. Please Angular uses the `(click)` directive to itself. If we use the `click` name as event alias to `plotly_click` we might get unexpected behaviour from both angular and plotly.js. We believe it is simpler to just avoid using the same name is better. Please see issue: https://github.com/plotly/angular-plotly.js/issues/63 + + +## How to access global Plotly object code + +If you're using `PlotlyModule`, which includes `plotly.js` via commonjs modules, you can access the `Plotly` object via `PlotlyService`. See the example: + +```typescript +import { PlotlyService } from 'angular-plotly.js'; + +@Component({ + selector: 'app-plotly', + template: '...', +}) +export class AppPlotlyComponent { + constructor(public plotlyService: PlotlyService) { + const Plotly = plotlyService.getPlotly(); + } +} +``` + + +## The graph is too slow when interacting, what could I do ? + +Angular checks all the data everytime to see if there is a change to be applied, sometimes this brings unexpected slowness when treating a large data to be displayed. To avoid this check, set the property `updateOnlyWithRevision` to `true`. When you need the component to update, you can use the `revision` property (a number) to force it to update. Simply incrementing it (e.g.: `this.revision += 1`) will force the component to be updated. + + +## How to support Angular 7.x ? + +There was a breaking change from `Angular 7.x` to `8.x`. Please see this thread: https://github.com/plotly/angular-plotly.js/issues/79 diff --git a/README.md b/README.md index 0a4597f..e606fa1 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ --- +Supports Angular 8.x and up. If you want to use with Angular 7.x, [please read the FAQ](https://github.com/plotly/angular-plotly.js/blob/master/FAQ.md#how-to-support-angular-7x-). + + ## Content * [Installation](#installation) @@ -94,22 +97,25 @@ The `plotly.js` is bundled within the angular code. To avoid this, please read [ **Warning**: for the time being, this component may _mutate_ its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed. -| Prop | Type | Default | Description | -| -------------------- | ---------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `[data]` | `Array` | `[]` | list of trace objects (see https://plot.ly/javascript/reference/) | -| `[layout]` | `Object` | `undefined` | layout object (see https://plot.ly/javascript/reference/#layout) | -| `[frames]` | `Array` | `undefined` | list of frame objects (see https://plot.ly/javascript/reference/) | -| `[config]` | `Object` | `undefined` | config object (see https://plot.ly/javascript/configuration-options/) | -| `[revision]` | `Number` | `undefined` | When provided, causes the plot to update _only_ when the revision is incremented. | -| `(initialized)` | `Function(figure, graphDiv)` | `undefined` | Callback executed after plot is initialized. See below for parameter information. | -| `(update)` | `Function(figure, graphDiv)` | `undefined` | Callback executed when when a plot is updated due to new data or layout, or when user interacts with a plot. See below for parameter information. | -| `(purge)` | `Function(figure, graphDiv)` | `undefined` | Callback executed when component unmounts, before `Plotly.purge` strips the `graphDiv` of all private attributes. See below for parameter information. | -| `(error)` | `Function(err)` | `undefined` | Callback executed when a plotly.js API method rejects | -| `[divId]` | `string` | `undefined` | id assigned to the `
` into which the plot is rendered. | -| `[className]` | `string` | `undefined` | applied to the `
` into which the plot is rendered | -| `[style]` | `Object` | `{position: 'relative', display: 'inline-block'}` | used to style the `
` into which the plot is rendered | -| `[debug]` | `Boolean` | `false` | Assign the graph div to `window.gd` for debugging | -| `[useResizeHandler]` | `Boolean` | `false` | When true, adds a call to `Plotly.Plot.resize()` as a `window.resize` event handler | +| Prop | Type | Default | Description | +| -------------------------- | ---------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[data]` | `Array` | `[]` | list of trace objects (see https://plot.ly/javascript/reference/) | +| `[layout]` | `Object` | `undefined` | layout object (see https://plot.ly/javascript/reference/#layout) | +| `[frames]` | `Array` | `undefined` | list of frame objects (see https://plot.ly/javascript/reference/) | +| `[config]` | `Object` | `undefined` | config object (see https://plot.ly/javascript/configuration-options/) | +| `[revision]` | `Number` | `undefined` | When provided, causes the plot to update _only_ when the revision is incremented. | +| `[updateOnLayoutChange]` | `Boolean` | `true` | Flag which determines if this component should watch to changes on `layout` property and update the graph. | +| `[updateOnDataChange]` | `Boolean` | `true` | Flag which determines if this component should watch to changes on `data` property and update the graph. | +| `[updateOnlyWithRevision]` | `Boolean` | `false` | If `true`, this component will update only when the property `revision` is increased. | +| `(initialized)` | `Function(figure, graphDiv)` | `undefined` | Callback executed after plot is initialized. See below for parameter information. | +| `(update)` | `Function(figure, graphDiv)` | `undefined` | Callback executed when when a plot is updated due to new data or layout, or when user interacts with a plot. See below for parameter information. | +| `(purge)` | `Function(figure, graphDiv)` | `undefined` | Callback executed when component unmounts, before `Plotly.purge` strips the `graphDiv` of all private attributes. See below for parameter information. | +| `(error)` | `Function(err)` | `undefined` | Callback executed when a plotly.js API method rejects | +| `[divId]` | `string` | `undefined` | id assigned to the `
` into which the plot is rendered. | +| `[className]` | `string` | `undefined` | applied to the `
` into which the plot is rendered | +| `[style]` | `Object` | `{position: 'relative', display: 'inline-block'}` | used to style the `
` into which the plot is rendered | +| `[debug]` | `Boolean` | `false` | Assign the graph div to `window.gd` for debugging | +| `[useResizeHandler]` | `Boolean` | `false` | When true, adds a call to `Plotly.Plot.resize()` as a `window.resize` event handler | **Note**: To make a plot responsive, i.e. to fill its containing element and resize when the window is resized, use `style` or `className` to set the dimensions of the element (i.e. using `width: 100%; height: 100%` or some similar values) and set `useResizeHandler` to `true` while setting `layout.autosize` to `true` and leaving `layout.height` and `layout.width` undefined. This will implement the behaviour documented here: https://plot.ly/javascript/responsive-fluid-layout/ diff --git a/package-lock.json b/package-lock.json index f746564..6dd8593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "angular-plotly.js", - "version": "1.4.2", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e90b1f3..42d01d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-plotly.js", - "version": "1.4.2", + "version": "1.5.0", "license": "MIT", "main": "index.ts", "scripts": { diff --git a/src/app/demo/bar-plots/bar-plots.component.html b/src/app/demo/bar-plots/bar-plots.component.html index 0f70e05..facd758 100644 --- a/src/app/demo/bar-plots/bar-plots.component.html +++ b/src/app/demo/bar-plots/bar-plots.component.html @@ -1,5 +1,5 @@
- +

 

 

diff --git a/src/app/demo/bar-plots/bar-plots.component.ts b/src/app/demo/bar-plots/bar-plots.component.ts index e60d2ef..bfa2520 100644 --- a/src/app/demo/bar-plots/bar-plots.component.ts +++ b/src/app/demo/bar-plots/bar-plots.component.ts @@ -63,4 +63,8 @@ export class BarPlotComponent { } }; + public onPlotlyClick(event: Event) { + console.log('Sim sim sim', event); + } + } diff --git a/src/app/demo/demo.component.html b/src/app/demo/demo.component.html index 9e09805..794db6e 100644 --- a/src/app/demo/demo.component.html +++ b/src/app/demo/demo.component.html @@ -16,6 +16,7 @@
Angular Plotly
  • Frames
  • Timeout Update
  • Huge Memory Usage
  • +
  • Slow Example
  • diff --git a/src/app/demo/demo.module.ts b/src/app/demo/demo.module.ts index dc8e476..80d2349 100644 --- a/src/app/demo/demo.module.ts +++ b/src/app/demo/demo.module.ts @@ -22,6 +22,7 @@ import { MemoryLeakComponent } from './memory-leak/memory-leak.component'; import { FramesComponent } from './frames/frames.component'; import { TimeoutUpdateComponent } from './timeout-update/timeout-update.component'; import { HugeMemoryUsageComponent } from './huge-memory-usage/huge-memory-usage.component'; +import { SlowExampleComponent } from './slow-example/slow-example.component'; const demoRoutes: Routes = [ @@ -35,13 +36,14 @@ const demoRoutes: Routes = [ { path: 'frames', component: FramesComponent, data: { title: 'Frames' } }, { path: 'timeout-update', component: TimeoutUpdateComponent, data: { title: 'Timeout Update' } }, { path: 'huge-memory-usage', component: HugeMemoryUsageComponent, data: { title: 'Huge Memory Usage' } }, + { path: 'slow-example', component: SlowExampleComponent, data: { title: 'Slow example' } }, { path: '', redirectTo: '/home', pathMatch: 'full' }, ]; // PlotlyModule.plotlyjs = PlotlyJS; -PlotlyViaCDNModule.plotlyVersion = '1.5.0'; +PlotlyViaCDNModule.plotlyVersion = '1.49.4'; // PlotlyViaCDNModule.plotlyBundle = 'cartesian'; @NgModule({ @@ -65,6 +67,7 @@ PlotlyViaCDNModule.plotlyVersion = '1.5.0'; FramesComponent, TimeoutUpdateComponent, HugeMemoryUsageComponent, + SlowExampleComponent, ], exports: [DemoComponent], }) diff --git a/src/app/demo/frames/frames.component.html b/src/app/demo/frames/frames.component.html index 01e6656..f631864 100644 --- a/src/app/demo/frames/frames.component.html +++ b/src/app/demo/frames/frames.component.html @@ -1,5 +1,6 @@
    - +

     

    Example got from: https://plot.ly/javascript/gapminder-example/ diff --git a/src/app/demo/slow-example/slow-example.component.html b/src/app/demo/slow-example/slow-example.component.html new file mode 100644 index 0000000..0d81b87 --- /dev/null +++ b/src/app/demo/slow-example/slow-example.component.html @@ -0,0 +1,29 @@ +
    + + +
    +
    + [updateOnLayoutChange]="false" +
    +
    + [updateOnDataChange]="false" +
    +
    + [updateOnlyWithRevision]="false" +
    +
    + + + +
    +
    + [updateOnLayoutChange]="true" +
    +
    + [updateOnDataChange]="true" +
    +
    + [updateOnlyWithRevision]="true" +
    +
    +
    diff --git a/src/app/demo/slow-example/slow-example.component.scss b/src/app/demo/slow-example/slow-example.component.scss new file mode 100644 index 0000000..1d10f26 --- /dev/null +++ b/src/app/demo/slow-example/slow-example.component.scss @@ -0,0 +1,12 @@ + + +.row { + display: flex; +} + +.col { + flex: 1; + align-self: center; + justify-content: center; + text-align: center; +} diff --git a/src/app/demo/slow-example/slow-example.component.ts b/src/app/demo/slow-example/slow-example.component.ts new file mode 100644 index 0000000..92270c3 --- /dev/null +++ b/src/app/demo/slow-example/slow-example.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'plotly-slow-example', + templateUrl: './slow-example.component.html', + styleUrls: ['./slow-example.component.scss'], +}) +export class SlowExampleComponent implements OnInit { + data: any; + layout: any; + + ngOnInit() { + this.layout = { showlegend: false }; + this.data = this.getData(); + } + + getData() { + const startValue = 0; + const stopValue = 100000; + const pointNum = 10000; + const traceNum = 10; + const currValue = startValue; + const step = (stopValue - startValue) / (pointNum - 1); + + const data = []; + + for (let j = 0; j < traceNum; j++) { + const X = []; + const Y = []; + for (let i = 0; i < pointNum; i++) { + X.push(currValue + step * i); + Y.push(this.gaussianRand() * 8 + j * 5); + } + data.push({ + type: 'scattergl', + mode: 'line', + x: X, + y: Y + }); + } + + return data; + } + + gaussianRand() { + let rand = 0; + for (let i = 0; i < 6; i += 1) { + rand += Math.random(); + } + return rand / 6 - 0.5; + } +} diff --git a/src/app/shared/plot/plot.component.spec.ts b/src/app/shared/plot/plot.component.spec.ts index 6fa16f1..e430708 100644 --- a/src/app/shared/plot/plot.component.spec.ts +++ b/src/app/shared/plot/plot.component.spec.ts @@ -73,25 +73,21 @@ describe('PlotComponent', () => { it('should update the graph when the data changes', (done) => { spyOn(component, 'updatePlot'); component.data = [{ y: [10, 15, 13, 17], type: 'box' }]; - expect(component.datarevision).toEqual(0); + component.createPlot().then(() => { component.ngDoCheck(); expect(component.updatePlot).not.toHaveBeenCalled(); - expect(component.datarevision).toEqual(0); component.data = [{ y: [11, 15, 13, 17], type: 'box' }]; component.ngDoCheck(); expect(component.updatePlot).toHaveBeenCalled(); - expect(component.datarevision).toEqual(1); component.ngDoCheck(); expect(component.updatePlot).toHaveBeenCalledTimes(1); - expect(component.datarevision).toEqual(1); component.data[0].y[0] = 12; component.ngDoCheck(); expect(component.updatePlot).toHaveBeenCalledTimes(2); - expect(component.datarevision).toEqual(2); done(); }); }); @@ -107,6 +103,9 @@ describe('PlotComponent', () => { component.ngDoCheck(); expect(component.updatePlot).toHaveBeenCalled(); + component.ngDoCheck(); + expect(component.updatePlot).toHaveBeenCalledTimes(1); + component.layout.title = 'title three '; component.ngDoCheck(); expect(component.updatePlot).toHaveBeenCalledTimes(2); diff --git a/src/app/shared/plot/plot.component.ts b/src/app/shared/plot/plot.component.ts index aa6586e..d684246 100644 --- a/src/app/shared/plot/plot.component.ts +++ b/src/app/shared/plot/plot.component.ts @@ -33,7 +33,6 @@ export class PlotComponent implements OnInit, OnChanges, OnDestroy, DoCheck { public resizeHandler?: (instance: Plotly.PlotlyHTMLElement) => void; public layoutDiffer: KeyValueDiffer; public dataDiffer: IterableDiffer; - public datarevision: number = 0; @ViewChild('plot', {static: true}) plotEl: ElementRef; @@ -49,6 +48,10 @@ export class PlotComponent implements OnInit, OnChanges, OnDestroy, DoCheck { @Input() debug: boolean = false; @Input() useResizeHandler: boolean = false; + @Input() updateOnLayoutChange = true; + @Input() updateOnDataChange = true; + @Input() updateOnlyWithRevision = false; + @Output() initialized = new EventEmitter(); @Output() update = new EventEmitter(); @Output() purge = new EventEmitter(); @@ -141,32 +144,39 @@ export class PlotComponent implements OnInit, OnChanges, OnDestroy, DoCheck { } ngDoCheck() { + if (this.updateOnlyWithRevision) { + return false; + } + let shouldUpdate = false; - if (this.layoutDiffer) { - const layoutHasDiff = this.layoutDiffer.diff(this.layout); - if (layoutHasDiff) { - shouldUpdate = true; + if (this.updateOnLayoutChange) { + if (this.layoutDiffer) { + const layoutHasDiff = this.layoutDiffer.diff(this.layout); + if (layoutHasDiff) { + shouldUpdate = true; + } + } else if (this.layout) { + this.layoutDiffer = this.keyValueDiffers.find(this.layout).create(); + } else { + this.layoutDiffer = undefined; } - } else if (this.layout) { - this.layoutDiffer = this.keyValueDiffers.find(this.layout).create(); - } else { - this.layoutDiffer = undefined; } - if (this.dataDiffer) { - const dataHasDiff = this.dataDiffer.diff(this.data); - if (dataHasDiff) { - shouldUpdate = true; + if (this.updateOnDataChange) { + if (this.dataDiffer) { + const dataHasDiff = this.dataDiffer.diff(this.data); + if (dataHasDiff) { + shouldUpdate = true; + } + } else if (Array.isArray(this.data)) { + this.dataDiffer = this.iterableDiffers.find(this.data).create(this.dataDifferTrackBy); + } else { + this.dataDiffer = undefined; } - } else if (Array.isArray(this.data)) { - this.dataDiffer = this.iterableDiffers.find(this.data).create(this.dataDifferTrackBy); - } else { - this.dataDiffer = undefined; } if (shouldUpdate && this.plotlyInstance) { - this.datarevision += 1; this.updatePlot(); } } @@ -227,10 +237,7 @@ export class PlotComponent implements OnInit, OnChanges, OnDestroy, DoCheck { throw error; } - const layout = { - ...{datarevision: this.datarevision}, - ...this.layout, - }; + const layout = {...this.layout}; return this.plotly.update(this.plotlyInstance, this.data, layout, this.config, this.frames).then(() => { const figure = this.createFigure();