1

I am locally using following highcharts dependencies:

  • "angular-highcharts": "latest"
  • "highcharts": "latest"
  • "@types/highcharts": "latest"

Here is a live demo of my source code,

I am extensively using angular-highcharts in my angular 5 application. Alot of time there is a need to expand the chart (when there are alot of data point visible on the chart), to accommodate such cases I though of creating a generic component.

This component named chart-widget displays the chart in a bootstrap card with an option to expand chart, on expanded the same chart is opened in a modal. This generic component would take care of all the logic required to open any chart in a modal, (which would be draggable and resizeable). This way we don't need to replicate same functionality every where.

I made a generic component and every thing used to work fine, but recently we upgraded our repo dependencies as there was some other issues in the highcharts version we were using, those issues were fixed in the latest version of highCharts so we thought its best to upgrade to the latest version. Since then this functionality stopped working.

Following logic used to work for cloning chartConfig at the time of modal open. Then the cloned config was passed to the expanded chart which resides inside the modal. But now the expanded chart is always blank now.

this.expandChartConfig = new Chart(Object.assign({}, this.chartConfig.options));

where chartConfig is the normal config being used for rendering chart,

and expandChartConfig is the chart object passed to the modal.

After upgrading I realized that chartConfig.options property has been made private now, so I also tried:

this.expandChartConfig = new Chart(Object.assign({}, this.chartConfig.ref.options));

but this also didn't work.

Initially I was using the same config for both the charts but that lead to issues as when modal was closed highChart was also destroyed. So I figured instantiating a separate config at the time of opening modal for the chart inside the modal was the best case.

So now In plain words my question is how can I clone an existing chart, dynamically.

  • This functionality is required at dozens of places so I can't maintain separate chart objects at every single place.

  • Also There are alot of operation being performed on the charts, like setData, setCategories, addSeries, removeSeries, update e.t.c. that's why its not recommended to maintaining copies and updating them at every operation. Also these operation would be performed by the parent component so ChartWidgetComponent can't be aware of such changes when they are being performed.

So in short how can I clone an existing highchart dynamically and also whats the best method?

P.s. I tried a bunch of methods mentioned on stackOverflow but none of them seems to be working.

Saif
  • 1,745
  • 5
  • 23
  • 46
  • I think the best way to do that would be to create a new chart in a modal container, with the same options (reference) passed to it. Then, your chart should mutate the data if user play with it because both charts would work on the same data reference. Are you able to reproduce a minimal working example of your app on some sandbox (i.e stackblitz or codesandbox), and provide me with it? – daniel_s Oct 08 '18 at 16:49
  • @daniel_s I do have provided a link to the stackBlitz at the top of the question. here it is, https://stackblitz.com/edit/highcharts-cloning-chart – Saif Oct 09 '18 at 07:09

2 Answers2

1

To achieve the the expected effect, unfortunately it not enough to copy chart.options and pass it to the new one, if you have not defined the series data before (initially). In this case you need to get your data (from response) and assign it to the new component variable, then pass it to the widget and update your series. Here is instructions how to do it:

Add new field in component:

export class AppComponent {
  chartConfig: Chart;
  chartData: Object;
...

Assign it the reponse to the created field:

private setChartData(chartData) {
  const options = this.chartConfig.ref.options;
  if (chartData.categories.length === 0) {
    options.lang.noData = `no data from: ${chartData.customMsgFromDate} to ${chartData.customMsgEndDate}.`;
  } else {
    options.lang.noData = 'No data to display';
  }
this.chartData = chartData;

Pass the data to the widget:

<app-chart-widget [chartConfig]="chartConfig" chartLabel="Title" [chartData]="chartData"></app-chart-widget>

Add the data of every series to the new chart options:

onExpandChart(content): void {
  this.expandChartConfig = new Chart(this.chartConfig.ref.options);
  // Clone series data
  this.expandChartConfig.options.series.forEach((s, i) => {
    let name = s.name.toLowerCase()
    s.data = this.chartData[name]
  })

  this.modalService.open(content, { size: 'xl' as 'lg' });
  setTimeout(() => {
    this.resizeChart();
  ...

Live example: https://stackblitz.com/edit/highcharts-cloning-chart-bo3tfp

Kind regards!

daniel_s
  • 3,635
  • 1
  • 8
  • 24
  • then to generalize this chart data has to be in a predefined format. The thing is their can be n number of either static or dynamic series. In case of static series I can have a predefined format for chart data. But in case of dynamic series, I'll have to not only call this.chartConfig.ref.addSeries but also update the chartConfig, so the actual config being used at the time of cloning is in sync with the actual chart? similarly at the time of this.chartConfig.ref.removeSeries, I'll have to perform the same steps. – Saif Oct 09 '18 at 12:58
  • The general conception of components with state is to store that state in one place, and share it with all the other components which needs that data. Then if you would like to change it, just do it in one place and all components has the same source. Instead of using the `addSeries()` method, just update your component state by new series object, and call `update()` on the chart, to make your chart data (main data seed) always up to date. By the way, have you tried to use official Highcharts wrapper? – daniel_s Oct 09 '18 at 14:02
  • that is really strange. I never realized that I am not using the official one. I guess the official one was released like an year ago, and at the time this project got started anuglar-highcharts was the only one. But I'll give the official one a try as well. Thanks really man. You are a hero! :) – Saif Oct 10 '18 at 06:10
  • Enjoy! Additionally I can say, that the official one is much better and simpler in use, most commonly and directly supported by Highcharts developers of course, and has a great documentation. Also, we're trying to solve issues with our wrappers as fast as it is possible. – daniel_s Oct 10 '18 at 06:15
  • Also if I use the official one, then I guess I won't have to maintain separate type definitions for highcharts, right? That would be great, as updating highcharts-angular would make sure that the type definitions always remain in sync. – Saif Oct 10 '18 at 10:28
  • Official wrapper has its own definitions file. Our officla definitions for pure Highcharts are now in build phase, and should be released soon, so regarding the question about the maintaining definitions, it doesn't change nothing at this moment. – daniel_s Oct 10 '18 at 12:10
0

I'm using Highcharts too, I have defined a chart as a reusable Component when ever I want to paint an other chart I just pass the values througth Input() decorattor

in this case you can use something like this:

Chart Component ts

@Component ({
    selector: 'char-component'
    ...
})
export class CharComponent {
  Input() options: any; 
} 

Reusable Component implementation

<char-component [options]="firstObject"></char-component>
<char-component [options]="secondObject"></char-component>

Component to Reusecode

export clas Sample implements OnInit {
  ngOninit(){
     firstObject = {...} //Already defined
     secondObject = Object.assign(this.firstObject); //create a copy of this object

  }
}

note: if you don't know how many chart are in total you can use an array with the options objects and painted in the template if you need another one just push it into the array

<char-component *ngfor="option of options" [options]="option "></char-component>
Abel Valdez
  • 2,368
  • 1
  • 16
  • 33
  • looks like, you misunderstood me, I have chartWidget component, which is basically a wrapper for the chart, at the time of expanding it, chartWiget needs to clone the current chart options and open a separate chart in a modal. I have already tried object.assign() way, and it doesn't work, it would be really great if you could take a look at the linked demo. thank you. – Saif Oct 06 '18 at 15:34