@object-ui/plugin-charts 0.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { ChartConfig } from './ChartContainerImpl';
2
2
  export interface AdvancedChartImplProps {
3
- chartType?: 'bar' | 'line' | 'area' | 'pie';
3
+ chartType?: 'bar' | 'line' | 'area' | 'pie' | 'donut' | 'radar' | 'scatter';
4
4
  data?: Array<Record<string, any>>;
5
5
  config?: ChartConfig;
6
6
  xAxisKey?: string;
@@ -0,0 +1,35 @@
1
+ import { default as React } from 'react';
2
+ export interface ChartBarRendererProps {
3
+ schema: {
4
+ type: string;
5
+ id?: string;
6
+ className?: string;
7
+ data?: Array<Record<string, any>>;
8
+ dataKey?: string;
9
+ xAxisKey?: string;
10
+ height?: number;
11
+ color?: string;
12
+ };
13
+ }
14
+ /**
15
+ * ChartBarRenderer - The public API for the bar chart component
16
+ */
17
+ export declare const ChartBarRenderer: React.FC<ChartBarRendererProps>;
18
+ export interface ChartRendererProps {
19
+ schema: {
20
+ type: string;
21
+ id?: string;
22
+ className?: string;
23
+ chartType?: 'bar' | 'line' | 'area';
24
+ data?: Array<Record<string, any>>;
25
+ config?: Record<string, any>;
26
+ xAxisKey?: string;
27
+ series?: Array<{
28
+ dataKey: string;
29
+ }>;
30
+ };
31
+ }
32
+ /**
33
+ * ChartRenderer - The public API for the advanced chart component
34
+ */
35
+ export declare const ChartRenderer: React.FC<ChartRendererProps>;
@@ -0,0 +1 @@
1
+ export declare const ObjectChart: (props: any) => import("react/jsx-runtime").JSX.Element;
@@ -1,42 +1,8 @@
1
- import { default as React } from 'react';
1
+ import { ChartBarRenderer, ChartRenderer } from './ChartRenderer';
2
2
  export type { BarChartSchema } from './types';
3
- export interface ChartBarRendererProps {
4
- schema: {
5
- type: string;
6
- id?: string;
7
- className?: string;
8
- data?: Array<Record<string, any>>;
9
- dataKey?: string;
10
- xAxisKey?: string;
11
- height?: number;
12
- color?: string;
13
- };
14
- }
15
- /**
16
- * ChartBarRenderer - The public API for the bar chart component
17
- * This wrapper handles lazy loading internally using React.Suspense
18
- */
19
- export declare const ChartBarRenderer: React.FC<ChartBarRendererProps>;
20
- export interface ChartRendererProps {
21
- schema: {
22
- type: string;
23
- id?: string;
24
- className?: string;
25
- chartType?: 'bar' | 'line' | 'area';
26
- data?: Array<Record<string, any>>;
27
- config?: Record<string, any>;
28
- xAxisKey?: string;
29
- series?: Array<{
30
- dataKey: string;
31
- }>;
32
- };
33
- }
34
- /**
35
- * ChartRenderer - The public API for the advanced chart component
36
- * Supports multiple chart types (bar, line, area) with full configuration
37
- */
38
- export declare const ChartRenderer: React.FC<ChartRendererProps>;
3
+ export { ChartBarRenderer, ChartRenderer };
4
+ export { ObjectChart } from './ObjectChart';
39
5
  export declare const chartComponents: {
40
- 'bar-chart': React.FC<ChartBarRendererProps>;
41
- chart: React.FC<ChartRendererProps>;
6
+ 'bar-chart': import('react').FC<import('./ChartRenderer').ChartBarRendererProps>;
7
+ chart: import('react').FC<import('./ChartRenderer').ChartRendererProps>;
42
8
  };
@@ -1,4 +1,4 @@
1
- import { BaseSchema } from '@object-ui/types';
1
+ import { BaseSchema } from '../../types/src';
2
2
  /**
3
3
  * Bar Chart component schema.
4
4
  * Renders a bar chart using Recharts library.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * Example: Using all new chart types
11
+ *
12
+ * This example demonstrates the new Pie, Donut, Radar, and Scatter chart types
13
+ */
14
+
15
+ export const pieChartExample = {
16
+ type: 'pie-chart',
17
+ data: [
18
+ { name: 'Chrome', value: 65 },
19
+ { name: 'Firefox', value: 20 },
20
+ { name: 'Safari', value: 10 },
21
+ { name: 'Edge', value: 5 },
22
+ ],
23
+ xAxisKey: 'name',
24
+ series: [{ dataKey: 'value' }],
25
+ config: {
26
+ Chrome: { label: 'Chrome', color: 'hsl(var(--chart-1))' },
27
+ Firefox: { label: 'Firefox', color: 'hsl(var(--chart-2))' },
28
+ Safari: { label: 'Safari', color: 'hsl(var(--chart-3))' },
29
+ Edge: { label: 'Edge', color: 'hsl(var(--chart-4))' },
30
+ }
31
+ };
32
+
33
+ export const donutChartExample = {
34
+ type: 'donut-chart',
35
+ data: [
36
+ { category: 'Electronics', revenue: 45000 },
37
+ { category: 'Clothing', revenue: 32000 },
38
+ { category: 'Food', revenue: 28000 },
39
+ { category: 'Books', revenue: 15000 },
40
+ ],
41
+ xAxisKey: 'category',
42
+ series: [{ dataKey: 'revenue' }]
43
+ };
44
+
45
+ export const radarChartExample = {
46
+ type: 'radar-chart',
47
+ data: [
48
+ { skill: 'React', score: 90 },
49
+ { skill: 'TypeScript', score: 85 },
50
+ { skill: 'Node.js', score: 80 }
51
+ ],
52
+ xAxisKey: 'skill',
53
+ series: [{ dataKey: 'score' }]
54
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-charts",
3
- "version": "0.3.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Chart components plugin for Object UI, powered by Recharts",
@@ -25,19 +25,19 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "recharts": "^3.7.0",
28
- "@object-ui/components": "0.3.1",
29
- "@object-ui/core": "0.3.1",
30
- "@object-ui/react": "0.3.1",
31
- "@object-ui/types": "0.3.1"
28
+ "@object-ui/components": "2.0.0",
29
+ "@object-ui/core": "2.0.0",
30
+ "@object-ui/react": "2.0.0",
31
+ "@object-ui/types": "2.0.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": "^18.0.0 || ^19.0.0",
35
35
  "react-dom": "^18.0.0 || ^19.0.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/react": "^19.2.9",
38
+ "@types/react": "^19.2.13",
39
39
  "@types/react-dom": "^19.2.3",
40
- "@vitejs/plugin-react": "^4.2.1",
40
+ "@vitejs/plugin-react": "^5.1.3",
41
41
  "typescript": "^5.9.3",
42
42
  "vite": "^7.3.1",
43
43
  "vite-plugin-dts": "^4.5.4"
@@ -16,6 +16,14 @@ import {
16
16
  AreaChart,
17
17
  Pie,
18
18
  PieChart,
19
+ Radar,
20
+ RadarChart,
21
+ PolarGrid,
22
+ PolarAngleAxis,
23
+ PolarRadiusAxis,
24
+ Scatter,
25
+ ScatterChart,
26
+ ZAxis,
19
27
  Cell,
20
28
  XAxis,
21
29
  YAxis,
@@ -62,7 +70,7 @@ const TW_COLORS: Record<string, string> = {
62
70
  const resolveColor = (color: string) => TW_COLORS[color] || color;
63
71
 
64
72
  export interface AdvancedChartImplProps {
65
- chartType?: 'bar' | 'line' | 'area' | 'pie';
73
+ chartType?: 'bar' | 'line' | 'area' | 'pie' | 'donut' | 'radar' | 'scatter';
66
74
  data?: Array<Record<string, any>>;
67
75
  config?: ChartConfig;
68
76
  xAxisKey?: string;
@@ -87,11 +95,25 @@ export default function AdvancedChartImpl({
87
95
  line: LineChart,
88
96
  area: AreaChart,
89
97
  pie: PieChart,
98
+ donut: PieChart,
99
+ radar: RadarChart,
100
+ scatter: ScatterChart,
90
101
  }[chartType] || BarChart;
91
102
 
92
103
  console.log('📈 Rendering Chart:', { chartType, dataLength: data.length, config, series, xAxisKey });
93
104
 
94
- if (chartType === 'pie') {
105
+ // Helper function to get color palette
106
+ const getPalette = () => [
107
+ 'hsl(var(--chart-1))',
108
+ 'hsl(var(--chart-2))',
109
+ 'hsl(var(--chart-3))',
110
+ 'hsl(var(--chart-4))',
111
+ 'hsl(var(--chart-5))'
112
+ ];
113
+
114
+ // Pie and Donut charts
115
+ if (chartType === 'pie' || chartType === 'donut') {
116
+ const innerRadius = chartType === 'donut' ? 60 : 0;
95
117
  return (
96
118
  <ChartContainer config={config} className={className}>
97
119
  <PieChart>
@@ -100,7 +122,7 @@ export default function AdvancedChartImpl({
100
122
  data={data}
101
123
  dataKey={series[0]?.dataKey || 'value'}
102
124
  nameKey={xAxisKey || 'name'}
103
- innerRadius={60}
125
+ innerRadius={innerRadius}
104
126
  strokeWidth={5}
105
127
  paddingAngle={2}
106
128
  outerRadius={80}
@@ -109,22 +131,9 @@ export default function AdvancedChartImpl({
109
131
  // 1. Try config by nameKey (category)
110
132
  let c = config[entry[xAxisKey]]?.color;
111
133
 
112
- // 2. Try series config (if only 1 series defined with color list, logic fails here usually)
134
+ // 2. Fallback to palette
113
135
  if (!c) {
114
- // Fallback: If 'colors' array was passed in schema, my adapter put it in config[seriesKey]?
115
- // Actually my adapter logic in index.tsx was: config[seriesKey].color = colors[idx]
116
- // But here we are iterating DATA items, not SERIES.
117
- // So we need a cycling palette.
118
- // Let's assume the user didn't provide per-category config here, so we cycle default colors.
119
- const palette = [
120
- 'hsl(var(--chart-1))',
121
- 'hsl(var(--chart-2))',
122
- 'hsl(var(--chart-3))',
123
- 'hsl(var(--chart-4))',
124
- 'hsl(var(--chart-5))'
125
- ];
126
- // Check if we can get colors from the first series config?
127
- // No, let's just use the palette or resolveColor from entry if provided in data.
136
+ const palette = getPalette();
128
137
  c = palette[index % palette.length];
129
138
  }
130
139
 
@@ -137,6 +146,73 @@ export default function AdvancedChartImpl({
137
146
  );
138
147
  }
139
148
 
149
+ // Radar chart
150
+ if (chartType === 'radar') {
151
+ return (
152
+ <ChartContainer config={config} className={className}>
153
+ <RadarChart data={data}>
154
+ <PolarGrid />
155
+ <PolarAngleAxis dataKey={xAxisKey} />
156
+ <PolarRadiusAxis />
157
+ <ChartTooltip content={<ChartTooltipContent />} />
158
+ <ChartLegend content={<ChartLegendContent />} />
159
+ {series.map((s: any) => {
160
+ const color = resolveColor(config[s.dataKey]?.color || DEFAULT_CHART_COLOR);
161
+ return (
162
+ <Radar
163
+ key={s.dataKey}
164
+ dataKey={s.dataKey}
165
+ stroke={color}
166
+ fill={color}
167
+ fillOpacity={0.6}
168
+ />
169
+ );
170
+ })}
171
+ </RadarChart>
172
+ </ChartContainer>
173
+ );
174
+ }
175
+
176
+ // Scatter chart
177
+ if (chartType === 'scatter') {
178
+ return (
179
+ <ChartContainer config={config} className={className}>
180
+ <ScatterChart>
181
+ <CartesianGrid vertical={false} />
182
+ <XAxis
183
+ type="number"
184
+ dataKey={xAxisKey}
185
+ name={String(config[xAxisKey]?.label || xAxisKey)}
186
+ tickLine={false}
187
+ axisLine={false}
188
+ />
189
+ <YAxis
190
+ type="number"
191
+ dataKey={series[0]?.dataKey || 'value'}
192
+ name={String(config[series[0]?.dataKey]?.label || series[0]?.dataKey)}
193
+ tickLine={false}
194
+ axisLine={false}
195
+ />
196
+ <ZAxis type="number" range={[60, 400]} />
197
+ <ChartTooltip content={<ChartTooltipContent />} />
198
+ <ChartLegend content={<ChartLegendContent />} />
199
+ {series.map((s: any, index: number) => {
200
+ const palette = getPalette();
201
+ const color = resolveColor(config[s.dataKey]?.color || palette[index % palette.length]);
202
+ return (
203
+ <Scatter
204
+ key={s.dataKey}
205
+ name={config[s.dataKey]?.label || s.dataKey}
206
+ data={data}
207
+ fill={color}
208
+ />
209
+ );
210
+ })}
211
+ </ScatterChart>
212
+ </ChartContainer>
213
+ );
214
+ }
215
+
140
216
  return (
141
217
  <ChartContainer config={config} className={className}>
142
218
  <ChartComponent data={data}>
@@ -0,0 +1,112 @@
1
+
2
+ import React, { Suspense } from 'react';
3
+ import { Skeleton } from '@object-ui/components';
4
+ import type { ChartConfig } from './ChartContainerImpl';
5
+
6
+ // 🚀 Lazy load the implementation files
7
+ const LazyChart = React.lazy(() => import('./ChartImpl'));
8
+ const LazyAdvancedChart = React.lazy(() => import('./AdvancedChartImpl'));
9
+
10
+ export interface ChartBarRendererProps {
11
+ schema: {
12
+ type: string;
13
+ id?: string;
14
+ className?: string;
15
+ data?: Array<Record<string, any>>;
16
+ dataKey?: string;
17
+ xAxisKey?: string;
18
+ height?: number;
19
+ color?: string;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * ChartBarRenderer - The public API for the bar chart component
25
+ */
26
+ export const ChartBarRenderer: React.FC<ChartBarRendererProps> = ({ schema }) => {
27
+ return (
28
+ <Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
29
+ <LazyChart
30
+ data={schema.data}
31
+ dataKey={schema.dataKey}
32
+ xAxisKey={schema.xAxisKey}
33
+ height={schema.height}
34
+ className={schema.className}
35
+ color={schema.color}
36
+ />
37
+ </Suspense>
38
+ );
39
+ };
40
+
41
+ export interface ChartRendererProps {
42
+ schema: {
43
+ type: string;
44
+ id?: string;
45
+ className?: string;
46
+ chartType?: 'bar' | 'line' | 'area';
47
+ data?: Array<Record<string, any>>;
48
+ config?: Record<string, any>;
49
+ xAxisKey?: string;
50
+ series?: Array<{ dataKey: string }>;
51
+ };
52
+ }
53
+
54
+ /**
55
+ * ChartRenderer - The public API for the advanced chart component
56
+ */
57
+ export const ChartRenderer: React.FC<ChartRendererProps> = ({ schema }) => {
58
+ // ⚡️ Adapter: Normalize JSON schema to Recharts Props
59
+ const props = React.useMemo(() => {
60
+ // 1. Defaults
61
+ let series = schema.series;
62
+ let xAxisKey = schema.xAxisKey;
63
+ let config = schema.config;
64
+
65
+ // 2. Adapt Tremor/Simple format (categories -> series, index -> xAxisKey)
66
+ if (!xAxisKey) {
67
+ if ((schema as any).index) xAxisKey = (schema as any).index;
68
+ else if ((schema as any).category) xAxisKey = (schema as any).category; // Support Pie/Donut category
69
+ }
70
+
71
+ if (!series) {
72
+ if ((schema as any).categories) {
73
+ series = (schema as any).categories.map((cat: string) => ({ dataKey: cat }));
74
+ } else if ((schema as any).value) {
75
+ // Single value adapter (for Pie/Simple charts)
76
+ series = [{ dataKey: (schema as any).value }];
77
+ }
78
+ }
79
+
80
+ // 3. Auto-generate config/colors if missing
81
+ if (!config && series) {
82
+ const colors = (schema as any).colors || ['hsl(var(--chart-1))', 'hsl(var(--chart-2))', 'hsl(var(--chart-3))'];
83
+ const newConfig: ChartConfig = {};
84
+ series.forEach((s: any, idx: number) => {
85
+ newConfig[s.dataKey] = { label: s.dataKey, color: colors[idx % colors.length] };
86
+ });
87
+ config = newConfig;
88
+ }
89
+
90
+ return {
91
+ chartType: schema.chartType,
92
+ data: schema.data,
93
+ config,
94
+ xAxisKey,
95
+ series,
96
+ className: schema.className
97
+ };
98
+ }, [schema]);
99
+
100
+ return (
101
+ <Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
102
+ <LazyAdvancedChart
103
+ chartType={props.chartType}
104
+ data={props.data}
105
+ config={props.config}
106
+ xAxisKey={props.xAxisKey}
107
+ series={props.series}
108
+ className={props.className}
109
+ />
110
+ </Suspense>
111
+ );
112
+ };
@@ -0,0 +1,79 @@
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { useDataScope, useSchemaContext } from '@object-ui/react';
4
+ import { ChartRenderer } from './ChartRenderer';
5
+ import { ComponentRegistry } from '@object-ui/core';
6
+
7
+ export const ObjectChart = (props: any) => {
8
+ const { schema } = props;
9
+ const context = useSchemaContext();
10
+ const dataSource = props.dataSource || context.dataSource;
11
+ const boundData = useDataScope(schema.bind);
12
+
13
+ const [fetchedData, setFetchedData] = useState<any[]>([]);
14
+ const [loading, setLoading] = useState(false);
15
+
16
+ useEffect(() => {
17
+ let isMounted = true;
18
+ const fetchData = async () => {
19
+ if (!dataSource || !schema.objectName) return;
20
+ if (isMounted) setLoading(true);
21
+ try {
22
+ // Apply filtering?
23
+ const results = await dataSource.find(schema.objectName, {
24
+ $filter: schema.filter
25
+ });
26
+
27
+ let data: any[] = [];
28
+ if (Array.isArray(results)) {
29
+ data = results;
30
+ } else if (results && typeof results === 'object') {
31
+ if (Array.isArray((results as any).value)) {
32
+ data = (results as any).value;
33
+ }
34
+ }
35
+
36
+ if (isMounted) {
37
+ setFetchedData(data);
38
+ }
39
+ } catch (e) {
40
+ console.error('[ObjectChart] Fetch error:', e);
41
+ } finally {
42
+ if (isMounted) setLoading(false);
43
+ }
44
+ };
45
+
46
+ if (schema.objectName && !boundData && !schema.data) {
47
+ fetchData();
48
+ }
49
+ return () => { isMounted = false; };
50
+ }, [schema.objectName, dataSource, boundData, schema.data, schema.filter]);
51
+
52
+ const finalData = boundData || schema.data || fetchedData || [];
53
+
54
+ // Merge data if not provided in schema
55
+ const finalSchema = {
56
+ ...schema,
57
+ data: finalData
58
+ };
59
+
60
+ if (loading && finalData.length === 0) {
61
+ // Return skeleton or loading state?
62
+ // ChartRenderer has suspense/skeleton handling but needs to be triggered.
63
+ // We pass empty data but it might render empty chart.
64
+ }
65
+
66
+ return <ChartRenderer {...props} schema={finalSchema} />;
67
+ };
68
+
69
+ // Register it
70
+ ComponentRegistry.register('object-chart', ObjectChart, {
71
+ namespace: 'plugin-charts',
72
+ label: 'Object Chart',
73
+ category: 'view',
74
+ inputs: [
75
+ { name: 'objectName', type: 'string', label: 'Object Name', required: true },
76
+ { name: 'data', type: 'array', label: 'Data', description: 'Optional static data' },
77
+ { name: 'filter', type: 'array', label: 'Filter' },
78
+ ]
79
+ });
package/src/index.test.ts CHANGED
@@ -13,7 +13,7 @@ describe('Plugin Charts', () => {
13
13
  // Import all renderers to register them
14
14
  beforeAll(async () => {
15
15
  await import('./index');
16
- });
16
+ }, 60000);
17
17
 
18
18
  describe('bar-chart component', () => {
19
19
  it('should be registered in ComponentRegistry', () => {