gevgeny / angular2-highcharts

:bar_chart: :chart_with_upwards_trend: Highcharts for your Angular project
MIT License
379 stars 113 forks source link

Chart object changes after exporting #158

Closed marioleed closed 5 years ago

marioleed commented 7 years ago

I don't know if this is a bug in Highcharts or in angular2-highcharts, but the saveInstance part doesn't apply to the Highcharts lib, so I'm starting the issue here.

It seems like there's a bug (and hopefully not by design) that if you save an instance of the chart object and include the exporting module and do an export (the burger menu on the top left corner - Download PNG image, etc) the chart object changes to something else. (You can cancel the File download dialog and the bug will still be there.)

Demo: http://plnkr.co/edit/KtNgEC8WtRaqdVxnawQh?p=preview

Of course, this is not good, since I need to update values on the chart, even if the user exports.

Here are the important parts:

<chart [options]="options" (load)="saveInstance($event.context)"></chart>
export class AppComponent {
  chart: any;

  saveInstance(chartInstance) {
    this.chart = chartInstance;
  }

And also include the exporting module:

@NgModule({
  imports:      [
    BrowserModule, 
    ChartModule.forRoot(
      require('highcharts'), 
      require('highcharts/modules/exporting'))
      ],
  declarations: [AppComponent],
  bootstrap:    [AppComponent]
})
class AppModule { }

Before exporting, console.log(this.chart); produces:

a.Chart
  _cursor: ""
  _sharedClip,1000,,280,,: null
  _sharedClip,1000,,280,,m: null
  angular: undefined
  axes: Array[2]
  axisOffset: Array[4]
  bounds: Object
  btnCount: 0
  buttonOffset: -27
  cache-highcharts-contextmenu: div.highcharts-contextmenu
  callback: undefined
  cancelClick: false
  chartBackground: a.SVGElement
  chartHeight: 400
  chartWidth: 600
  clipBox: Object
  clipOffset: Array[4]
  clipRect: a.SVGElement
  colorCounter: 1
  container: div#highcharts-h0593gj-0.highcharts-container
  containerHeight: 0
  containerWidth: 0
  credits: a.SVGElement
  exportDivElements: Array[8]
  exportMenuHeight: 214
  exportMenuWidth: 208
  exportSVGElements: Array[2]
  exporting: Object
  hasCartesianSeries: true
  hasRendered: true
  hcEvents: Object
  hoverPoint: null
  hoverPoints: null
  hoverSeries: null
  index: 0
  inverted: undefined
  isDirtyBox: false
  isDirtyExporting: false
  isResizing: 0
  legend: a.Legend
  margin: Array[4]
  marginBottom: 74
  marginRight: 10
  mouseDownX: 553
  mouseDownY: 106
  mouseIsDown: false
  navigation: Object
  onload: null
  openMenu: false
  options: Object
  plotBackground: a.SVGElement
  plotBorder: a.SVGElement
  plotBorderWidth: 0
  plotBox: Object
  plotHeight: 280
  plotLeft: 71
  plotSizeX: 519
  plotSizeY: 280
  plotTop: 46
  plotWidth: 519
  pointCount: 4
  pointer: a.Pointer
  polar: undefined
  renderTo: chart
  renderer: a.SVGRenderer
  respRules: Array[0]
  series: Array[1]
  seriesGroup: a.SVGElement
  spacing: Array[4]
  spacingBox: Object
  symbolCounter: 1
  title: a.SVGElement
  titleOffset: 21
  tooltip: a.Tooltip
  userOptions: Object
  xAxis: Array[1]
  yAxis: Array[1]
  __proto__: Object
    addAxis: (a,b,c,d)
    addButton: (a)
    addCredits: (a)
    addSeries: (a,b,c)
    callbacks: Array[2]
    cloneRenderTo: (a)
    contextMenu: (a,c,e,b,f,m,g)
    currentOptions: (a)
    destroy: ()
    destroyExport: (a)
    drawChartBox: ()
    exportChart: (a,c)
    firstRender: ()
    get: (a)
    getArgs: ()
    getAxes: ()
    getAxisMargins: ()
    getChartHTML: ()
    getChartSize: ()
    getContainer: ()
    getMargins: (a)
    getSVG: (a)
    getSVGForExport: (a,c)
    getSelectedPoints: ()
    getSelectedSeries: ()
    getStacks: ()
    hideLoading: ()
    hideOverlappingLabels: (a)
    init: (b,c)
    initReflow: ()
    initSeries: (b)
    isInsidePlot: (a,b,c)
    isReadyToRender: ()
    layOutTitles: (a)
    linkSeries: ()
    matchResponsiveRule: (f,g)
    onload: ()
    orderSeries: (a)
    pan: (a,b)
    print: ()
    propFromSeries: ()
    propsRequireDirtyBox: Array[20]
    propsRequireUpdateSeries: Array[6]
    redraw: (b)
    reflow: (a)
    render: ()
    renderExporting: ()
    renderLabels: ()
    renderSeries: ()
    resetMargins: ()
    sanitizeSVG: (a,c)
    setChartSize: (a)
    setClassName: (a)
    setResponsive: (a)
    setSize: (b,c,e)
    setSubtitle: (a)
    setTitle: (a,b,c)
    showLoading: (a)
    showResetZoom: ()
    update: (a,c)
    zoom: (a)
    zoomOut: ()

After exporting, console.log(this.chart); produces:

a.Chart
  __proto__: Object
    addAxis: (a,b,c,d)
    addButton: (a)
    addCredits: (a)
    addSeries: (a,b,c)
    callbacks: Array[2]
    cloneRenderTo: (a)
    contextMenu: (a,c,e,b,f,m,g)
    currentOptions: (a)
    destroy: ()
    destroyExport: (a)
    drawChartBox: ()
    exportChart: (a,c)
    firstRender: ()
    get: (a)
    getArgs: ()
    getAxes: ()
    getAxisMargins: ()
    getChartHTML: ()
    getChartSize: ()
    getContainer: ()
    getMargins: (a)
    getSVG: (a)
    getSVGForExport: (a,c)
    getSelectedPoints: ()
    getSelectedSeries: ()
    getStacks: ()
    hideLoading: ()
    hideOverlappingLabels: (a)
    init: (b,c)
    initReflow: ()
    initSeries: (b)
    isInsidePlot: (a,b,c)
    isReadyToRender: ()
    layOutTitles: (a)
    linkSeries: ()
    matchResponsiveRule: (f,g)
    onload: ()
    orderSeries: (a)
    pan: (a,b)
    print: ()
    propFromSeries: ()
    propsRequireDirtyBox: Array[20]
    propsRequireUpdateSeries: Array[6]
    redraw: (b)
    reflow: (a)
    render: ()
    renderExporting: ()
    renderLabels: ()
    renderSeries: ()
    resetMargins: ()
    sanitizeSVG: (a,c)
    setChartSize: (a)
    setClassName: (a)
    setResponsive: (a)
    setSize: (b,c,e)
    setSubtitle: (a)
    setTitle: (a,b,c)
    showLoading: (a)
    showResetZoom: ()
    update: (a,c)
    zoom: (a)
    zoomOut: ()
    __proto__: Object

As you can see, there are no properties on the Chart object anymore.

gevgeny commented 7 years ago

This is strange indeed. Could you make also live for native highcharts (forked from this for example http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/line-basic/ ) so that we can find out whose the bug is.

marioleed commented 7 years ago

Sure, couldn't reproduce the bug there, though, so it seems like the bug is not in the native highcharts.

Here's a jQuery version with JavaScript: http://jsfiddle.net/qwbyf8m9/

And here's one with TypeScript: http://jsfiddle.net/utvko3ee/1/

It doesn't seem like jQuery or plain Javascript or TypeScript does anything strange, but maybe it's angular, I don't know.

gevgeny commented 7 years ago

thanks. looks like it is the bug.

marioleed commented 7 years ago

@gevgeny, Any updates?

gevgeny commented 7 years ago

sorry. busy with other project now.

attilacsanyi commented 7 years ago

Hi @marioleed did you find any workaround to prevent this bug? I also need the exporting feature. Thanks for your feedback.

attilacsanyi commented 7 years ago

The only workaround for now is to store the original chartObject before call exportChartLocal and restore right after, I hope this helps.

marioleed commented 7 years ago

Hey @attilacsanyi, no, unfortunately I haven't had the time (or priority from the customer) to fix this yet. But if you found a workaround, I'll apply it myself if it's quick =) Thanks!

attilacsanyi commented 7 years ago

Sorry for the late answer @marioleed I am using the following method for exporting:

exportChartLocal = (type = 'image/png', options = this.chartOptions) => {
    const oldChartObject = this.chartObject;
    this.chartObject.exportChartLocal({
      sourceWidth: 2048,
      type: type,
      filename: 'my-chart'
    }, options);

    /**
     * ISSUE: https://github.com/gevgeny/angular2-highcharts/issues/158
     * Workaround is to store the original chartObject before export and restore after
     */
    this.chartObject = _.cloneDeep(oldChartObject);
  }

Beside this I have couple of issues during export, I mean not consistent, for example JPEG or PDF exported format is different in styles, it might due to my wrong implementation or this workaround, not figured it out yet...

Anourian commented 7 years ago

I encountered this at work this week. I had some code pushing the context instance into an array for a specific situation where the same options were used in different views for a wizard but required access. Even outside of the wizard it would add an extra instance. After 2 manual getSVG calls for a custom full page pdf export it would delete the oldest instance which was being used and had the correct object. Haven't looked into the angular2-highcharts library yet, but may this weekend to see if there's a simple fix.

A work around is to put a condition in the (load) callback. The issue is that on exportation(which generally uses getSVG I think) it calls the (load) callback again with seemingly a copy that's missing details. You can see it being called constantly by putting a basic console log in the (load) callback.

For the time being, something like the following should fix the issue of the context being overwritten, while avoiding the need for a deep clone.

callback(instance)
    if (!this.chartObject) {
        this.chartObject = instance;
    }
loonix commented 6 years ago

Hi Guys!any update on this bug?

Anj21 commented 5 years ago

Hello Guys, any update on this bug, i am also facing the same problem?

loonix commented 5 years ago

@Anj21 my solution was to change to https://github.com/highcharts/highcharts-angular .Highcharts official wrapper for Angular.

Anj21 commented 5 years ago

@loonix <highcharts-chart [Highcharts]="highcharts" [options]="chartOptions">

console.log("highcharts charts => ", Highcharts); //This one give highchart object as output in console. console.log("highcharts charts => ", Highcharts.charts); //Highcharts.charts give charts object as output in console. Array(0) => 0: a.Chart {renderTo: highcharts-chart, exporting: {…}, navigation: {…}, userOptions: {…}, margin: Array(4), …} length: 1 proto: Array(0)

console.log("highcharts charts[0] => ", Highcharts.charts.length, Highcharts.charts[0]); Now in this, when i am trying to access Highcharts.charts[0] it give undefined why? Can Anyone please help me to figure out this, i want to get Highcharts.charts[0].series to re-render the chart for live polling.

In AngularJs we can do like that, but i am not finding any lead for Angular7. var chart = $('#theChart').highcharts(); chart.series[0].update({ color: '#d14242' }); This is how, we can do in AngularJs, but In angular7, I am not getting. Can anyone Please help.

Anj21 commented 5 years ago

@loonix here is the sanpshot for the same. image

gevgeny commented 5 years ago

@Anj21 my solution was to change to https://github.com/highcharts/highcharts-angular .Highcharts official wrapper for Angular.

Thanks for the link, now i finally added the message to the readme https://github.com/gevgeny/angular2-highcharts#%EF%B8%8F-not-supported-anymore--consider-using-the-offical-highcharts-wrapper-for-angular

loonix commented 5 years ago

@loonix <highcharts-chart [Highcharts]="highcharts" [options]="chartOptions">

console.log("highcharts charts => ", Highcharts); //This one give highchart object as output in console. console.log("highcharts charts => ", Highcharts.charts); //Highcharts.charts give charts object as output in console. Array(0) => 0: a.Chart {renderTo: highcharts-chart, exporting: {…}, navigation: {…}, userOptions: {…}, margin: Array(4), …} length: 1 proto: Array(0)

console.log("highcharts charts[0] => ", Highcharts.charts.length, Highcharts.charts[0]); Now in this, when i am trying to access Highcharts.charts[0] it give undefined why? Can Anyone please help me to figure out this, i want to get Highcharts.charts[0].series to re-render the chart for live polling.

In AngularJs we can do like that, but i am not finding any lead for Angular7. var chart = $('#theChart').highcharts(); chart.series[0].update({ color: '#d14242' }); This is how, we can do in AngularJs, but In angular7, I am not getting. Can anyone Please help.

You should be able to use the official plugin without much problems. Just take a look at the documentation and adapt to your project, I've done the same and it worked out just fine.