appreciated/apexcharts-flow

Adding formatter to Donut/Pie Chart label causes JavaScript Error

Closed this issue · 4 comments

Describe the bug
Adding a formatter via com.github.appreciated.apexcharts.config.plotoptions.pie.builder.ValueBuilder.withFormatter(String) causes JavaScript error "TypeError: (0 , t.value.formatter) is not a function".

Steps or code example to Reproduce
Sample Code:

private static Labels buildLabels() {		
	
	final String formatter = 	"function doSomeJavaScript(x) { " + 
						"    return x.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");" + 
						"}";
	
	return LabelsBuilder.get()
						.withShow(true)
						.withName(NameBuilder	.get()
										.withColor(ColourCodes.BLACK)
										.withFontSize(LABEL_FONT_PX)
										.build())
						.withValue(ValueBuilder	.get()													
										.withFormatter(formatter)
										.build())
						.build();
}

Expected behavior
A formatted number should appear in the middle of the pie chart e.g. 1,000,000

Screenshots
image

I have the same issue while trying to set a formatter on the total label of donut chart.
I am setting the following formatter:

function(w) {              
  w.globals.seriesTotals.reduce((a, b) => a + b, 0);               
} 

I think the problem here is that the Java wrapper sends the formatter value as a String while JavaScript side expects a function.

I don't think it's possible to send functions via JSON directly since JSON is not supposed to transfer functions: https://stackoverflow.com/questions/2001449/is-it-valid-to-define-functions-in-json-results

A workaround is needed: e.g. you'll need to attach the functions beforehand, say to the window object:

window.formatLabelTotal = function() { ... }

Then the wrapper code would accept the formatter Strings as function names, such as window.formatLabelTotal. However this needs to be implemented.

I am able to set a formatter on other chart/label and it works.
For example, I do this

private val jvmMemoryChart = ApexChartsBuilder.get()
            .withChart(ChartBuilder.get()
                .withType(Type.bar)
                .build())
            .withPlotOptions(PlotOptionsBuilder.get()
                    .withBar(BarBuilder.get()
                            .withHorizontal(false)
                            .withColumnWidth("55%")
                            .build())
                    .build())
            .withTitle(TitleSubtitleBuilder.get().withText("JVM Memory").build())
            .withDataLabels(DataLabelsBuilder.get()
                    .withEnabled(true)
                    .withFormatter(jvmMemFormatterJsFucntion)
                    .build())
[...]

With this formatter string:

private val jvmMemFormatterJsFucntion =
          """
          function(val, {seriesIndex, dataPointIndex, w}) {              
              return (parseFloat(val).toFixed(2)) + " kB";               
          }    
          """.trimIndent()

When digging in the wrapper's JS code, this snippet must be the reason it works:

if (this.dataLabels) {
  this.config.dataLabels = JSON.parse(this.dataLabels);
  if (this.config.dataLabels.formatter) {
    this.config.dataLabels.formatter = this.evalFunction(this.config.dataLabels.formatter);
  }
}

The evalFunction() does the trick.

Also, by looking at the wrapper's JS code, I don't see anywhere that it handles the plotOptions Pie/Donut label configurations and evalutate the formatter's string. It is just not implemented for all types of label.

Excellent find! I can indeed see at apexcharts-wrapper.js:175 that the plotOptions formatter for pie/donut is not post-processed. Perhaps something like this should be in place:

if (this.plotOptions.pie && this.plotOptions.pie.donut && this.plotOptions.pie.donut.labels && this.plotOptions.pie.donut.labels.total && this.plotOptions.pie.donut.labels.total.formatter) {
  this.plotOptions.pie.donut.labels.total.formatter = this.evalFunction(this.plotOptions.pie.donut.labels.total.formatter);
}

(the same for this.plotOptions.pie.donut.labels.value).