This is an automated email from the ASF dual-hosted git repository.

hainenber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 2e72080a163 chore(deps): bump echarts to 6.1.0 with locale and 
containLabel guards (#42315) (#42321)
2e72080a163 is described below

commit 2e72080a16397f636d2d76fe12d293150ab56fcc
Author: Evan Rusackas <[email protected]>
AuthorDate: Thu Jul 23 07:48:11 2026 -0700

    chore(deps): bump echarts to 6.1.0 with locale and containLabel guards 
(#42315) (#42321)
    
    Co-authored-by: Claude Code <[email protected]>
    Co-authored-by: Amin Ghadersohi <[email protected]>
---
 superset-frontend/package-lock.json                |  16 +--
 superset-frontend/package.json                     |   2 +-
 .../plugin-chart-echarts/src/components/Echart.tsx |  79 +--------------
 .../src/components/echartsLocale.ts                | 108 +++++++++++++++++++++
 .../test/components/echartsLocale.test.ts          |  82 ++++++++++++++++
 5 files changed, 204 insertions(+), 83 deletions(-)

diff --git a/superset-frontend/package-lock.json 
b/superset-frontend/package-lock.json
index 633e19f08de..e2441d76d4c 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -91,7 +91,7 @@
         "dayjs": "^1.11.21",
         "dom-to-image-more": "^3.10.2",
         "dom-to-pdf": "^0.3.2",
-        "echarts": "^5.6.0",
+        "echarts": "^6.1.0",
         "fast-glob": "^3.3.2",
         "fs-extra": "^11.3.6",
         "fuse.js": "^7.5.0",
@@ -19176,13 +19176,13 @@
       }
     },
     "node_modules/echarts": {
-      "version": "5.6.0",
-      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz";,
-      "integrity": 
"sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz";,
+      "integrity": 
"sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==",
       "license": "Apache-2.0",
       "dependencies": {
         "tslib": "2.3.0",
-        "zrender": "5.6.1"
+        "zrender": "6.1.0"
       }
     },
     "node_modules/echarts/node_modules/tslib": {
@@ -43754,9 +43754,9 @@
       }
     },
     "node_modules/zrender": {
-      "version": "5.6.1",
-      "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz";,
-      "integrity": 
"sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz";,
+      "integrity": 
"sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==",
       "license": "BSD-3-Clause",
       "dependencies": {
         "tslib": "2.3.0"
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index ea36ce764aa..2abbf32923e 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -176,7 +176,7 @@
     "dayjs": "^1.11.21",
     "dom-to-image-more": "^3.10.2",
     "dom-to-pdf": "^0.3.2",
-    "echarts": "^5.6.0",
+    "echarts": "^6.1.0",
     "fast-glob": "^3.3.2",
     "fs-extra": "^11.3.6",
     "fuse.js": "^7.5.0",
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx 
b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
index 2c5c9efa2e0..4eba1d22a4a 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/components/Echart.tsx
@@ -63,7 +63,7 @@ import {
   MarkAreaComponent,
   MarkLineComponent,
 } from 'echarts/components';
-import { LabelLayout } from 'echarts/features';
+import { LabelLayout, LegacyGridContainLabel } from 'echarts/features';
 import {
   EchartsHandler,
   EchartsProps,
@@ -72,6 +72,7 @@ import {
 } from '../types';
 import { DEFAULT_LOCALE } from '../constants';
 import { mergeEchartsThemeOverrides } from '../utils/themeOverrides';
+import { loadLocale } from './echartsLocale';
 
 // Define this interface here to avoid creating a dependency back to 
superset-frontend,
 // TODO: to move the type to @superset-ui/core
@@ -119,81 +120,11 @@ use([
   TitleComponent,
   VisualMapComponent,
   LabelLayout,
+  // Superset chart options rely on `grid.containLabel`, which echarts 6
+  // ignores (clipping axis labels) unless this legacy feature is registered.
+  LegacyGridContainLabel,
 ]);
 
-// Explicit per-locale imports rather than a template-literal dynamic
-// import: a computed import makes bundlers build a "context module" over
-// echarts/i18n, and resolving that directory through the echarts package's
-// `exports` map fails intermittently in webpack incremental builds
-// ("Package path ./i18n is exported ... but no valid target file was
-// found"). A static map is also the only thing that lets bundlers
-// code-split exactly the locales listed here. Keys are Superset locales
-// uppercased (see LANGUAGES in superset/config.py); values point at the
-// echarts bundle, whose naming differs for some locales (Slovenian is
-// langSI, Brazilian Portuguese is langPT-br). Only locales that echarts
-// 5.6.0 ships a bundle for are listed — Greek (langEL) and Latvian
-// (langLV) were added in echarts 6 and must stay out until this
-// dependency is upgraded again. Superset locales absent from this map
-// fall back to English.
-//
-// The "-obj" suffix is required and must not be dropped. echarts ships two
-// UMD builds per locale: langXX.js self-registers under echarts' own key and
-// exports nothing, while langXX-obj.js exports the locale object. Importing
-// the plain langXX.js yields an empty object, and registering that erases the
-// locale's `time` section, so every time-axis label crashes echarts'
-// formatTime with "Cannot read properties of null" — it evaluates
-// `month[u - 1]` eagerly for every template, even "{yyyy}". Registering the
-// object ourselves is also what lets Superset locale keys that differ from
-// echarts' own (SL -> SI, PT_BR -> PT-br) resolve at init time.
-type EChartsLocaleOption = Parameters<typeof registerLocale>[1];
-
-const LOCALE_LOADERS: Record<
-  string,
-  () => Promise<{ default: EChartsLocaleOption }>
-> = {
-  AR: () => import('echarts/i18n/langAR-obj.js'),
-  CS: () => import('echarts/i18n/langCS-obj.js'),
-  DE: () => import('echarts/i18n/langDE-obj.js'),
-  EN: () => import('echarts/i18n/langEN-obj.js'),
-  ES: () => import('echarts/i18n/langES-obj.js'),
-  FA: () => import('echarts/i18n/langFA-obj.js'),
-  FI: () => import('echarts/i18n/langFI-obj.js'),
-  FR: () => import('echarts/i18n/langFR-obj.js'),
-  HU: () => import('echarts/i18n/langHU-obj.js'),
-  IT: () => import('echarts/i18n/langIT-obj.js'),
-  JA: () => import('echarts/i18n/langJA-obj.js'),
-  KO: () => import('echarts/i18n/langKO-obj.js'),
-  NL: () => import('echarts/i18n/langNL-obj.js'),
-  PL: () => import('echarts/i18n/langPL-obj.js'),
-  RO: () => import('echarts/i18n/langRO-obj.js'),
-  PT_BR: () => import('echarts/i18n/langPT-br-obj.js'),
-  RU: () => import('echarts/i18n/langRU-obj.js'),
-  SL: () => import('echarts/i18n/langSI-obj.js'),
-  SV: () => import('echarts/i18n/langSV-obj.js'),
-  TH: () => import('echarts/i18n/langTH-obj.js'),
-  TR: () => import('echarts/i18n/langTR-obj.js'),
-  UK: () => import('echarts/i18n/langUK-obj.js'),
-  VI: () => import('echarts/i18n/langVI-obj.js'),
-  ZH: () => import('echarts/i18n/langZH-obj.js'),
-};
-
-const loadLocale = async (locale: string) => {
-  const loader = LOCALE_LOADERS[locale];
-  if (!loader) {
-    // Locale not supported in ECharts
-    return undefined;
-  }
-  try {
-    const localeObj = (await loader()).default;
-    // registerLocale replaces any built-in entry under the same key, so a
-    // partial object would leave echarts formatting against missing data.
-    // Fall back to the built-in locale instead of registering a broken one.
-    return localeObj?.time ? localeObj : undefined;
-  } catch {
-    return undefined;
-  }
-};
-
 // Report/thumbnail screenshots use standalone="true" (charts) or 3 (reports);
 // live embeds use 1/2 and keep animation. See superset/utils/screenshots.py.
 export function isReportScreenshotMode(): boolean {
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/components/echartsLocale.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/components/echartsLocale.ts
new file mode 100644
index 00000000000..77f0e751539
--- /dev/null
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/components/echartsLocale.ts
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { registerLocale } from 'echarts/core';
+
+export type EChartsLocaleOption = Parameters<typeof registerLocale>[1];
+
+// Import the `lang*-obj.js` locale bundles, NOT the plain `lang*.js` ones.
+// The plain files are side-effect UMD modules: they call
+// `registerLocale` against `require('echarts/lib/echarts')` — a second,
+// separate echarts instance from the treeshaken `echarts/core` the app
+// uses — and export nothing. Under webpack's CommonJS interop that gives
+// `.default === {}`, and registering that empty object under a builtin
+// locale name ('EN'/'ZH') wipes out the builtin definitions: every
+// time-axis chart then crashes in echarts' time formatter reading
+// `time.month` from the empty locale. The `-obj` variants export the
+// locale object itself, with no side effects and no duplicate echarts.
+//
+// Explicit per-locale imports rather than a template-literal dynamic
+// import: a computed import makes bundlers build a "context module" over
+// echarts/i18n, and resolving that directory through the echarts package's
+// `exports` map fails intermittently in webpack incremental builds
+// ("Package path ./i18n is exported ... but no valid target file was
+// found"). A static map is also the only thing that lets bundlers
+// code-split exactly the locales listed here. Keys are Superset locales
+// uppercased (see LANGUAGES in superset/config.py); values point at the
+// echarts bundle, whose naming differs for some locales (Slovenian is
+// langSI, Brazilian Portuguese is langPT-br). Superset locales absent
+// from this map fall back to English.
+export const LOCALE_LOADERS: Record<
+  string,
+  () => Promise<{ default: EChartsLocaleOption }>
+> = {
+  AR: () => import('echarts/i18n/langAR-obj.js'),
+  CS: () => import('echarts/i18n/langCS-obj.js'),
+  DE: () => import('echarts/i18n/langDE-obj.js'),
+  EL: () => import('echarts/i18n/langEL-obj.js'),
+  EN: () => import('echarts/i18n/langEN-obj.js'),
+  ES: () => import('echarts/i18n/langES-obj.js'),
+  FA: () => import('echarts/i18n/langFA-obj.js'),
+  FI: () => import('echarts/i18n/langFI-obj.js'),
+  FR: () => import('echarts/i18n/langFR-obj.js'),
+  HU: () => import('echarts/i18n/langHU-obj.js'),
+  IT: () => import('echarts/i18n/langIT-obj.js'),
+  JA: () => import('echarts/i18n/langJA-obj.js'),
+  KO: () => import('echarts/i18n/langKO-obj.js'),
+  LV: () => import('echarts/i18n/langLV-obj.js'),
+  NL: () => import('echarts/i18n/langNL-obj.js'),
+  PL: () => import('echarts/i18n/langPL-obj.js'),
+  RO: () => import('echarts/i18n/langRO-obj.js'),
+  PT_BR: () => import('echarts/i18n/langPT-br-obj.js'),
+  RU: () => import('echarts/i18n/langRU-obj.js'),
+  SL: () => import('echarts/i18n/langSI-obj.js'),
+  SV: () => import('echarts/i18n/langSV-obj.js'),
+  TH: () => import('echarts/i18n/langTH-obj.js'),
+  TR: () => import('echarts/i18n/langTR-obj.js'),
+  UK: () => import('echarts/i18n/langUK-obj.js'),
+  VI: () => import('echarts/i18n/langVI-obj.js'),
+  ZH: () => import('echarts/i18n/langZH-obj.js'),
+};
+
+/**
+ * Resolve the ECharts locale object for a Superset locale, or undefined
+ * when the locale is unsupported or the bundle yields no usable content.
+ * Returning undefined makes the caller skip `registerLocale`, so echarts
+ * keeps its builtin locale definitions instead of having them overwritten
+ * by an empty object.
+ */
+export const loadLocale = async (
+  locale: string,
+): Promise<EChartsLocaleOption | undefined> => {
+  const loader = LOCALE_LOADERS[locale];
+  if (!loader) {
+    // Locale not supported in ECharts
+    return undefined;
+  }
+  try {
+    const mod = await loader();
+    // Depending on the module system, the locale object is either the
+    // module's default export or the module namespace itself.
+    const localeObj = mod.default ?? (mod as unknown as EChartsLocaleOption);
+    // registerLocale replaces any builtin entry under the same key, and
+    // `time` is the section whose absence crashes every time-axis chart, so
+    // a locale without it (an empty or partial bundle) must not be
+    // registered. Fall back to the builtin locale instead.
+    if (localeObj?.time) {
+      return localeObj;
+    }
+  } catch {
+    // fall through to the builtin locale
+  }
+  return undefined;
+};
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/test/components/echartsLocale.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/test/components/echartsLocale.test.ts
new file mode 100644
index 00000000000..380b9284f3a
--- /dev/null
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/test/components/echartsLocale.test.ts
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { init, use, registerLocale } from 'echarts/core';
+import { LineChart } from 'echarts/charts';
+import { SVGRenderer } from 'echarts/renderers';
+import { GridComponent } from 'echarts/components';
+import { LOCALE_LOADERS, loadLocale } from 
'../../src/components/echartsLocale';
+
+type LocaleWithTime = {
+  time?: { month?: string[]; dayOfWeek?: string[] };
+};
+
+test('loadLocale returns undefined for locales echarts does not ship', async 
() => {
+  expect(await loadLocale('XX')).toBeUndefined();
+});
+
+test.each(Object.keys(LOCALE_LOADERS))(
+  'locale %s loads a locale object with time names',
+  async localeKey => {
+    const localeObj = (await loadLocale(localeKey)) as LocaleWithTime;
+    // A locale object without content would clobber echarts' builtin
+    // locales when registered; missing `time` names crash every
+    // time-axis chart inside echarts' time formatter (see #42314
+    // discussion — "Cannot read properties of null (reading '0')").
+    expect(localeObj).toBeDefined();
+    expect(localeObj.time?.month).toHaveLength(12);
+    expect(localeObj.time?.dayOfWeek).toHaveLength(7);
+  },
+);
+
+test('registering the loaded EN locale keeps time-axis charts rendering', 
async () => {
+  use([SVGRenderer, LineChart, GridComponent]);
+  const localeObj = await loadLocale('EN');
+  expect(localeObj).toBeDefined();
+  registerLocale('EN', localeObj!);
+
+  const chart = init(null, null, {
+    renderer: 'svg',
+    ssr: true,
+    width: 400,
+    height: 300,
+    locale: 'EN',
+  });
+  try {
+    chart.setOption(
+      {
+        xAxis: { type: 'time' },
+        yAxis: { type: 'value' },
+        series: [
+          {
+            type: 'line',
+            data: [
+              [new Date(1965, 0, 1).getTime(), 100],
+              [new Date(1985, 0, 1).getTime(), 200],
+              [new Date(2005, 0, 1).getTime(), 150],
+            ],
+          },
+        ],
+      },
+      { notMerge: true, lazyUpdate: false },
+    );
+    expect(chart.renderToSVGString()).toContain('<svg');
+  } finally {
+    chart.dispose();
+  }
+});

Reply via email to