Skip to content

feat(plugin-charts): resolve groupBy values to labels via field metadata, remove X-axis truncation#1165

Merged
hotlong merged 4 commits intomainfrom
copilot/fix-charts-groupby-label-issue
Apr 1, 2026
Merged

feat(plugin-charts): resolve groupBy values to labels via field metadata, remove X-axis truncation#1165
hotlong merged 4 commits intomainfrom
copilot/fix-charts-groupby-label-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 1, 2026

Chart X-axis labels showed raw field values (closed_won, lookup IDs like 2) and were hard-truncated to 3 characters via value.slice(0, 3), making them unreadable.

Data layer (ObjectChart.tsx)

  • Added resolveGroupByLabels() — after data fetch, resolves groupBy field values to display labels using object schema metadata:
    • select/picklist: value→label via field.options (handles both {value, label} objects and plain strings)
    • lookup/master_detail: batch dataSource.find() on reference_to object, maps id→name using metadata-driven id_field and reference_field/display_field (with sensible fallbacks)
    • fallback: humanizeLabel() (snake_case → Title Case)
  • Calls dataSource.getObjectSchema() in fetchData, gracefully no-ops if unavailable
  • Lookup queries derive the ID field from fieldDef.id_field (fallback to id) and the display label from fieldDef.reference_field / fieldDef.display_field (fallback chain: configured field → namelabeltitle → id), consistent with LookupField widget behavior
// Before: { stage: 'closed_won', amount: 500 }
// After:  { stage: 'Closed Won', amount: 500 }

// Before: { account: '2', amount: 300 }
// After:  { account: 'Globex Inc', amount: 300 }

Rendering layer (AdvancedChartImpl.tsx)

  • Removed value.slice(0, 3) from both XAxis tickFormatters (combo + default charts)
  • Mobile: truncate at 8 chars + ; Desktop: full label with angle: -35 rotation when labels > 5 chars
  • Memoized hasLongLabels via useMemo to avoid O(n) check per render
  • Removed console.log from render path to avoid noisy output in consuming apps

Tests

  • 19 new tests covering select mapping, string options, lookup resolution (including display_field and id_field metadata), error fallback, edge cases
  • Existing 16 aggregation tests unaffected

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui-demo Ready Ready Preview, Comment Apr 1, 2026 3:59am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Apr 1, 2026 3:59am

Request Review

…xis truncation

- Add resolveGroupByLabels() to convert groupBy field values to display labels
  using field metadata (select options, lookup record names, humanizeLabel fallback)
- Integrate label resolution into ObjectChart fetchData flow
- Remove value.slice(0, 3) truncation from AdvancedChartImpl XAxis tickFormatters
- Replace with adaptive formatting: mobile truncates at 8 chars, desktop shows
  full labels with angle=-35 rotation for long text
- Add comprehensive tests for label resolution (17 tests)

Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/eae827ef-e746-4d58-8b55-9f9e8fcd1dd0

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…Impl

Address code review feedback: memoize the data.some() long label check
using useMemo to avoid O(n) recalculation on every render.

Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/eae827ef-e746-4d58-8b55-9f9e8fcd1dd0

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@hotlong hotlong marked this pull request as ready for review April 1, 2026 03:44
Copilot AI review requested due to automatic review settings April 1, 2026 03:44
Copilot AI changed the title [WIP] Fix groupBy field value-to-label issue in charts feat(plugin-charts): resolve groupBy values to labels via field metadata, remove X-axis truncation Apr 1, 2026
Copilot AI requested a review from hotlong April 1, 2026 03:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves chart X-axis labeling in @object-ui/plugin-charts by resolving groupBy values into human-readable labels using object field metadata, and by replacing an overly aggressive tick truncation strategy in the advanced chart implementation.

Changes:

  • Added resolveGroupByLabels() (plus a local humanizeLabel() fallback) and integrated it into the ObjectChart data-fetch flow.
  • Updated AdvancedChartImpl X-axis tick formatting to be adaptive (mobile truncation, desktop full labels with rotation when needed).
  • Added targeted tests for groupBy label resolution and documented the fix in CHANGELOG.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
packages/plugin-charts/src/ObjectChart.tsx Adds value→label resolution for groupBy (select + lookup + fallback) and wires it into fetch flow.
packages/plugin-charts/src/AdvancedChartImpl.tsx Replaces hardcoded slice(0, 3) with adaptive tick formatting and conditional label rotation.
packages/plugin-charts/src/tests/ObjectChart.labelResolution.test.ts Adds unit tests covering select/lookup/fallback label resolution behavior.
CHANGELOG.md Documents the charts groupBy label resolution fix and the tick formatting change.

Comment on lines 7 to +13

/**
* Humanize a snake_case or kebab-case string into Title Case.
* Local implementation to avoid a dependency on @object-ui/fields.
*/
export function humanizeLabel(value: string): string {
return value.replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

humanizeLabel() duplicates the existing implementation in @object-ui/fields (packages/fields/src/index.tsx). If keeping charts independent from fields is important, consider moving this shared helper to a common package (e.g. @object-ui/core utils) so the behavior doesn’t diverge over time across packages.

Suggested change
/**
* Humanize a snake_case or kebab-case string into Title Case.
* Local implementation to avoid a dependency on @object-ui/fields.
*/
export function humanizeLabel(value: string): string {
return value.replace(/[_-]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
import startCase from 'lodash/startCase';
/**
* Humanize a snake_case or kebab-case string into Title Case.
* Uses lodash's startCase to avoid duplicating logic from other packages.
*/
export function humanizeLabel(value: string): string {
const normalized = value.replace(/[_-]+/g, ' ').trim();
return startCase(normalized);

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +138
const ids = [...new Set(data.map(row => row[groupByField]).filter(v => v != null))];
if (ids.length === 0) return data;

try {
const results = await dataSource.find(referenceTo, {
$filter: { id: { $in: ids } },
$top: ids.length,
});
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lookup label resolution hardcodes the ID field in the query as id ($filter: { id: { $in: ids } }). Elsewhere (e.g. LookupField) metadata can specify id_field, and some datasets use _id. Consider deriving the ID field from lookup metadata (fallback to id) and normalizing ids to strings to improve compatibility across DataSource implementations.

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +145
// Build id→name map using common display fields
const idToName: Record<string, string> = {};
for (const rec of records) {
const id = String(rec.id ?? rec._id ?? '');
const name = rec.name || rec.label || rec.title || id;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When building the lookup id→label map, the display label is inferred via rec.name || rec.label || rec.title, but lookup metadata already provides reference_field (and some schemas use a non-name display field). Using fieldDef.reference_field/display_field (with a sensible fallback) would make label resolution correct and consistent with the LookupField widget behavior.

Suggested change
// Build id→name map using common display fields
const idToName: Record<string, string> = {};
for (const rec of records) {
const id = String(rec.id ?? rec._id ?? '');
const name = rec.name || rec.label || rec.title || id;
// Build id→label map using reference/display field metadata with sensible fallbacks
const idToName: Record<string, string> = {};
const displayField =
(fieldDef as any).reference_field ||
(fieldDef as any).display_field ||
'name';
for (const rec of records) {
const id = String(rec.id ?? rec._id ?? '');
const configuredLabel = displayField ? rec[displayField] : undefined;
const name =
configuredLabel ||
rec.name ||
rec.label ||
rec.title ||
id;

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +118
console.log('📈 Rendering Chart:', { chartType, dataLength: data.length, config, series, xAxisKey });

// Memoize whether any X-axis label is long enough to warrant angle rotation
const hasLongLabels = React.useMemo(
() => data.some((d: any) => String(d[xAxisKey] || '').length > 5),
[data, xAxisKey],
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a console.log in the render path which will run on every render and can create noisy output (and slight perf impact) in consuming apps. Consider removing it or gating it behind an explicit debug flag/env check before shipping.

Copilot uses AI. Check for mistakes.
@hotlong
Copy link
Copy Markdown
Contributor

hotlong commented Apr 1, 2026

@copilot fix all

- Use id_field/display_field from lookup metadata instead of hardcoding 'id'/'name'
- Remove console.log from AdvancedChartImpl render path
- Add tests for display_field and id_field metadata support

Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/2a93e570-33d5-4c95-9f22-d13493ed8da4

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 1, 2026

@copilot fix all

Fixed all review comments in a897212:

  1. Lookup ID field: Now uses fieldDef.id_field from metadata (fallback to 'id') instead of hardcoding id in the $filter query.
  2. Lookup display field: Now uses fieldDef.reference_field / fieldDef.display_field from metadata (fallback chain: configured field → namelabeltitle → id) instead of hardcoding rec.name.
  3. console.log in render path: Removed from AdvancedChartImpl.tsx.
  4. humanizeLabel duplication: Kept as a local implementation to avoid adding @object-ui/fields as a dependency to plugin-charts. It's a trivial 1-liner and keeping it local preserves package independence.

Added 2 new tests for display_field and id_field metadata support. All 35 tests pass.

@hotlong hotlong merged commit 066a336 into main Apr 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Charts groupBy字段应通过metadata自动 value→label(包含select, lookup等),X轴显示应自适应/完整

3 participants