highcharts / highcharts-react

The official Highcharts supported wrapper for React
Other
1.07k stars 109 forks source link

Bubble chart with NextJS and highcharts-more #76

Closed aorsten closed 5 years ago

aorsten commented 5 years ago

Probably related to: https://github.com/highcharts/highcharts-react/issues/16 (it didn't solve it for me)

I am making an app with NextJS, which is mostly React. I have been using many different chart items with success, like line/spline/area/scatter/column, etc. Now I needed to add bubble charts, and thus it seems I need highcharts-more.

I tried applying HighchartsMore like this:

import Highcharts from 'highcharts'
import HC_More from 'highcharts/highcharts-more'
import HighchartsReact from 'highcharts-react-official'

HC_More(Highcharts)

and I have not changed the code for charts in the render method since it worked for all the other chart types:

class BaseChart extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <figure className="figure w-100">
                <HighchartsReact 
                    highcharts={Highcharts}
                    options={this.props.options}
                />
                <style jsx>{`
                    figure {
                        margin-top: 3em;
                        margin-bottom: 3em;
                    }
                `}</style>
            </figure>
        );
    }
}

Without the HC_More lines, I get error #17, since 'bubble' charts don't exist. However, with the lines, I get:

Uncaught (in promise) Error: n is not a function
    at C:\(...)\node_modules\highcharts\highcharts-more.js:8:280
    at C:\(...)\node_modules\highcharts\highcharts-more.js:11:268
    at Module../app/(...)/BaseChart.js

I have been struggling with this for many hours, and hope someone knows how to get this working?

pa87901 commented 5 years ago

I get the same thing. I'm interested to see how this resolves.

The following steps make it work but it's not ideal and I would still like to have a better resolution.

  1. I have to do the following imports (but comment out the HC_more callback invocation).
import Highcharts from 'highcharts' //core
import HighchartsReact from 'highcharts-react-official';
import HC_more from 'highcharts/highcharts-more.src' //module
// HC_more(Highcharts) //init module
  1. Perform an npm start and navigate to the page containing my bubble chart. It will crash giving me error 17 as aorsten described above:
Error: Highcharts error #17:
  1. While React and Node are still watching, I release the previously commented out line:
    HC_more(HighCharts)

Then it works.

It should work right from the start though without having to do the above steps.

aorsten commented 5 years ago

I have the exact same experience, @pa87901. Which probably means it could be solved somehow?

ppotaczek commented 5 years ago

Hi aorsten, hi pa87901,

Thank you for reporting this issue.

@aorsten, is it possible for you to prepare and send me some minimal project that presents this problem?

@pa87901, do you also use NextJS?

pa87901 commented 5 years ago

Hi ppotaczek,

No, I am using ReactJS.

aorsten commented 5 years ago

@ppotaczek , I have recreated it in a really simple next-js project. It's in the uploaded zip file: hcmore.zip

To create it, I suppose this will work:

  1. Unpack to a folder you name hcmore.
  2. do npm install inside the folder.
  3. do npm run dev and open localhost:3000.

Now you should have a working scatter chart. Then:

Now you should have a working bubble chart. But if you refresh the page, the error returns.

Thanks for having a look at this!

KacperMadej commented 5 years ago

Thank you for the demo @aorsten

Code in index.js runs at twice. First run is done in an environment that lacks window (server side) and causes Highcharts to be loaded, but not initialized - see first few lines of code: http://code.highcharts.com/6.2.0/highcharts.src.js (it's a link to version 6.2.0 because that version is used in your demo project, but it's not different in current v7.0.0).

Easy fix is to place all modules inits in a if checking if Highcharts is an object or a function. It should be an object for modules initialization to work without any errors, so code like below is an easy fix:

if (typeof Highcharts === 'object') {
    HC_More(Highcharts)
}

After a file save the project is rebuild, but imported libraries are not reloaded. Because of this if you will change Highcharts instance (e.g. through a module initialization) and save the index.js file with those changes removed they will stay. The code run on environment without window defined runs once, so after index.js file saves you are safe - this should explain why saving helped.

The error is not about this repository's React wrapper for Highcharts - it's about loading Highcharts in an environment where it cannot be build because it requires browser environment with window and other related features. This is a know issue with NextJS and is covered here: https://github.com/zeit/next.js/wiki/FAQ#i-use-a-library-which-throws-window-is-undefined

skoch commented 5 years ago

I had the same issue however mine was with using highcharts/modules/sankey.

I know about the whole window issue with Next JS so my first move was to upgrade functional Component to a class Component and put the initialization in componentDidMount however that created a different error which left me stumped:

image

Uncaught TypeError: t[(e.contructorType || "chart")]

Anyone have insight as to why attempting to initialize in componentDidMount doesn't work?

componentDidMount() {
  addSankeyModule(Highcharts);
}

Regardless, thanks for the obvious (in hindsight) solution.

KacperMadej commented 5 years ago

@skoch Just from the look of the error it refers to this code line: https://github.com/highcharts/highcharts-react/blob/8642adb61a534cc0d24b41d2c6d63e05dd612b6c/src/HighchartsReact.js#L13

Error like that might be caused by:

Please try checking the above options and if you could provide a way to recreate the problem then we could help you with debugging.

skoch commented 5 years ago

Here's a sandbox for testing the constructorType: https://codesandbox.io/s/lxl2292nz

If you comment out the usage of the constructorType it works... meaning it's defaulting to just chart.

How does one know what the constructorType strings should be for various modules/charts? The only examples given are mapChart and stockChart.

I guess I was confused by the documentation:

If you have added a module or a plugin that adds new constructor then you can use it and set using this property.

My (incorrect) assumption was that sankeyChart would be needed for the constructorType.

As for what Highcharts is when my class based component is mounted the following:

componentDidMount() {
  console.log('Highcharts is:', (typeof Highcharts));
}
// output: Highcharts is: object

However on the server the same log (when done just after importing Highcharts) reports it's a function.

So if it's supposed to be an object, why doesn't it work on the mount? Below is my test component:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Highcharts from 'highcharts';
import addSankeyModule from 'highcharts/modules/sankey';
import HighchartsReact from 'highcharts-react-official';

// console.log('Highcharts is:', (typeof Highcharts));
// on the server, this is a function which is why we need to check before we
// init the module

// bad
// addSankeyModule(Highcharts);

// good
if (typeof Highcharts === 'object') {
  addSankeyModule(Highcharts);
}

class SankeyChart extends Component {
  static propTypes = {
    data: PropTypes.shape({
    }).isRequired,
  };

  componentDidMount() {
    // but if Highcharts is an object here, why doesn't this work?
    // this doesn't get called on the server
    // addSankeyModule(Highcharts);
  }

  render() {
    const { data } = this.props;

    return (
      <section className="chart-wrapper">
        <div className="chart">
          <HighchartsReact
            highcharts={Highcharts}
            options={data}
            // what are valid strings to use here?
            // removing it, for sankey, just works
            // constructorType="sankeyChart"
          />
        </div>
        <style jsx>
          ...
        </style>
      </section>
    );
  }
}

export default SankeyChart;

Edit for clarity

KacperMadej commented 5 years ago

If you would define a custom constructor (like it's done through custom code for sparkline) then the wrapper will work through constructorType.

Docs says:

String for constructor method, defaults to 'chart'. Other official constructors are:

'stockChart' for Highstock charts 'mapChart' for Highmaps charts

Another official constructor is ganttChart. In case of doubt please check live demo of pure JS Highcharts to check what constructor is being used for what chart. However, the general rule is to use constructor defined for a product (Highcharts, Highstock, Highmaps, Highcharts Gantt). As always, in case you have a need for technical support please use https://www.highcharts.com/blog/support/

I hope this info will clear thing up, but please ask if anything still needs to be better explained.

Highcharts will be an object or a function depending on where it is loaded. The Highcharts file starts with:

'use strict';
(function (root, factory) {
    if (typeof module === 'object' && module.exports) {
        module.exports = root.document ?
            factory(root) :
            factory;
    } else if (typeof define === 'function' && define.amd) {
        define(function () {
            return factory(root);
        });
    } else {
        root.Highcharts = factory(root);
    }
}(typeof window !== 'undefined' ? window : this, function (win) {

Different ways of loading Highcharts are listed here: https://github.com/highcharts/highcharts#download-and-install-highcharts

TillaTheHun0 commented 5 years ago

@KacperMadej thanks for that shim suggestion while the issue still exists with NextJS 👍 . I can confirm that works.