Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

ANDROID: Charts don't render at seemingly random times

Hello,

I'm creating charts (HIChartView) DialogFragment, and then I add them with addView() inside a scrollable LinearLayout dynamically, depending on how many graphs need to be plotted. My main issue is that there are times where graphs simply wont be there, but if I close the dialog and reopen it, they will be there, and might some other graph disappear. And there are times where everything shows up as expected.

This seems to happen at random.

Here is some examples of how the charts are initialized as Views and getting ready to be configured:

Code: Select all

final View chartLayoutView = getLayoutInflater().from(getContext()).inflate(R.layout.chart_constraint_layout, linearLayout, false);
HIChartView chartView = (HIChartView) chartLayoutView.findViewById(R.id.chart);

linearLayout.addView(chartLayoutView, dynamicPos);//if  there are already graphs inside linearLayout and it is required for the graph to be in specific order,  //otherwise dynamicPos wont be used as parameter when I use addView.
This is similar to the methods that is being called to configure the graph and insert any necessary data for its creation(Note: we add fonts in styles aswell):

Code: Select all

public static void initializeChart(HIChartView hiChartView, ArrayList<Float> maxMinArrayList, ArrayList<HIXAxis> hixAxisList,
                                          Map<String, Double> chartPlotLines, ArrayList<HIPlotBands> plotBands, ArrayList<Number> tickPositions,
                                          ArrayList<HISeries> hiSeriesList, Map<String, String> chartTexts) {

        if (hiChartView.getOptions() == null) {
            HIOptions options = new HIOptions();
            HIChart chart = new HIChart();
            chart.setZoomType("xy");
            chart.setMarginRight(10);
            chart.setMarginLeft(10);
            options.setChart(chart);

            HICredits credits = new HICredits();
            credits.setEnabled(false);
            options.setCredits(credits);

            HIExporting hiExporting = new HIExporting();
            hiExporting.setEnabled(false);
            options.setExporting(hiExporting);

            HITitle title = new HITitle();
            title.setText("");
            options.setTitle(title);

            options.setXAxis(hixAxisList);
            options.setYAxis(initializeSingleTrendingYAxis(maxMinArrayList, chartPlotLines, plotBands, tickPositions, chartTexts.get("ChartSideTitle"), chartTexts.get("isInfinitePlotBands"), chartTexts));

            HITooltip tooltip = new HITooltip();
           //......configuration of tooltip
            options.setTooltip(tooltip);

            HILegend legend = new HILegend();
            //......configuration of legend

            hiSeriesList.addAll(createLegendLines());//... passing some parameters it returns an ArrayList of HILine type
            options.setLegend(legend);

            options.setSeries(hiSeriesList);

            hiChartView.setOptions(options);
            hiChartView.update(options, true, true);
        } else {
            updateTrending(hiChartView, maxMinArrayList, hiSeriesList, graphPlotLines, plotBands, tickPositions, graphTexts);
        }
    }

yaxis initialization:

Code: Select all

public static ArrayList<HIYAxis> initializeSingleTrendingYAxis(ArrayList<Float> maxMinArrayList, Map<String, Double> plotLines,
                                                                   ArrayList<HIPlotBands> plotBands, ArrayList<Number> tickPositions, String title, Map<String, String> graphTexts) {
        ArrayList<HIPlotLines> plotLines = new ArrayList<>();

        HIYAxis yaxis = new HIYAxis();
        yaxis.setTitle(new HITitle());
        yaxis.getTitle().setText(title);
        yaxis.getTitle().setRotation(0);
        yaxis.getTitle().setOffset(40);
       
        yaxis.getTitle().setY(-20);
        yaxis.getTitle().setAlign("high");
        yaxis.getTitle().setStyle(new HICSSObject());
        yaxis.getTitle().getStyle().setColor("2E3040");
        yaxis.getTitle().getStyle().setFontSize("13");
        yaxis.getTitle().setTextAlign("left");

        yaxis.setLineWidth(2);
        yaxis.setLineColor(HIColor.initWithHexValue("E6E6E6"));
        yaxis.setTickColor(HIColor.initWithHexValue("E6E6E6"));
        yaxis.setVisible(true);

        float maxValue;
        float minValue;
        if (!maxMinArrayList.isEmpty()) {
            maxValue = maxMinArrayList.get(1);
            minValue= maxMinArrayList.get(0);
            yaxis.setPlotBands(createPlotBandsRange(maxMinArrayList.get(1), maxMinArrayList.get(0), plotBands, isInfinitePlotBands));
            if (null != tickPositions && !tickPositions.isEmpty()) {
                yaxis.setTickPositions(tickPositions);
                yaxis.setEndOnTick(false);
            }
            yaxis.setSoftMin(minValue);
            yaxis.setSoftMax(maxValue);
        }

        yaxis.setStartOnTick(false);
        yaxis.setGridLineWidth(1);
        yaxis.setGridLineColor(HIColor.initWithHexValue("D9DADB"));

        HILabels labels = new HILabels();
        //....configuration of labels
        yaxis.setLabels(labels);
        yaxis.setMinorGridLineWidth(0);
        yaxis.setMinorTickInterval(0.5);
        yaxis.setMinorTickLength(5);
        yaxis.setMinorTickWidth(1);
        yaxis.setMinorTickColor(HIColor.initWithHexValue("ffffff"));
        HICrosshair crosshair = new HICrosshair();
        crosshair.setWidth(3);
        yaxis.setCrosshair(crosshair);

        plotLines.addAll(addAdditionalPlotLines(plotLines)); //returns an ArrayList of HIPlotLines

        yaxis.setPlotLines(plotLines);

        return new ArrayList<>(Collections.singletonList(yaxis));
    }
The issue appears at the first initialization so there is no reason to place the "else code where update of the chart is happening (in which I am having issues as well, but that will be discussed in another submission).

If more information/parts of missing code is necessary, please ask.
Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

Re: ANDROID: Charts don't render at seemingly random times

My question is how can I solve this, I am not just reporting this behaviour.
User avatar
sebastian.h
Posts: 1734
Joined: Fri Aug 07, 2020 7:08 am

Re: ANDROID: Charts don't render at seemingly random times

Hi,
Welcome to our forum and thanks for contacting us with your question!

We process your message as soon as possible, with our politic of response with 24 hours on business days.

I back with information from our android developer, they created a similar project using your code and were unable to reproduce it.
Could you send a working demo in the online code editor? That helps us better diagnose your case.

About your problem, they recommend using RecyclerView.

Keep me in the loop on how are you going with this.
Best regards.
Sebastian Hajdus
Highcharts Developer
Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

Re: ANDROID: Charts don't render at seemingly random times

Hello,

Thank you for your quick response, I replaced linearlayout with recyclerview, but I am not sure if the problem is solved since the graphs won't render properly now.

RecyclerView code:

Code: Select all


public GraphRecyclerViewAdapter(BaseFragmentActivity activity, List<String> graphNameList, OnDisplay displayListener) {
        this.graphNameList = graphNameList;
        this.activity = activity;
        this.displayListener= displayListener;
    }

    public interface OnDisplay {
        void OnDisplay (View trendingLayoutView, String graphName);
    }

    protected class ViewHolder extends RecyclerView.ViewHolder {

        View layoutView;
        HIChartView chartView;
        TextView labelOne;
        TextView labelTwo;

        protected ViewHolder(View itemView) {
            super(itemView);
            chartView = (HIChartView) itemView.findViewById(R.id.chartView);
            layoutView = itemView;
            labelOne= ((ViewGroup) trendingChartView.getParent()).findViewById(R.id.labelOne);
            labelTwo= ((ViewGroup) trendingChartView.getParent()).findViewById(R.id.labelTwo);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {

        viewHolder.labelOne.setVisibility(View.VISIBLE);
        viewHolder.labelTwo.setVisibility(View.VISIBLE);
        if(null != viewHolder.chartView.getOptions()) {
            viewHolder.chartView.destroy();
            viewHolder.chartView.setOptions(new HIOptions());
            //viewHolder.chartView.redraw();
            
        }
        displayListener.OnDisplay (viewHolder.layoutView, graphNameList.get(position));

    }

    @NonNull
    @Override
    public GraphRecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view;
        view = LayoutInflater.from(unwrap(parent.getContext())).inflate(R.layout.chart_layout, parent, false);
        return new GraphViewHolder(view);
    }

    private static Activity unwrap(Context context) {
        while (!(context instanceof Activity) && context instanceof ContextWrapper) {
            context = ((ContextWrapper) context).getBaseContext();
        }

        return (Activity) context;
    }


This is the basic functionality code for the RecyclerView. Whenever a ViewHolder is created, I'll take some elements and the HIChartView element and store them inside the ViewHolder . When a ViewHolder is binded, I'll try to reset the chart by executing the part of code inside the if statement within onBindViewHolder, but the end result is that the graph is not rendered at all.

The reason I am trying to reset the entire chart is because, as you can see in the first code snippet I shared, I have two functionalities within the chart generation;
- first generation, where all the configurations for the HIOption is made
- update existing chart, in which I update with new values the chart components such as HIyAxis, HISeries etc.

When having a RecyclerView, the ViewHolders get reused, so the layouts and the charts, after a scroll, will be reused. That means that the chart will already be configured and it will just try to update the current one, when it actually needs proper reconfiguration(clear old, set new). So I tried to clear chart options when I am binding the layout but I wasn't able to achieve it.

My main question is how can I reset the chart(clear configurations and use initializeChart method to rebuild them)?

As for the demo that was requested, I can only provide it in .apk form, and I would like to proceed with it in case this doesn't work.

Note: Since the graph generation method is called from where the RecyclerView's listener (OnDisplay) is implemented, what I initially did was to just pass the entire layout of the ViewHolder and edit its layout elements and the chart as well there. But that didn't work as I though it would, since the layout views were not properly updated and simply calling again the method used to configure the graph (initializeChart) didn't work obviously. I understand the need for clear on the chart, but it is unclear to me why the layout components won't update properly. Making them visible from within recyclerview worked, but from InitializeChart didn't.

Thank you in advance.
User avatar
sebastian.h
Posts: 1734
Joined: Fri Aug 07, 2020 7:08 am

Re: ANDROID: Charts don't render at seemingly random times

Hi,
I have another suggestion for you.

ad 1. It would be necessary to somehow set options in the onBindViewHolder method because for now you are setting an empty new HIOptions() object so that nothing will be drawn (this condition is completely pointless because HIOptions are always null at the beginning.

ad 2. I suggest you make some "general" options in the adapter, and in the list that will be responsible for the number of data in the recyclerView, keep the data for individual charts (i.e. have a list of lists).

Best regards.
Sebastian Hajdus
Highcharts Developer
Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

Re: ANDROID: Charts don't render at seemingly random times

Hello,

Yes you are correct, the set of a newly made HIOptions doesn't make sense there, it was a test from my side to check something, and I didn't remove it for this answer. However I think I am currently doing this, and still get that behaviour. Implementing an interface, once the view is about to be shown (a.k.a when OnBindViewHolder gets called) I pass the viewHolder's layout as a parameter to the DialogFragment, where it handles the creation of the graph by finally calling the "initializeChart" method that we have already seen. As I understand, that is equal of passing the proper configuration onto the chart layout, once the OnBindViewHolder gets called. The way I made the charts reset for now was to destroy them, and after setting their configuration, I added "chart.reload()" which is a deprecated method, but it is the only thing that has worked so far for the reset. Even with this implementation the problem still occurs, with the difference that now you just have to scroll past the empty graph and once you come back it will most likely be shown (since the RecyclerView reloads it).

Is there a reason that I'm overlooking that the presented implementation is not the same as the one you suggested?

Currently inside RecyclerView:

Code: Select all


@Override
    public void onBindViewHolder(@NonNull GraphViewHolder viewHolder, int position) {
        if(null != viewHolder.chartView.getOptions()) {
            viewHolder.chartView.destroy();
        }
        displayListener.onDisplay(viewHolder.layoutView, graphNameList.get(position));
    }
User avatar
sebastian.h
Posts: 1734
Joined: Fri Aug 07, 2020 7:08 am

Re: ANDROID: Charts don't render at seemingly random times

Hi,
Thanks for the details.

Our developer suggests using the newest version of HC and has some tips for you.

You don't need an interface, no reloading charts to put them in the list.
These problems occur in old versions of Highcharts, there was indeed a problem with the recyclerviews that you describe but that was somehow in version 6, In subsequent versions this problem has been gone for a long time.

Below sample example project with activate and adapter.

Activity:

Code: Select all

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        ArrayList<ArrayList<Integer>> chartList = new ArrayList<>();
        chartList.add(new ArrayList<>(Arrays.asList(1, 5, 6, 7, 8, 4)));
        chartList.add(new ArrayList<>(Arrays.asList(4, 6, 7, 9, 1, 2)));
        chartList.add(new ArrayList<>(Arrays.asList(5, 6, 6, 8, 1, 7)));
        chartList.add(new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6)));
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new MyAdapter(chartList));
    }
}

Adapter:

Code: Select all

class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private ArrayList<ArrayList<Integer>> mValues;
    public MyAdapter(ArrayList<ArrayList<Integer>> mValues) {
        this.mValues = mValues;
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public HIChartView chartView;
        public ViewHolder(View v) {
            super(v);
            chartView = v.findViewById(R.id.hc_row);
        }
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.hc_rv_holder, parent, false);
        return new ViewHolder(itemView);
    }
    @Override
    public void onBindViewHolder(@NonNull MyAdapter.ViewHolder holder, int position) {
        HIOptions options = new HIOptions();
        options.setCredits(new HICredits());
        options.getCredits().setEnabled(false);
        options.setExporting(new HIExporting());
        options.getExporting().setEnabled(false);
        HIChart chart = new HIChart();
        chart.setType("column");
        options.setChart(chart);
        HITitle title = new HITitle();
        title.setText("Chart no. " + position);
        options.setTitle(title);
        HICredits credits = new HICredits();
        credits.setEnabled(false);
        options.setCredits(credits);
        HISeries series = new HISeries();
        series.setData(mValues.get(position));
        options.setSeries(new ArrayList<>(Collections.singletonList(series)));
        holder.chartView.setOptions(options);
    }
    @Override
    public int getItemCount() {
        return mValues.size();
    }
}
​If you have further inquiries, you may reach out to me at any time.
Best regards.
Sebastian Hajdus
Highcharts Developer
Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

Re: ANDROID: Charts don't render at seemingly random times

Hi,
I recently continued re-investigating the issue, so excuse the late response.

For example's sake, I tried to simplify the structure that we have for the creation of the charts to what you propose in this thread. Below I am posting the current code:

Inside recyclerview's adapter:
===============================

Code: Select all


public ChartAdapter( List<Map<String, Object>> configList) {
        this.configList = configList;
    }
    
    protected class MyViewHolder extends RecyclerView.ViewHolder {
        HIChartView trendingChartView;

        protected GraphViewHolder(View itemView) {
            super(itemView);
            trendingChartView = (HIChartView) itemView.findViewById(R.id.trendingChart);
        }
    }

@Override
    public void onBindViewHolder(@NonNull MyViewHolder viewHolder, int position) {
        initializeTrending(viewHolder.trendingChartView, position);
    }

InitializeTrending method:

Code: Select all

private void initializeTrending(HIChartView chartView, int configPosition) {

        Map<String, Object> configurationMap = configList.get(configPosition);
        Map<String, String> graphTexts = new HashMap<>((Map<String, String>) configurationMap.get("graphTexts"));
        ArrayList<HISeries> hiLinesList = new ArrayList<>((ArrayList<HISeries>) configurationMap.get("hiLinesList"));
        ArrayList<Float> graphYaxisMaxMix = new ArrayList<>((ArrayList<Float>) configurationMap.get("graphYaxisMaxMix"));
        ArrayList<HIXAxis> hiXaxis = (ArrayList<HIXAxis>) configurationMap.get("hiXaxis");
        Map<String, Double> hiPlotLines = (Map<String, Double>) configurationMap.get("hiPlotLines");
        ArrayList<HIPlotBands> hiPlotBands = new ArrayList<>((ArrayList<HIPlotBands>) configurationMap.get("hiPlotBands"));
        ArrayList<Number> graphTickPositions = (ArrayList<Number>) configurationMap.get("graphTickPositions");

        initializeTrendingTextFields(chartView, graphTexts, hiLinesList.get(0).getData().size());
        if (null != hiLinesList && !hiLinesList.isEmpty() && 0 < hiLinesList.get(0).getData().size()) {
            HIOptions options = new HIOptions();
            HIChart chart = new HIChart();
            chart.setZoomType("xy");
            chart.setMarginRight(38);
            chart.setMarginLeft(50);
            options.setChart(chart);

            HICredits credits = new HICredits();
            credits.setEnabled(false);
            options.setCredits(credits);

            options.setBoost(new HIBoost());
            options.getBoost().setSeriesThreshold(1);

            HIExporting hiExporting = new HIExporting();
            hiExporting.setEnabled(false);
            options.setExporting(hiExporting);

            HITitle title = new HITitle();
            title.setText("");
            options.setTitle(title);

            UtilitiesForChart.setMinMax(hiLinesList, graphYaxisMaxMix);
           
            options.setXAxis(hiXaxis);
            options.setYAxis(initializeYAxis(graphYaxisMaxMix, hiPlotLines, hiPlotBands, graphTickPositions, graphTexts.get("ChartSideTitle"), graphTexts.get("isInfinitePlotBands"), graphTexts));

           
            HITooltip tooltip = new HITooltip();
            tooltip.setPointFormat(configPosition + "{point.key}" + "\n" + hiLinesList.get(0).getName() + ": <b>{point.y}</b>");
            tooltip.getPointFormat();
            HICSSObject tooltipStyle = new HICSSObject();
            tooltipStyle.setColor("2E3040");
            tooltipStyle.setFontFamily("inter_medium");
            tooltipStyle.setFontSize("18");
            tooltip.setStyle(tooltipStyle);
            tooltip.setShared(false);
            tooltip.setBorderColor(HIColor.initWithHexValue("D9DADB"));
            tooltip.setBorderRadius(5);
            tooltip.setUseHTML(true);
            tooltip.setEnabled(true);
            tooltip.setShape("none");
            options.setTooltip(tooltip);

            HILegend legend = new HILegend();
            legend.setLayout("horizontal");
            legend.setAlign("center");
            legend.setVerticalAlign("top");
            legend.setSymbolWidth(30);
            legend.setFloating(false);
            HICSSObject legendStyle = new HICSSObject();
            legendStyle.setFontWeight("normal");
            legendStyle.setFontSize("13");
            legendStyle.setFontFamily("inter_semi_bold");

            hiLinesList.addAll(createLegend(chartView.getContext(), hiPlotLines, graphYaxisMaxMix, configPosition));

            legend.setItemStyle(legendStyle);
            legend.setBackgroundColor(HIColor.initWithHexValue("00000000"));
            options.setLegend(legend);

            options.setSeries(hiLinesList);
            chartView.addFont(R.font.inter_medium);
            chartView.addFont(R.font.inter_semi_bold);

            HIPlotOptions plotOptions = new HIPlotOptions();
            plotOptions.setSeries(new HISeries());
            plotOptions.getSeries().setPoint(new HIPoint());
            plotOptions.getSeries().setTooltip(tooltip);
            plotOptions.getSeries().getPoint().setEvents(new HIEvents());
            plotOptions.getSeries().getPoint().getEvents().setClick(new HIFunction(
                    "function () { " +
//                        "if (this.series.data.length > 1) { " +
//                        "this.remove(); " +
//                        "}" +
                            "}"));
            options.setPlotOptions(plotOptions);
            chartView.setOptions(options);
            chartView.update(options, true, true);
            //hiChartView.reload();
        }
    }
===============================

Few clarifications:
I have taken the simplest approach where I'm loading charts based on the configuration list passed in the adapter as suggested. Besides the recreation of chart during onBindViewHolder() method, there are no buttons that trigger updates to the displaying graphs. New "options" object is being created each time a chart is created and added to the layout and the ".reload()." method was removed aswell, to try to get as close to the example as possible. I have removed the interface for this practice and initializeTrending method is inside the adapter for the example's sake. There are some external methods that are being imported and used by initializeTrending but I never thought that could be an issue. If it is please let me know and ill add them inside the adapter to check.

To separate graphs from one another, I insert as legend inside every chart the position of the viewHolder (Since I'm using the same dummy data set for every chart). Without this line "chartView.update(options, true, true);", what would happen is that legends would count up to 5 position, but the next graph would show 0 as position, and the next after 1. So viewHolders would get recycled without the charts having any updates. After placing the line, legend was showing the positions correctly.

The problem is still there, even with this simple approach it was reproduced once again. The only difference is that since no ".reload()" method is used, when the rendering issue occurs, that specific viewHolder that contains the chart layout will never show a chart, even when it gets recycled. I have to re-initialize the adapter to make it go away.

If more information is needed please ask.

Thank you
User avatar
sebastian.h
Posts: 1734
Joined: Fri Aug 07, 2020 7:08 am

Re: ANDROID: Charts don't render at seemingly random times

Hi,
I received information from Android developer.

This looks like a standard problem with recycleviews, you can always try to use the method on the adapter setHasStableIds(true) and override the method getItemId in the adapter and return some unique value there, e.g. items.get(i).hashCode()

Best regards.
Sebastian Hajdus
Highcharts Developer
Iraklis
Posts: 7
Joined: Thu Feb 25, 2021 1:02 pm

Re: ANDROID: Charts don't render at seemingly random times

Did that in adapter:

Code: Select all

@Override
    public long getItemId(int position) {
        return configList.get(position).hashCode();
    }
On fragment just before the adapter is set to the recyclerview:

Code: Select all

chartAdapter.setHasStableIds(true);
Nothing changed.
User avatar
sebastian.h
Posts: 1734
Joined: Fri Aug 07, 2020 7:08 am

Re: ANDROID: Charts don't render at seemingly random times

Hi,
I had information that other support channel will solving your case.

Best regards.
Sebastian Hajdus
Highcharts Developer

Return to “Highcharts Usage”