afv-library/skills/building-webapp-data-visualization/implementation/bar-line-chart.md
k-j-kim 9ba064174a
feat: syncing webapp skills sync to afv @W-21338965@ (#57)
* feat: removing old webapp skills

* feat: adding sync of skills from webapps to afv

* feat: adding the first iteration of skills

* feat: pin template deps to latest npm versions and flatten skill folders

- Add pin-template-deps.js to resolve "*" deps to exact npm versions
- Integrate pinning into sync-template-skills npm script
- Remove check-template-skills-versions.js (no longer needed)
- Simplify workflow to single sync step
- Flatten skill output: one folder per skill with cleaned names

Made-with: Cursor

* fix: resolve skill validation errors

- Move .template-versions.json from skills/ to root
- Shorten skill names to meet 64-char limit:
  - salesforce-webapp-feature-micro-frontend-generating-micro-frontend-lwc → salesforce-webapp-micro-frontend-lwc
  - salesforce-webapp-feature-react-agentforce-conversation-client-integrating-agentforce-conversation-client → salesforce-webapp-agentforce-conversation-client
  - salesforce-webapp-feature-react-file-upload-implementing-file-upload → salesforce-webapp-react-file-upload
- Expand descriptions to meet 20-word minimum with trigger context

* Add webapp skills from template, sync script updates

- Rename skill folders from salesforce-webapp-* to *-webapp-* convention
- Update sync-template-skills.js: set SKILL.md front matter name to dest folder
- Remove sync-template-skills workflow and pin-template-deps script
- Add .synced-template-skills.json manifest, deploying-webapp-to-salesforce skill
- Replace salesforce-webapp-designing-webapp-ui-ux with designing-webapp-ui-ux

Made-with: Cursor

* Align SKILL.md front matter name with folder for all webapp skills

Made-with: Cursor

* Fix skill validation: description length and trigger context for configuring-webapp-metadata, creating-webapp

Made-with: Cursor

* Rename sync script to sync-webapp-skills, drop manifest file

- Rename sync-template-skills.js to sync-webapp-skills.js
- Update package.json script to sync-webapp-skills
- Remove .synced-template-skills.json creation and add to .gitignore

Made-with: Cursor

* Revert sync-react-b2e-sample and sync-react-b2x-sample to upstream version

Made-with: Cursor

* Sync script: pin b2e and b2x to latest, sync skills from template

- Pin both template packages to latest in sync-webapp-skills.js
- Update package.json / package-lock.json (b2x 1.109.0)
- Sync skills: managing-webapp-agentforce-conversation-client, bar-line-chart,
  remove building-webapp-analytics-charts and integrating-webapp-agentforce-conversation-client
- Minor skill content updates

Made-with: Cursor

* Remove interactive map, weather widget, and Unsplash skills (no longer in template)

Made-with: Cursor

---------

Co-authored-by: Hemant Singh Bisht <hsinghbisht@salesforce.com>
2026-03-19 22:46:27 +05:30

6.9 KiB

Bar & Line / Area Chart — Implementation Guide

Requires recharts (install from the web app directory; see SKILL.md Step 2).


Data shapes

Time-series (line / area chart)

Use when data represents a trend over time or ordered sequence.

interface TimeSeriesDataPoint {
  x: string;   // date or label on the x-axis
  y: number;   // numeric value
}

Map raw fields to this shape: e.g. datex, revenuey.

Categorical (bar chart)

Use when data compares discrete categories.

interface CategoricalDataPoint {
  name: string;   // category label
  value: number;  // numeric value
}

Map raw fields to this shape: e.g. productname, salesvalue.

How to decide

Signal Type
"over time", "trend", date-like keys Time-series → line chart
"by category", "by X", label-like keys Categorical → bar chart

Theme colors

Pick a theme based on the data's sentiment:

Theme Stroke / Fill When to use
green #22c55e Growth, gain, positive trend
red #ef4444 Decline, loss, negative trend
neutral #6366f1 Default or mixed data

Define colors as constants — do not use inline hex values.

const THEME_COLORS = {
  red: "#ef4444",
  green: "#22c55e",
  neutral: "#6366f1",
} as const;

type ChartTheme = keyof typeof THEME_COLORS;

Line chart component

Create at components/LineChart.tsx (or colocate with the page):

import React from "react";
import {
  LineChart as RechartsLineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";

const THEME_COLORS = {
  red: "#ef4444",
  green: "#22c55e",
  neutral: "#6366f1",
} as const;

type ChartTheme = keyof typeof THEME_COLORS;

interface TimeSeriesDataPoint {
  x: string;
  y: number;
}

interface TimeSeriesChartProps {
  data: TimeSeriesDataPoint[];
  theme?: ChartTheme;
  title?: string;
  className?: string;
}

export function TimeSeriesChart({
  data,
  theme = "neutral",
  title,
  className = "",
}: TimeSeriesChartProps) {
  if (data.length === 0) {
    return <p className="text-muted-foreground text-center py-8">No data to display</p>;
  }

  const color = THEME_COLORS[theme];

  return (
    <div className={className}>
      {title && (
        <h3 className="text-sm font-medium text-primary mb-2 uppercase tracking-wide">
          {title}
        </h3>
      )}
      <ResponsiveContainer width="100%" height={300}>
        <RechartsLineChart data={data}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="x" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Line type="monotone" dataKey="y" stroke={color} strokeWidth={2} dot={false} />
        </RechartsLineChart>
      </ResponsiveContainer>
    </div>
  );
}

Bar chart component

Create at components/BarChart.tsx (or colocate with the page):

import React from "react";
import {
  BarChart as RechartsBarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";

const THEME_COLORS = {
  red: "#ef4444",
  green: "#22c55e",
  neutral: "#6366f1",
} as const;

type ChartTheme = keyof typeof THEME_COLORS;

interface CategoricalDataPoint {
  name: string;
  value: number;
}

interface CategoricalChartProps {
  data: CategoricalDataPoint[];
  theme?: ChartTheme;
  title?: string;
  className?: string;
}

export function CategoricalChart({
  data,
  theme = "neutral",
  title,
  className = "",
}: CategoricalChartProps) {
  if (data.length === 0) {
    return <p className="text-muted-foreground text-center py-8">No data to display</p>;
  }

  const color = THEME_COLORS[theme];

  return (
    <div className={className}>
      {title && (
        <h3 className="text-sm font-medium text-primary mb-2 uppercase tracking-wide">
          {title}
        </h3>
      )}
      <ResponsiveContainer width="100%" height={300}>
        <RechartsBarChart data={data}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="name" />
          <YAxis />
          <Tooltip />
          <Legend />
          <Bar dataKey="value" fill={color} radius={[4, 4, 0, 0]} />
        </RechartsBarChart>
      </ResponsiveContainer>
    </div>
  );
}

Area chart variant

For a filled area chart (useful for volume-over-time), swap Line for Area:

import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";

<ResponsiveContainer width="100%" height={300}>
  <AreaChart data={data}>
    <CartesianGrid strokeDasharray="3 3" />
    <XAxis dataKey="x" />
    <YAxis />
    <Tooltip />
    <Area type="monotone" dataKey="y" stroke={color} fill={color} fillOpacity={0.2} />
  </AreaChart>
</ResponsiveContainer>

Chart container wrapper

Wrap any chart in a styled card for consistent spacing:

import { Card } from "@/components/ui/card";

interface ChartContainerProps {
  children: React.ReactNode;
  className?: string;
}

export function ChartContainer({ children, className = "" }: ChartContainerProps) {
  return (
    <Card className={`p-4 border-gray-200 shadow-sm ${className}`}>
      {children}
    </Card>
  );
}

Usage:

<ChartContainer>
  <TimeSeriesChart data={monthlyData} theme="green" title="Monthly Revenue" />
</ChartContainer>

Preparing raw data

Map API responses to the expected shape before passing to the chart:

const timeSeriesData = useMemo(
  () => apiRecords.map((r) => ({ x: r.date, y: r.revenue })),
  [apiRecords],
);

const categoricalData = useMemo(
  () => apiRecords.map((r) => ({ name: r.product, value: r.sales })),
  [apiRecords],
);

Key Recharts concepts

Component Purpose
ResponsiveContainer Wraps chart to fill parent width
CartesianGrid Background grid lines
XAxis / YAxis Axis labels; dataKey maps to the data field
Tooltip Hover info
Legend Series labels
Line Line series; type="monotone" for smooth curves
Bar Bar series; radius rounds top corners
Area Filled area; fillOpacity controls transparency

Accessibility

  • Always include a text legend (not just colors).
  • Chart should be wrapped in a section with a visible heading.
  • For critical data, provide a text summary or table alternative.
  • Use sufficient color contrast between the chart stroke/fill and background.
  • Consider prefers-reduced-motion for chart animations.

Common mistakes

Mistake Fix
Missing ResponsiveContainer Chart won't resize; always wrap
Fixed width/height on chart Let ResponsiveContainer control sizing
No empty-data handling Show "No data" message when data.length === 0
Inline colors Extract to THEME_COLORS constant
Using raw Recharts for every chart type Use DonutChart (see donut-chart.md) for pie/donut