apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60.28k stars 19.61k forks source link

[Bug] X-axis label formatter doesn't support callback even it should according to the type #20027

Open yl-endress opened 3 months ago

yl-endress commented 3 months ago

Version

5.5.5

Link to Minimal Reproduction

https://echarts.apache.org/examples/en/editor.html?code=PYBwLglsB2AEC8sDeAoWsAeBBDEDOAXMmurGAJ4gCmRA5JALZW0A0J6AhrngDIcBGVADZFUpWADNgAJwYcwYKtNHtS5Kh2WwAFADcOQgK5UWsCNAAmVDAEoEAPlj6jVWAGpYAImTlf5AL6ebOLoDDBgABZEegbGpuZWtg5Osa4enkgAstmBwSEWHOTRznFmltZ28I4laV5IFv4AdMjZmbmq6BHAhloxLvHlSVUpLu51ABLj_gRIDAz-ADrQ9U0tOUEdsAzmhorFqQOJldWpYxmT07PzSyvNWet54nhUAMYwFvv9ZUfJNWdIFxmc0ueDwi2WDTurXaIS2ECEQnwr3en1KCQqv1O6SQEQil2BM1B_mQAGUyeDbms2htxP5VHT0P48uQcPgVOIKNQ6DVaCQmSRntIIFRCLAANqqMT5eQcIgS2HoMWeABMAAZlQAWAC0qoAbFqAIwAZgAKqqDQQAKwAdgIeoAWkFYAbLaqALqmTaKlXq7V6w2m9VW20Op3Ko3uz0K8U-zU6_XGs1G4N23WO0zKzUe2BemNquP-xOqjUp0MZg0ADmzuaV-b9CcDltLaadxstHprsfrAbNuub6edGut1ejtd98Z7qttNtTA-VuvduY7sM5NFgtER0GY9JIHZQ_gA3EA

Steps to Reproduce

Try to use a formatter function for the xAxisLabel, like the following:

  xAxis: {
    type: 'time',
    axisLabel: {
    formatter: {
      year: (value, index) => value + " {yyyy}",
      month: (value, index) => value + "{MMM}",
      day: (value, index) => value + "{d}. {MMM}",
      hour: (value, index) => value + "{HH}:{mm}\n{d}. {MMM}",
      minute: (value, index) => value + "{HH}:{mm}\n{d}. {MMM}",
      second: (value, index) => value + "{HH}:{mm}:{ss}\n{d}. {MMM}",
      millisecond: (value, index) => value + "{hh}:{mm}:{ss} {SSS}\n{d}. {MMM}",
    }
    }
  },

The declared type "AxisLabelValueFormatter" cannot be used for xAxis of type time, as an internal error occurs. image

The function is called by the leveledFormat(...). It checks on high-level for function calls (see line 138 in following screenshot), but not inside the object itself. image

Eventually, the error happens in the format functionality, as a string is expected and not a function. image

My suggestion is to check if the given "time" parameter is a function, and than call the function, else as it is:

//Current:
  return (template || '').replace(/{yyyy}/g, y + '').replace(/{yy}/g, pad(y % 100 + '', 2)).replace(/{Q}/g, q + '').replace(/{MMMM}/g, month[M - 1]).replace(/{MMM}/g, monthAbbr[M - 1]).replace(/{MM}/g, pad(M, 2)).replace(/{M}/g, M + '').replace(/{dd}/g, pad(d, 2)).replace(/{d}/g, d + '').replace(/{eeee}/g, dayOfWeek[e]).replace(/{ee}/g, dayOfWeekAbbr[e]).replace(/{e}/g, e + '').replace(/{HH}/g, pad(H, 2)).replace(/{H}/g, H + '').replace(/{hh}/g, pad(h + '', 2)).replace(/{h}/g, h + '').replace(/{mm}/g, pad(m, 2)).replace(/{m}/g, m + '').replace(/{ss}/g, pad(s, 2)).replace(/{s}/g, s + '').replace(/{SSS}/g, pad(S, 3)).replace(/{S}/g, S + '');

//Suggestion
  return ((typeof template ==='function' ? template() : template) || '').replace(/{yyyy}/g, y + '').replace(....);

Current Behavior

Empty x-axis is displayed

image

Expected Behavior

X-axis gets formatted according to the given functionality

Environment

- OS:
- Browser: Version 125.0.6422.142 (Official Build) (64-bit)
- Framework:

Any additional comments?

Idea is to resolve with this the not supported timezone problem, by simple using specific formatters

yl-endress commented 3 months ago

For everyone else who is having issues to format the x-axis in a specific timezone, I found a solution as a workaround:

Use your own formatter function by using the way how it's written in the documentation.

   ...
   axisLabel: {
      formatter: (value) => {
          return this.getFormattedUnitFromValue(value);
      }
   }  
   ...

Finally extract the functionality of https://github.com/apache/echarts/blob/master/src/util/time.ts#L223C3-L223C31 to your needs, like the following:

 private getFormattedUnitFromValue(
    value: number | string | Date,
  ): string {
    // The following code is kind of a copy to distinquish which format should be used
    const isUTC: boolean = this.useUtc;
    const date: Date = new Date(value);
    const M: number = (isUTC ? date.getUTCMonth() : date.getMonth()) + 1;
    const d: number = isUTC ? date.getUTCDate() : date.getDate();
    const h: number = isUTC ? date.getUTCHours() : date.getHours();
    const m: number = isUTC ? date.getUTCMinutes() : date.getMinutes();
    const s: number = isUTC ? date.getUTCSeconds() : date.getSeconds();
    const S: number = isUTC ? date.getUTCMilliseconds() : date.getMilliseconds();

    const isSecond: boolean = S === 0;
    const isMinute: boolean = isSecond && s === 0;
    const isHour: boolean = isMinute && m === 0;
    const isDay: boolean = isHour && h === 0;
    const isMonth: boolean = isDay && d === 1;
    const isYear: boolean = isMonth && M === 1;

    // Here you can put your own format logic
    const is24hFormat: boolean = !this.myTimeFormat.includes("am");
    let format: string;
    if (isYear) {
      format = "yyyy";
    } else if (isMonth) {
      format = "MMM";
    } else if (isDay) {
      format = "d. MMM";
    } else if (isHour) {
      format = is24hFormat ? "hh:mm a\nd. MMM" : "HH:mm\nd. MMM";
    } else if (isMinute) {
      format = is24hFormat ? "hh:mm:ss a\nd. MMM" : "HH:mm\nd. MMM";
    } else if (isSecond) {
      format = is24hFormat ? "hh:mm:ss a\nd. MMM" : "HH:mm:ss\nd. MMM";
    } else {
      format = is24hFormat ? "hh:mm:ss SSS a\nd. MMM" : "HH:mm:ss SSS\nd. MMM";
    }

    // Finally, you can use your logic to handle specific timezones
    return this.dateTimePipe.transform(date, format); // Angular example with a custom datePipe
  }