0

I explain my situation:

I am using HighCharts maps. The code forces a DrillDown (force to change map) and once done that, I must search inside all points of this new map (taking care that the DrillDown -doDrilldown()`- is not a function declared by me, it is a function property of HighCharts)

The problem is that I must wait until DrillDown ends before searching all points of this newly-loaded map. I have solved it using a SetTimeOut function:

var names = name.split(', ');

// Find point in countries map
this.internalChart.series[0].data.forEach(data => {
    if (data.name === names[1]) {
      data.doDrilldown() // FORCE DRILLDOWN. HERE I MUST WAIT
      // WAITING WITH A TIMEOUT IS WORKING
      setTimeout(() => {
        // CODE THAT MUST BE EXECUTED ONCE MAP IS LOADED
        this.state.loadedMaps.length > 0 && this.internalChart.options.drilldown.series[0].data.forEach(dataInside => {
          if (dataInside.name === names[0]) {
            this.setState({
              province: dataInside['hc-key'],
              loading: false
            });
          }
        });
      }, 1000)
    }
});

But this is not the correct way. I do not want always wait one second, I want that the code executes once load is done. I have tried using async/await but it is not working. The code I have tried is:

var names = name.split(', ');

// Find point in countries map
this.internalChart.series[0].data.forEach(data => {
    if (data.name === names[1]) {
        (async () => { await data.doDrilldown(); })() //HERE I MUST WAIT FOR DO THIS ACTION
            .then(()=>{
                // ONCE DONE BELOW ACTION, EXECUTE NEXT CODE:
                this.internalChart.options.drilldown.series[0].data.forEach(dataInside => {
                    if (dataInside.name === names[0]) {
                        this.setState({
                            //country: country,
                            province: dataInside['hc-key'],
                            loading: false
                        });
                    }
                });
            });
    }
});

Anyone knows how could I solve my problem?.

Thank you.


EDIT 1:

I have made an example which represents the problem. It can be found here: JSFiddle example


EDIT 2:

I need to know when .doDrilldown() finish. This function loads a new map and its data, so when the new map and new data would be loaded the code should continue executing. I am loading new drilldown series like:

// Import all map data
import(`./maps/${point['hc-key']}-all.geo`).then(mapData => {

  // Set data of the map
  var data = [];
  mapData.default.features.forEach((element, i) => {
    data.push({ 'hc-key': element.properties['hc-key'], 'name': element.properties.name, 'value': 0 });
  });

  // Create the new drilldown serie
  try {
    var drilldownSerie = {
      id: point['hc-key'],
      mapData: mapData.default,
      data: data,
      joinBy: 'hc-key',
      name: mapData.default.title,
      allowPointSelect: true,
      borderColor: '#ffffff',
      borderWidth: 1.2,
      states: {
        hover: {
          color: this.props.geoColor
        },
        select: {
          color: this.props.geoColor
        }
      },
      dataLabels: {
        enabled: true,
        format: '{point.name}'
      },
      point: {
        events: {
          click: (event) => {
            this.props.handleZoneChange(event.point);

            this.setState({
              selectedPoint: event.point
            });

            console.log("Click")
            console.log(this.state.selectedPoint)
            console.log("---")

          }
        }
      }
    };

    // Add the new drilldown serie
    this.internalChart.addSeriesAsDrilldown(point, drilldownSerie);

  } catch (err) {
    console.log(err.message)
  }
}).catch((err) => {
  console.log(err.message)
})

EDIT 3:

Here is full code, if were necessary.

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import Highcharts from 'highcharts'
import HC_map from 'highcharts/modules/map'; //module
import HC_drilldown from 'highcharts/modules/drilldown'; //module
import HighchartsReact from 'highcharts-react-official';
import Button from '@material-ui/core/Button';
import worldMap, { dataWorldMap } from "./maps/worldMap.js";

HC_map(Highcharts); //init module
HC_drilldown(Highcharts) //init module

Highcharts.setOptions({
lang: {
    drillUpText: '◁ Volver a: {series.name}'
}
});

class GeoZoneChart extends Component {

constructor(props) {
    super(props);

    this.state = {
    loading: false,
    selectedPoint: ''
    };
}

geoZoneOptions = {
    chart: {
    map: worldMap,
    events: {
        drilldown: (event) => {
        this.handleMapChange(event.point);
        },
        drillup: (event) => {

        setTimeout(() => {
            console.log("DU")
            console.log(this.state.selectedPoint)
            console.log("---")
        }, 1000)

        },
    }
    },
    title: {
    text: ""
    },
    mapNavigation: {
    enabled: true,
    buttonOptions: {
        verticalAlign: 'bottom'
    }
    },
    colorAxis: {
    min: 0,
    max: 0,
    minColor: "#f7f7f7",
    maxColor: "#e2e2e2"
    },
    tooltip: {
    enabled: false
    },
    legend: {
    enabled: false
    },
    exporting: { enabled: false },
    series: [{
    mapData: worldMap,
    data: dataWorldMap,
    joinBy: 'hc-key',
    name: 'Mundo',
    allowPointSelect: true,
    borderColor: '#ffffff',
    borderWidth: 1.2,
    states: {
        hover: {
        color: this.props.geoColor
        },
        select: {
        color: this.props.geoColor
        }
    },
    dataLabels: {
        enabled: true,
        format: '{point.name}'
    },
    }, {
    name: 'Separators',
    type: 'mapline',
    color: 'silver',
    showInLegend: false,
    enableMouseTracking: false
    }],
    drilldown: {
    activeDataLabelStyle: {
        textDecoration: 'none',
        color: 'black'
    },
    series: []
    }
}

internalChart = undefined;

handleMapChange = (point) => {

    // Import all map data
    import(`./maps/${point['hc-key']}-all.geo`).then(mapData => {

    // Set data of the map
    var data = [];
    mapData.default.features.forEach((element, i) => {
        data.push({ 'hc-key': element.properties['hc-key'], 'name': element.properties.name, 'value': 0 });
    });

    // Create the new drilldown serie
    try {
        var drilldownSerie = {
        id: point['hc-key'],
        mapData: mapData.default,
        data: data,
        joinBy: 'hc-key',
        name: mapData.default.title,
        allowPointSelect: true,
        borderColor: '#ffffff',
        borderWidth: 1.2,
        states: {
            hover: {
            color: this.props.geoColor
            },
            select: {
            color: this.props.geoColor
            }
        },
        dataLabels: {
            enabled: true,
            format: '{point.name}'
        },
        point: {
            events: {
            click: (event) => {
                this.props.handleZoneChange(event.point);

                this.setState({
                selectedPoint: event.point
                });

                console.log("Click")
                console.log(this.state.selectedPoint)
                console.log("---")

            }
            }
        }
        };

        // Add the new drilldown serie
        this.internalChart.addSeriesAsDrilldown(point, drilldownSerie);

        // Select all map 
        //this.selectAll();
    } catch (err) {
        console.log(err.message)
    }
    }).catch((err) => {
    console.log(err.message)
    })
}

componentDidMount = () => {
    // Recover and set selected zone if exist
    this.props.defaultZone && this.selectRegionByName(this.props.defaultZone)
}

selectRegionByName = (name) => {
    if (!name.includes(', ')) {
    // Find point in global map
    this.internalChart.series[0].data.forEach(data => {
        if (data.name === name) {
        // Select the point 
        data.select(true, true)
        }
    });
    } else {
    var names = name.split(', ');
    // Find point in countries map
    this.internalChart.series[0].data.forEach(data => {
        if (data.name === names[1]) {
        // Drilldown on the map
        data.doDrilldown();
        setTimeout(() => {
            this.internalChart.series[0].data.forEach(dataInside => {
            if (dataInside.name === names[0]) {

                // Select the point
                dataInside.select(true, true)
            }
            });
        }, 100)
        }
    });
    }
}

afterChartCreated = (chart) => {
    this.internalChart = chart;
}

selectAll = () => {
    this.internalChart.series[0].data.forEach(data => {
    data.select(true, true);
    });

    this.props.handleSelectAllZones(this.internalChart.series[0].name);
}

componentWillUnmount = () => {
    this.internalChart.series[0].data.forEach(data => {
    data.select(false, false);
    });
}

render() {
    return (
    <Fragment>
        <HighchartsReact
        highcharts={Highcharts}
        constructorType={'mapChart'}
        options={this.geoZoneOptions}
        callback={this.afterChartCreated}
        />

        <Button
        variant="contained"
        color="primary"
        onClick={this.selectAll}
        style={{
            marginTop: -28,
            padding: 0,
            paddingLeft: 10,
            paddingRight: 10,
            float: "right",
            backgroundColor: this.props.geoColor,
            '&:hover': {
            backgroundColor: this.props.geoDarkColor
            }
        }}
        >
        Seleccionar todo
        </Button>
    </Fragment >
    );
}
}

GeoZoneChart.propTypes = {
handleZoneChange: PropTypes.func
};

export default GeoZoneChart;

EDIT 4:

I want to achieve that a code were executed after doDrilldown(). My problem is that when I do a drilldown on a point (point.doDrilldown()) the code loads a map async but the code continue executing (the map is not loaded yet) and fails (if I do not use a setTimeout). So I need wait that doDrilldown() ends, load async map ends, and then, continue executing the code.

The code of @WojciechChmiel (modified, I have added async load but it is not working) I have been trying for achieving that is:

// @WojciechChmiel function modified (function(H) { H.Point.prototype.doDrilldown = function( _holdRedraw, category, originalEvent ) { var series = this.series, chart = series.chart, drilldown = chart.options.drilldown, i = (drilldown.series || []).length, seriesOptions;

  if (!chart.ddDupes) {
    chart.ddDupes = [];
  }

  while (i-- && !seriesOptions) {
    if (
      drilldown.series[i].id === this.drilldown &&
      chart.ddDupes.indexOf(this.drilldown) === -1
    ) {
      seriesOptions = drilldown.series[i];
      chart.ddDupes.push(this.drilldown);
    }
  }

  // Fire the event. If seriesOptions is undefined, the implementer can check
  // for  seriesOptions, and call addSeriesAsDrilldown async if necessary.
  H.fireEvent(chart, 'drilldown', {
    point: this,
    seriesOptions: seriesOptions,
    category: category,
    originalEvent: originalEvent,
    points: (
      category !== undefined &&
      this.series.xAxis.getDDPoints(category).slice(0)
    )
  }, function(e) {
    var chart = e.point.series && e.point.series.chart,
      seriesOptions = e.seriesOptions;

    if (chart && seriesOptions) {
      if (_holdRedraw) {
        chart.addSingleSeriesAsDrilldown(e.point, seriesOptions);
      } else {
        chart.addSeriesAsDrilldown(e.point, seriesOptions);
      }
    }
    // My code should go here?
    else {
      console.log(e.point)
      // Import all map data
      import(`./maps/${e.point['hc-key']}-all.geo`)
      .then(mapData => {

          // Set data of the map
          var data = [];
          mapData.default.features.forEach((element, i) => {
          data.push({ 'hc-key': element.properties['hc-key'], 'name': element.properties.name, 'value': 0 });
          });

          // Create the new drilldown serie
          try {
              var drilldownSerie = {
                  id: e.point['hc-key'],
                  mapData: mapData.default,
                  data: data,
                  joinBy: 'hc-key',
                  name: mapData.default.title,
                  allowPointSelect: true,
                  borderColor: '#ffffff',
                  borderWidth: 1.2,
                  states: {
                  hover: {
                      color: this.props.geoColor
                  },
                  select: {
                      color: this.props.geoColor
                  }
                  },
                  dataLabels: {
                  enabled: true,
                  format: '{point.name}'
                  },
                  point: {
                  events: {
                      click: (event) => {
                      this.props.handleZoneChange(event.point);

                      this.setState({
                          selectedPoint: event.point['hc-key']
                      });
                      }
                  }
              }
          };

          // Add the new drilldown serie
          this.internalChart.addSeriesAsDrilldown(e.point, drilldownSerie);

          // Select all map 
          //this.selectAll();
          } catch (err) {
          console.log(err.message)
          }
      }).catch((err) => {
          console.log(err.message)
      })
    }
  });

      console.log('After drilldown');
}
})(Highcharts);

EDIT 5:

This is the main problem:

this.internalChart.series[0].data.forEach(data => { // FIND A POINT IN ALL MAP
    if (data.name === SelectedName1) { // IF A POINT IS FOUND...
        data.doDrilldown() // DRILLDOWN TO CHILD MAP
        // HERE I MUST WAIT. WHEN DODRILLDOWN ENDS, CONTINUE WITH BELOW CODE
        // UNDERSTANDING "ENDS" WHEN NEW MAP AND ITS DATA IS LOADED AND READY
        this.internalChart.options.drilldown.series[0].data.forEach(dataInside => { // FOR EACH POINT OF NEW MAP (DRILLDOWN/CHILD MAP)
            if (dataInside.name === SelectedName2) { // IF A POINT IS FOUND
                this.setState({ province: dataInside['hc-key'] }); // CHANGE STATE
            }
        });
    }
});
JuMoGar
  • 1,740
  • 2
  • 19
  • 46
  • Is a mess to read this code if you can provide us some example of what all variable are it could be more easy to help you – Gianmarco May 05 '19 at 14:23
  • @Gianmarco No problem. I have done this JSFiddle which represents my problem: https://jsfiddle.net/bo9h0zxg/4/ . Remember that I can't modify `doDrilldown()` function. – JuMoGar May 05 '19 at 15:31
  • I have updated the JSFiddle: https://jsfiddle.net/x7uzb85o/ – JuMoGar May 05 '19 at 15:45
  • 1
    There's a section about async drilldown in highcharts docs: https://www.highcharts.com/docs/chart-concepts/drilldown Maybe it can help – mikheevm May 05 '19 at 15:55
  • Thank you, but this is for adding dynamics drilldowns. I can't help me in this chase :( . The problem is that I must wait for the drilldown (or any other action) finish before continue executing code. In the JSFiddle can be seen the real problem. – JuMoGar May 05 '19 at 16:00
  • @Bergi, I have seen this question too, but this not solves my problem... Please, unblock it – JuMoGar May 05 '19 at 16:21
  • So your question is not really about any of the loops or how to use `async`/`await`, but how to know when `data.doDrilldown()` finishes? Have you read (and can you link) its documentation? Does it take any callbacks, does it return a promise? – Bergi May 05 '19 at 16:29
  • I need a wrapper of `data.doDrilldown()` which returns a promise and inside then execute rest of code ( I think that it could be the solution). Maybe other solution is the same wrapper using async/await but I do not know how to achieve that, I have been trying many ways. My necesity is know when `data.doDrilldown()` finish. This function does not appear in Higcharts documentation (I have been searching it). I saw this function on a forum and no return a promise itself. – JuMoGar May 05 '19 at 16:36

1 Answers1

0

Here is the wrapper of H.Point.prototype.doDrilldown:

(function(H) {
  H.Point.prototype.doDrilldown = function(
    _holdRedraw,
    category,
    originalEvent
  ) {
    var series = this.series,
      chart = series.chart,
      drilldown = chart.options.drilldown,
      i = (drilldown.series || []).length,
      seriesOptions;

    if (!chart.ddDupes) {
      chart.ddDupes = [];
    }

    while (i-- && !seriesOptions) {
      if (
        drilldown.series[i].id === this.drilldown &&
        chart.ddDupes.indexOf(this.drilldown) === -1
      ) {
        seriesOptions = drilldown.series[i];
        chart.ddDupes.push(this.drilldown);
      }
    }

    // Fire the event. If seriesOptions is undefined, the implementer can check
    // for  seriesOptions, and call addSeriesAsDrilldown async if necessary.
    H.fireEvent(chart, 'drilldown', {
      point: this,
      seriesOptions: seriesOptions,
      category: category,
      originalEvent: originalEvent,
      points: (
        category !== undefined &&
        this.series.xAxis.getDDPoints(category).slice(0)
      )
    }, function(e) {
      var chart = e.point.series && e.point.series.chart,
        seriesOptions = e.seriesOptions;

      if (chart && seriesOptions) {
        if (_holdRedraw) {
          chart.addSingleSeriesAsDrilldown(e.point, seriesOptions);
        } else {
          chart.addSeriesAsDrilldown(e.point, seriesOptions);
        }
      }
    });

        console.log('After drilldown');
  }
})(Highcharts);

As you can see this function is synchronous. If you use it to add data asynchronously please add an example that reproduces it.

Wojciech Chmiel
  • 7,302
  • 1
  • 7
  • 16
  • Ok, thank you. Yes I am loading data asynchronously, I will update the question adding that. Could you explain a bit your code? I do not understand whats is doing the code. Thank you again. – JuMoGar May 07 '19 at 11:27
  • Ready. Question updated with the asynchronous loading of drilldowns – JuMoGar May 07 '19 at 11:32
  • I re-updated the question adding a Edit 3 with full code due to if it was necessary – JuMoGar May 07 '19 at 11:37
  • I have been trying your code inserting my own async load (edit 2) but I do not achieve do it works. It is suppossed I shoul include the async load in the last if (creating an else clause in this if: `if (chart && seriesOptions)`)? – JuMoGar May 08 '19 at 08:05
  • Ok, this is a wrapper of `doDrilldown` method. You can add your logic at the end of the method code so that you will know when it is finished or wrap its code with Promise that will be returned. Could you explain to me more precisely what do you want to achieve? Perhaps you can reproduce it in codesandbox as a simplified piece of react app? You can use this example as a template: https://codesandbox.io/s/4r57245nw7 – Wojciech Chmiel May 08 '19 at 11:08
  • Sorry. I have been trying to convert my logic to the JSFiddle but I can't, It is not easy due to I load async locals JS and I do not know how to use Highcharts API. Anyway, I have updated the question (Update 4) where I specify my problem, what I want to achieve and how I am using your code. Hope this helps. And thank you again for your effort. – JuMoGar May 09 '19 at 07:55
  • You can simplify the app - use only the data and code that is important in this issue. Instead of loading data from api you can use setTimeout to imitate this behavior. As I posted you earlier the `doDrilldown()` method is synchronous and that's why it is hard to understand what exactly you're trying to achieve. Update 4 will work the same if you just add this piece of code after invoking `doDrilldown()`. – Wojciech Chmiel May 09 '19 at 10:10
  • The main trouble is know when `doDrilldown()` finish (understanding by "finish" when new map and its data iis loaded). So I need to wait before all is loading before continue executing code. In the main question you can see this in the first piece of code (It is solved by a `setTimeout`). I will update again the question with a simplified piece of code commented where is the problem. – JuMoGar May 09 '19 at 12:00
  • Ok, so in your last edit code, I've noticed that you are invoking `setState()` method on the data array and not point object. To get point you can loop through `series.points` which are updated after drilldown. Check this simplified demo with columns drilldown: https://jsfiddle.net/BlackLabel/tf9uwmgj/ – Wojciech Chmiel May 10 '19 at 07:39
  • Yes. I have read this code example before asking the question, But it is not helpfull, I continue with the problem. Thank you again for your help and time – JuMoGar May 14 '19 at 08:19