ChartGraph is used for displaying various types of graphs plotted on an x and y axis. It makes it easier to identify and understand patterns over time across different categories, enabling people to make informed decisions quickly.
also known as Bar Graph, Line Graph, Column Graph
Props
Usage guidelines
These are overall guidelines for ChartGraph. For usage guidelines on specific graphs, see:
-
Bar graph usage guidelines
-
Line graph usage guidelines
-
Charts and graphs general guidelines
- To compare data sets
- To show trends, frequency of occurrences and distribution of data across time
- To visually summarize and explain complex data sets and concepts
- To show parts-to-whole. Use a pie or donut chart instead.
- To visualize a series of stages in a sequence that get smaller over time. Use a funnel chart instead.
- To show large data sets that are too hard to show in a chart. Use Table instead.
Best practices
These are overall best practices for ChartGraph. For best practices on specific graphs, see:
-
Bar graph best practices
-
Line graph best practices
-
Charts and graphs general guidelines
Limit the amount of data you show in a graph so that it is readable and easy to follow.
Add a lot of data to a graph that makes it hard to read. If you need to show a lot of data, use Table instead. Another option is using multiple graphs in a grid.
When displaying multiple categories in lines or bars, stick to the default color sequence provided since it has been optimized for color blindness.
Pick colors that are too similar to each other and hard to tell apart, especially for those with visual impairments.
Use a biaxial chart when there is a significant difference between values. A suggestion is a 50%+ difference.
Use a biaxial chart when there is a very minor difference between values being compared. Use a chart with a single axis instead.
Accessibility
Visual patterns
Charts use color to represent discrete categories. However, people with color blindness or other visual impairments may have difficulty telling certain colors apart.
Therefore, ChartGraph provides an accessibility view mode where colors in bars and lines are replaced with visual patterns to help in their interpretation. Bar charts use pattern fills and line charts use series markers with different shapes to help distinguish between data points without using color alone. Each pattern fill and time series shape corresponds to one of the colors in our 12-color categorical palette.
ChartGraph provides visualPatternSelected
and onVisualPatternChange
props to manage the visual state of the component externally. If a person selects Low Vision Features in the settings or enables the visual patterns in one component, other charts can also be enabled at the same time to adapt to a user’s accessibility preferences.
ChartGraph displays an IconButton in the header section that allows to enable the visual pattern from the component itself.
import { useState } from 'react'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('visualPattern'); const data = [ { name: 'A', Color_01: 90, Color_02: 90, Color_03: 90, Color_04: 90, Color_05: 90, Color_06: 90, }, ]; return ( <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of chart with decal pattern in bars" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } range={[0, 100]} type="bar" data={data} elements={[ { type: 'bar', id: 'Color_01' }, { type: 'bar', id: 'Color_02' }, { type: 'bar', id: 'Color_03' }, { type: 'bar', id: 'Color_04' }, { type: 'bar', id: 'Color_05' }, { type: 'bar', id: 'Color_06' }, ]} /> ); }
For line graphs, shapes are used to help tell categories apart.
import { useState } from 'react'; import { FixedZIndex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('visualPattern'); const data = [ { name: new Date(2023, 1, 1).getTime(), Color_01: 50, Color_02: 100, Color_03: 150, Color_04: 200, Color_05: 250, Color_06: 300, }, { name: new Date(2023, 5, 15).getTime(), Color_01: 100, Color_02: 150, Color_03: 200, Color_04: 250, Color_05: 300, Color_06: 350, }, { name: new Date(2023, 10, 1).getTime(), Color_01: 150, Color_02: 200, Color_03: 250, Color_04: 300, Color_05: 350, Color_06: 400, }, ]; return ( <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of chart with decal pattern in lines" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } type="line" tickFormatter={{ timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} range={{ xAxisBottom: [ new Date(2023, 0, 1).getTime(), new Date(2023, 11, 1).getTime(), ], }} data={data} elements={[ { type: 'line', id: 'Color_01' }, { type: 'line', id: 'Color_02' }, { type: 'line', id: 'Color_03' }, { type: 'line', id: 'Color_04' }, { type: 'line', id: 'Color_05' }, { type: 'line', id: 'Color_06' }, ]} modalZIndex={new FixedZIndex(11)} /> ); }
ARIA attributes
Charts are presented as images to screen readers. Provide a description using the accessibilityLabel
to provide more context around ChartGraph’s content.
Don't use accessibilityLabel
to describe the ChartGraph content itself. We’re working on adding a Table view feature to access the detailed data of ChartGraph in a more granular and accessible way.
accessibilityLabel
is automatically prefixed with "ChartGraph." to differentiate ChartGraph from other images.
import { useState } from 'react'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'A', Series_01: 50, Series_02: 100, }, { name: 'B', Series_01: 100, Series_02: 200, }, { name: 'C', Series_01: 250, Series_02: 300, }, ]; return ( <ChartGraph accessibilityLabel="Example of Bar chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } type="bar" title="ChartGraph" titleDisplay="hidden" data={data} elements={[ { type: 'bar', id: 'Series_01' }, { type: 'bar', id: 'Series_02' }, ]} /> ); }
Tabular representation
An additional button is available to show chart data as a table so that it’s easier to
- navigate with a screen reader
- read data for those who have difficulty processing visual information
- download data to view in a person’s own tools
The tabular data is also available to download as a .csv file.
Localization
Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.
To localize data content, use the labelMap
prop. See the example for detailed implementation guidance.
To localize dates in time series, use the tickFormatter.timeseries
prop. See the
time series example
for detailed implementation guidance.
import { useState } from 'react'; import { DefaultLabelProvider } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); return ( <DefaultLabelProvider // $FlowExpectedError[incompatible-type] For demostration purposes labels={{ ChartGraph: { accessibilityLabelPrefixText: 'ChartGraph', defaultViewText: 'Standard-Ansichtsmodus.', accessibleViewText: 'Ansichtsmodus für Barrierefreiheit.', tabularData: 'Tabellarische Darstellung.', accessibilityLabelDismissModal: 'Tabellendarstellung modal aufheben.', tableSeriesText: 'Reihe.', tableXAxisText: 'x-Achsen-Werte.', tableYAxisText: 'y-Achsen-Werte.', downloadCsvButtonText: 'Als .csv herunterladen.', cancelButtonText: 'Abbrechen.', }, }} > <ChartGraph title="Eindrücke" description="Leistung im Laufe der Zeit. Impressionen geben an, wie oft Ihr Pin auf dem Bildschirm angezeigt wurde." labelMap={{ Women: 'Frauen', Men: 'Männer', Users: 'Benutzer', Clickthroughs: 'Durchklicken', }} visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } accessibilityLabel="Beispiel für ein Liniendiagramm" type="bar" data={[ { name: 'Women', Users: 100, Clickthroughs: 200, }, { name: 'Men', Users: 200, Clickthroughs: 300, }, ]} elements={[ { type: 'bar', id: 'Users' }, { type: 'bar', id: 'Clickthroughs' }, ]} /> </DefaultLabelProvider> ); }
Subcomponents
LegendIcon
LegendIcon should only be used within custom tooltips. See the custom tooltip variant for implementation guidance.
LegendIcon Props
Variants
Bar horizontal
Arrange bars in rows that stack from top to bottom when horizontal space and you have longer text labels. Also known as a "horizontal bar chart".
Props: type="bar"
elements=[{..., type:'bar'}]
layout="horizontal"
import { useState } from 'react'; import { Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'Pacific Northwest', Impressions: 1000, CPM: 1050, CPC: 1000, }, { name: 'Sunbelt', Impressions: 1000, CPM: 1500, CPC: 1000, }, { name: 'Great Lakes', Impressions: 1000, CPM: 1050, CPC: 1000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Regions in the US" accessibilityLabel="Example of a vertical bar chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } type="bar" layout="horizontal" data={data} elements={[ { type: 'bar', id: 'Impressions' }, { type: 'bar', id: 'CPM' }, { type: 'bar', id: 'CPC' }, ]} /> </Flex> ); }
Bar column
In a column chart, bars are ordered horizontally. Use this when you have a small amount of data to compare and the horizontal space to do so. This includes text labels. If text labels are long, use Bar horizontal instead.
Props: type="bar"
layout="horizontal"
import { useState } from 'react'; import { Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: '10-24', Impressions: 1000, CPM: 1050, CPC: 1000, }, { name: '25-50', Impressions: 1000, CPM: 1500, CPC: 1000, }, { name: '50+', Impressions: 1000, CPM: 1050, CPC: 1000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Age" accessibilityLabel="Example of a vertical bar chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } type="bar" data={data} elements={[ { type: 'bar', id: 'Impressions' }, { type: 'bar', id: 'CPM' }, { type: 'bar', id: 'CPC' }, ]} /> </Flex> ); }
Stacked bars
Stacked bar charts break bars into smaller categories so that their relationship to the whole can be seen.
Props: type="bar"
stacked={true}
import { useState } from 'react'; import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: new Date(2023, 0, 1).getTime(), Awareness: 5000, Consideration: 1000, 'Catalog sales': 4000, Conversions: 2500, }, { name: new Date(2023, 1, 1).getTime(), Awareness: 4000, Consideration: 5000, 'Catalog sales': 1000, Conversions: 2500, }, { name: new Date(2023, 2, 1).getTime(), Awareness: 2500, Consideration: 5000, 'Catalog sales': 1000, Conversions: 4000, }, { name: new Date(2023, 3, 1).getTime(), Awareness: 2500, Consideration: 4000, 'Catalog sales': 1000, Conversions: 5000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="508 campaigns" stacked accessibilityLabel="Example of stacked bars chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } data={data} range={{ xAxisBottom: ['auto', 'auto'], }} elements={[ { type: 'bar', id: 'Awareness' }, { type: 'bar', id: 'Consideration' }, { type: 'bar', id: 'Catalog sales' }, { type: 'bar', id: 'Conversions' }, ]} tickFormatter={{ yAxisLeft: (value) => `${value / 1000}K`, timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Line
A line graph plots numeric values for categorical data as a line that shows a progression through time.
Props: type="line"
elements=[{..., type:'line'}]
import { useState } from 'react'; import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: new Date(2023, 0, 1).getTime(), Engagement: 850000, Saves: 870000, Impressions: 280000, 'Page visits': 5000, }, { name: new Date(2023, 1, 2).getTime(), Engagement: 800000, Saves: 690000, Impressions: 790000, 'Page visits': 9000, }, { name: new Date(2023, 2, 3).getTime(), Engagement: 890000, Saves: 850000, Impressions: 250000, 'Page visits': 8000, }, { name: new Date(2023, 3, 4).getTime(), Engagement: 870000, Saves: 550000, Impressions: 230000, 'Page visits': 7000, }, { name: new Date(2023, 4, 5).getTime(), Engagement: 830000, Saves: 84000, Impressions: 710000, 'Page visits': 3000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Performance over time" accessibilityLabel="Example of line chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } data={data} range={{ xAxisBottom: ['auto', 'auto'], }} elements={[ { type: 'line', id: 'Engagement' }, { type: 'line', id: 'Saves' }, { type: 'line', id: 'Impressions' }, { type: 'line', id: 'Page visits' }, ]} type="line" tickFormatter={{ yAxisLeft: (value) => { if (value >= 1000000) return `${value / 1000000}m`; if (value >= 1000) return `${value / 1000}k`; return value; }, timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Combo
This combines a bar graph with a line graph. It is useful to see both categories and a trend or range over time.
Props: type="combo"
elements=[{..., type:'bar' or type:'line'}]
import { useState } from 'react'; import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'Quarter 1', 'This year': 850000, Profit: 870000, 'Last year': 980000, }, { name: 'Quarter 2', 'This year': 800000, Profit: 690000, 'Last year': 590000, }, { name: 'Quarter 3', 'This year': 890000, Profit: 850000, 'Last year': 950000, }, { name: 'Quarter 4', 'This year': 870000, Profit: 550000, 'Last year': 830000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Product group sales:" accessibilityLabel="Example of combo chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } data={data} elements={[ { type: 'bar', id: 'This year' }, { type: 'bar', id: 'Profit' }, { type: 'line', id: 'Last year' }, ]} type="combo" tickFormatter={{ yAxisLeft: (value) => { if (value >= 1000000) return `${value / 1000000}m`; if (value >= 1000) return `${value / 1000}k`; return value; }, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Biaxial
Biaxial graphs show two y-axis. They're used when either comparing two categories with mixed types of data—for example, clicks vs spend—or when there is a significant difference between values. A suggestion is to move to a biaxial chart if there is a 50%+ difference between compared values.
Props: layout="verticalBiaxial"
layout="horizontalBiaxial"
import { useState } from 'react'; import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: new Date(2023, 0, 1).getTime(), Spend: 40000, 'Total ROAS (Checkout)': 570000, }, { name: new Date(2023, 1, 1).getTime(), Spend: 45000, 'Total ROAS (Checkout)': 690000, }, { name: new Date(2023, 2, 1).getTime(), Spend: 55000, 'Total ROAS (Checkout)': 850000, }, { name: new Date(2023, 3, 1).getTime(), Spend: 70000, 'Total ROAS (Checkout)': 550000, }, { name: new Date(2023, 4, 1).getTime(), Spend: 830000, 'Total ROAS (Checkout)': 1000000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Performance over last 30 days" accessibilityLabel="Example of line chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } layout="verticalBiaxial" data={data} range={{ xAxisBottom: ['auto', 'auto'] }} elements={[ { type: 'line', id: 'Spend', axis: 'left' }, { type: 'line', id: 'Total ROAS (Checkout)', axis: 'right' }, ]} type="line" tickFormatter={{ yAxisLeft: (value) => { if (value >= 500) return `$${value / 500}k`; return value; }, yAxisRight: (value) => { if (value >= 1000) return `${value / 1000}k`; return value; }, timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )} ${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Header
ChartGraph's header support a title, description, a HelpButton, and the accessibility features: tabular data and visual patterns switch buttons.
- Title. A title for the graph in case it’s not displayed elsewhere on the screen.
- Description. An optional description is available if more context is needed.
- HelpButton
- Tabular data switch button. Not available yet.
- Visual pattern switch button.
Props: title
description
helpButton
import { Flex, HelpButton } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const data = [ { name: 'Quarter 1', Clicks: 850000, Conversions: 870000, }, { name: 'Quarter 2', Clicks: 800000, Conversions: 690000, }, { name: 'Quarter 3', Clicks: 890000, Conversions: 850000, }, { name: 'Quarter 4', Clicks: 870000, Conversions: 550000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph accessibilityLabel="Example of chart with title and description" visualPatternSelected="disabled" onVisualPatternChange={() => {}} title="Clicks vs conversions" helpButton={ <HelpButton accessibilityLabel="Click to learn more this ChartGraph" accessibilityPopoverLabel="Expanded information about this ChartGraph" text="If you want to learn more about Clicks vs conversions, visit our Help center." link={{ href: 'https://help.pinterest.com/en/business/article/conversion-insights/', text: 'Read our documentation', accessibilityLabel: 'Visit our Help center', }} /> } description="Includes both web and mobile" layout="verticalBiaxial" data={data} elements={[ { type: 'bar', id: 'Clicks', axis: 'left', }, { type: 'line', id: 'Conversions', axis: 'right', }, ]} /> </Flex> ); }
Tooltip
For showing more precise details on hover.
Props: renderTooltip
import { useState } from 'react'; import { Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'iOS', '18-30': 850000, '30-50': 870000, '50+': 980000, }, { name: 'Android', '18-30': 800000, '30-50': 690000, '50+': 590000, }, { name: 'Web', '18-30': 890000, '30-50': 850000, '50+': 950000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Views by demographics and device" accessibilityLabel="Example of chart with tooltip" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } initialTicks={3} data={data} elements={[ { type: 'bar', id: '18-30' }, { type: 'bar', id: '30-50' }, { type: 'bar', id: '50+' }, ]} type="bar" tickFormatter={{ yAxisLeft: (value) => { if (value >= 1000000) return `${value / 1000000}m`; if (value >= 1000) return `${value / 1000}k`; return value; }, }} /> </Flex> ); }
import { useState } from 'react'; import { Flex, Text } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'USA', '20-30': 100, '40-50': 200, '50-60': 300, }, { name: 'Europe', '20-30': 200, '40-50': 300, '50-60': 400, }, ]; return ( <Flex height="100%" width="100%" direction="column"> <ChartGraph title="MAU per regions" accessibilityLabel="Example of chart with tooltip" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } tickFormatter={{ yAxisLeft: (value) => `${value}m` }} initialTicks={3} type="bar" data={data} renderTooltip={({ label, payload, active }) => active && Array.isArray(payload) ? ( <Flex direction="column" gap={2}> <Flex.Item> {payload.map((payloadData) => ( <Flex key={payloadData.name} alignItems="center" gap={2}> <ChartGraph.LegendIcon payloadData={payloadData} /> <Flex.Item flex="grow"> <Text size="100">{payloadData.name}</Text> </Flex.Item> <Text size="200" weight="bold"> ${payloadData.value} </Text> </Flex> ))} </Flex.Item> <Text size="100" color="subtle"> {label} </Text> </Flex> ) : null } elements={[ { type: 'bar', id: '20-30', }, { type: 'bar', id: '40-50', }, { type: 'bar', id: '50-60', }, ]} /> </Flex> ); }
Legend
import { useState } from 'react'; import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: new Date(2023, 0, 1).getTime(), California: 1500000, Arizona: 500000, }, { name: new Date(2023, 1, 2).getTime(), California: 1000000, Arizona: 400000, }, { name: new Date(2023, 2, 3).getTime(), California: 1500000, Arizona: 500000, }, { name: new Date(2023, 3, 4).getTime(), California: 1000000, Arizona: 400000, }, { name: new Date(2023, 4, 5).getTime(), California: 1500000, Arizona: 500000, }, { name: new Date(2023, 5, 6).getTime(), California: 1000000, Arizona: 400000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Average spend by region" accessibilityLabel="Example of chart with tooltip" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } data={data} elements={[ { type: 'bar', id: 'California' }, { type: 'bar', id: 'Arizona' }, ]} type="bar" tickFormatter={{ yAxisLeft: (value) => { if (value >= 1000000) return `${value / 1000000}m`; if (value >= 1000) return `${value / 1000}k`; return value; }, timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )} ${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
ReferenceArea
Use to highlight an area in a graph for extra context. A common example is showing when data isn’t available.
Props: referenceAreas
import { Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const data = [ { name: new Date(2023, 0, 1).getTime(), Impressions: 850000, }, { name: new Date(2023, 0, 2).getTime(), Impressions: 800000, }, { name: new Date(2023, 0, 3).getTime(), Impressions: 890000, }, { name: new Date(2023, 0, 4).getTime(), Impressions: 870000, }, { name: new Date(2023, 0, 5).getTime(), Impressions: 830000, }, { name: new Date(2023, 0, 6).getTime(), Impressions: 930000, }, { name: new Date(2023, 0, 7).getTime(), Impressions: 630000, }, { name: new Date(2023, 0, 8).getTime(), Impressions: 730000, }, { name: new Date(2023, 0, 9).getTime(), Impressions: 890000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Impressions over time" accessibilityLabel="Example of chart with reference area" visualPatternSelected="disabled" onVisualPatternChange={() => {}} data={data} range={{ yAxisLeft: [0, 1000000], xAxisBottom: ['auto', new Date(2023, 0, 10).getTime()], }} elements={[{ type: 'line', id: 'Impressions' }]} type="line" tickFormatter={{ yAxisLeft: (value) => { if (value >= 1000000) return `${value / 1000000}m`; if (value >= 1000) return `${value / 1000}k`; return value; }, timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} referenceAreas={[ { id: 'ExampleBD', label: 'Real-time data not available', x1: new Date(2023, 0, 9).getTime(), x2: new Date(2023, 0, 10).getTime(), yAxisId: 'left', y1: 0, y2: 1000000, }, ]} /> </Flex> ); }
Precision in line graphs
To show exact and accurate data, lines should be rectilinear. When showing trends, forecasts and imprecise data, then lines should be curved to denote that these are just approximations.
Props: elements=[{..., type:'line', precision='estimate'}]
import { FixedZIndex, Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const data = [ { name: new Date(2023, 0, 1).getTime(), 'Actual data': 1000 }, { name: new Date(2023, 1, 1).getTime(), 'Actual data': 1567 }, { name: new Date(2023, 2, 1).getTime(), 'Actual data': 1005 }, { name: new Date(2023, 3, 1).getTime(), 'Actual data': 1003 }, { name: new Date(2023, 4, 1).getTime(), 'Actual data': 1100 }, { name: new Date(2023, 5, 1).getTime(), 'Actual data': 1030 }, { name: new Date(2023, 6, 1).getTime(), 'Actual data': 1089 }, { name: new Date(2023, 7, 1).getTime(), 'Actual data': 1065, Forecast: 1065, }, { name: new Date(2023, 8, 1).getTime(), Forecast: 1089 }, { name: new Date(2023, 9, 1).getTime(), Forecast: 1030 }, { name: new Date(2023, 10, 1).getTime(), Forecast: 1990 }, { name: new Date(2023, 11, 1).getTime(), Forecast: 2034 }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <ChartGraph title="Forecast for 2023" accessibilityLabel="Example of time series chart" visualPatternSelected="disabled" onVisualPatternChange={() => {}} data={data} range={{ xAxisBottom: ['auto', 'auto'], }} elements={[ { type: 'line', id: 'Actual data', precision: 'exact' }, { type: 'line', id: 'Forecast', precision: 'estimate' }, ]} type="line" tickFormatter={{ timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Colors
Colors on data series are set automatically for best contrast.
If different graphs need to be compared simultaneously, see example below, color in time series can be set in the elements
prop setting each color color='01'
individually.
import { useState } from 'react'; import { Flex } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const data = [ { name: 'A', Series_01: 100, }, { name: 'B', Series_01: 200, }, { name: 'C', Series_01: 300, }, ]; return ( <Flex direction="column" width="100%" wrap> <Flex> <ChartGraph title="ChartGraph A" description="Color 01" accessibilityLabel="Example of line chart color 01" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } initialTicks={3} type="line" renderTooltip="none" legend="none" data={data} elements={[{ type: 'line', id: 'Series_01' }]} /> <ChartGraph title="ChartGraph B" description="Color 02" accessibilityLabel="Example of line chart color 02" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } initialTicks={3} type="line" renderTooltip="none" legend="none" data={data} elements={[{ type: 'line', id: 'Series_01', color: '02' }]} /> </Flex> <Flex> <ChartGraph title="ChartGraph C" description="Color 03" accessibilityLabel="Example of line chart color 03" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } initialTicks={3} type="line" renderTooltip="none" legend="none" data={data} elements={[{ type: 'line', id: 'Series_01', color: '03' }]} /> <ChartGraph title="ChartGraph D" description="Color 04" accessibilityLabel="Example of line chart color 04" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } initialTicks={3} type="line" renderTooltip="none" legend="none" data={data} elements={[{ type: 'line', id: 'Series_01', color: '04' }]} /> </Flex> </Flex> ); }
Layout
ChartGraph supports 4 layouts. Legend positions automatically on each layout for easier comprehension.
Single axis:
- vertical (default)
- horizontal
Dual axis:
- verticalBiaxial
- horizontalBiaxial
Props: layout
import { useState } from 'react'; import { Flex, RadioGroup } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const [type, setType] = useState('bar'); const [layout, setLayout] = useState('vertical'); let axisSeries01; let axisSeries02; if (layout === 'horizontalBiaxial') { axisSeries01 = 'bottom'; axisSeries02 = 'top'; } if (layout === 'verticalBiaxial') { axisSeries01 = 'left'; axisSeries02 = 'right'; } const data = [ { name: 'A', Series_01: 100, Series_02: 200, }, { name: 'B', Series_01: 200, Series_02: 300, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <Flex width="100%" justifyContent="between" wrap> <RadioGroup legend="ChartGraph type" direction="row" id="layout-type"> <RadioGroup.RadioButton checked={type === 'bar'} id="layout-type-bar" label="Bar" name="bar" onChange={() => setType('bar')} value="bar" size="sm" /> <RadioGroup.RadioButton checked={type === 'line'} id="layout-type-line" label="Line" name="line" onChange={() => setType('line')} value="line" size="sm" /> <RadioGroup.RadioButton checked={type === 'combo'} id="layout-type-combo" label="Combo" name="combo" onChange={() => setType('combo')} value="combo" size="sm" /> </RadioGroup> <RadioGroup legend="Layout" direction="row" id="layout_layout"> <RadioGroup.RadioButton checked={layout === 'horizontal'} id="layout_layout-horizontal" label="Horizontal" name="horizontal" onChange={() => setLayout('horizontal')} value="horizontal" size="sm" /> <RadioGroup.RadioButton checked={layout === 'horizontalBiaxial'} id="layout_layout-horizontalBiaxial" label="HorizontalBiaxial" name="horizontalBiaxial" onChange={() => setLayout('horizontalBiaxial')} value="horizontalBiaxial" size="sm" /> <RadioGroup.RadioButton checked={layout === 'vertical'} id="layout_layout-vertical" label="Vertical" name="vertical" onChange={() => setLayout('vertical')} value="vertical" size="sm" /> <RadioGroup.RadioButton checked={layout === 'verticalBiaxial'} id="layout_layout-verticalBiaxial" label="VerticalBiaxial" name="verticalBiaxial" onChange={() => setLayout('verticalBiaxial')} value="verticalBiaxial" size="sm" /> </RadioGroup> </Flex> <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of chart with decal custom dimension" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } layout={layout} type={type} legend="none" data={data} elements={[ { type: type === 'combo' ? 'bar' : type, id: 'Series_01', axis: axisSeries01, }, { type: type === 'combo' ? 'line' : type, id: 'Series_02', axis: axisSeries02, }, ]} /> </Flex> ); }
Range
ChartGraph automatically sets the minimum and maximum axis values. The range
prop allows adjusting them in case we need broader range values in the axis.
Props: range
import { useState } from 'react'; import { Flex, RadioGroup } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const [type, setType] = useState('bar'); const data = [ { name: 'A', Percentage: 20, Absolute: 2000, }, { name: 'B', Percentage: 40, Absolute: 3000, }, { name: 'C', Percentage: 80, Absolute: 4000, }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <RadioGroup legend="ChartGraph type" direction="row" id="range"> <RadioGroup.RadioButton checked={type === 'bar'} id="range-bar" label="Bar" name="bar" onChange={() => setType('bar')} value="bar" size="sm" /> <RadioGroup.RadioButton checked={type === 'line'} id="range-line" label="Line" name="line" onChange={() => setType('line')} value="line" size="sm" /> <RadioGroup.RadioButton checked={type === 'combo'} id="range-combo" label="Combo" name="combo" onChange={() => setType('combo')} value="combo" size="sm" /> </RadioGroup> <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of range in charts" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } layout="verticalBiaxial" type={type} data={data} range={{ yAxisLeft: [0, 100] }} elements={[ { type: type === 'combo' ? 'bar' : type, id: 'Percentage', axis: 'left', }, { type: type === 'combo' ? 'line' : type, id: 'Absolute', axis: 'right', }, ]} /> </Flex> ); }
Responsive
ChartGraph is responsive. ChartGraph's width adjusts to the parent container. In order to render properly, ChartGraph requires a parent container with set dimensions.
For vertical layouts, ChartGraph has a set height based on the amount of ticks (five or three). For horizontal layouts, ChartGraph has a set height.
When ChartGraphs are contained within small containers (under 576px wide), set initialTicks={3}
to prevent ChartGraph to flick.
import { useId, useRef, useState } from 'react'; import { Box, Flex, Label, RadioGroup, Text } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const labelId = useId(); const [width, setWidth] = useState(700); const scrollContainerRef = useRef(); const updateWidth = ({ target }) => { setWidth(Number(target.value)); }; const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const [type, setType] = useState('bar'); const [layout, setLayout] = useState('horizontal'); let axisSeries01; let axisSeries02; if (layout === 'horizontalBiaxial') { axisSeries01 = 'bottom'; axisSeries02 = 'top'; } if (layout === 'verticalBiaxial') { axisSeries01 = 'left'; axisSeries02 = 'right'; } const data = [ { name: 'A', Series_01: 100, Series_02: 200, }, { name: 'B', Series_01: 200, Series_02: 300, }, ]; return ( <Box padding={2} width="100%"> <Flex direction="column" gap={5} width="100%"> <Flex width="100%" justifyContent="between"> <RadioGroup legend="ChartGraph type" direction="row" id="responsive-type" > <RadioGroup.RadioButton checked={type === 'bar'} id="responsive-type-bar" label="Bar" name="bar" onChange={() => setType('bar')} value="bar" size="sm" /> <RadioGroup.RadioButton checked={type === 'line'} id="responsive-type-line" label="Line" name="line" onChange={() => setType('line')} value="line" size="sm" /> <RadioGroup.RadioButton checked={type === 'combo'} id="responsive-type-combo" label="Combo" name="combo" onChange={() => setType('combo')} value="combo" size="sm" /> </RadioGroup> <RadioGroup legend="Layout" direction="row" id="responsive_layout"> <RadioGroup.RadioButton checked={layout === 'horizontal'} id="responsive_layout-horizontal" label="Horizontal" name="horizontal" onChange={() => setLayout('horizontal')} value="horizontal" size="sm" /> <RadioGroup.RadioButton checked={layout === 'horizontalBiaxial'} id="responsive_layout-horizontalBiaxial" label="HorizontalBiaxial" name="horizontalBiaxial" onChange={() => setLayout('horizontalBiaxial')} value="horizontalBiaxial" size="sm" /> <RadioGroup.RadioButton checked={layout === 'vertical'} id="responsive_layout-vertical" label="Vertical" name="vertical" onChange={() => setLayout('vertical')} value="vertical" size="sm" /> <RadioGroup.RadioButton checked={layout === 'verticalBiaxial'} id="responsive_layout-verticalBiaxial" label="VerticalBiaxial" name="verticalBiaxial" onChange={() => setLayout('verticalBiaxial')} value="verticalBiaxial" size="sm" /> </RadioGroup> </Flex> <Flex alignItems="center" direction="column"> <Flex> <Label htmlFor={labelId}> <Text>Container Width</Text> </Label> <Text>{`: ${width}px`}</Text> </Flex> <input id={labelId} type="range" defaultValue={800} onChange={updateWidth} min={200} max={800} step={1} style={{ width: '400px', display: 'block', margin: '10px auto' }} /> </Flex> <div ref={(el) => { scrollContainerRef.current = el; }} style={{ height: '300px', margin: '0 auto', outline: '3px solid #ddd', overflowY: 'scroll', width: `${width}px`, }} > <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of chart with decal custom dimension" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } layout={layout} type={type} legend="none" data={data} elements={[ { type: type === 'combo' ? 'bar' : type, id: 'Series_01', axis: axisSeries01, }, { type: type === 'combo' ? 'line' : type, id: 'Series_02', axis: axisSeries02, }, ]} /> </div> </Flex> </Box> ); }
Selectors
import { useState } from 'react'; import { TileData } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const dataA = [ { name: 'A', Impressions: 100, }, { name: 'B', Impressions: 200, }, { name: 'C', Impressions: 300, }, ]; const dataB = [ { name: 'A', Engagement: 90, }, { name: 'B', Engagement: 180, }, { name: 'C', Engagement: 250, }, ]; const [selectedId, setSelectedId] = useState('01'); const isSelected = (id) => selectedId === id; const getColor = (value) => { const colorMap = { '01': '01', '02': '02', '03': '03', '04': '04', '05': '05', '06': '06', '07': '07', '08': '08', '09': '09', 10: '10', 11: '11', 12: '12', }; return colorMap[value]; }; return ( <ChartGraph accessibilityLabel="Example of Bar chart" visualPatternSelected="disabled" onVisualPatternChange={() => {}} type="bar" title="Title" initialTicks={3} legend="none" description="Description" data={selectedId === '01' ? dataA : dataB} elements={[ { type: 'bar', id: selectedId === '01' ? 'Impressions' : 'Engagement', color: getColor(selectedId || '01'), }, ]} > <TileData id="01" color="01" title="Impressions" value="10M" selected={isSelected('01')} onTap={({ id }) => setSelectedId(id)} trend={{ value: 29, accessibilityLabel: 'Trending up' }} /> <TileData id="02" color="02" title="Engagement" value="2M" selected={isSelected('02')} onTap={({ id }) => setSelectedId(id)} trend={{ value: 29, accessibilityLabel: 'Trending up' }} /> </ChartGraph> ); }
import { useEffect, useState } from 'react'; import { TagData } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const [elements, setElements] = useState([]); const [selectedId, setSelectedId] = useState(['Campaign Autumn']); const isSelected = (id) => selectedId.includes(id); const getColor = (value) => { const colorMap = { '01': '01', '02': '02', '03': '03', '04': '04', '05': '05', '06': '06', '07': '07', '08': '08', '09': '09', 10: '10', 11: '11', 12: '12', }; return colorMap[value]; }; const handleSelectors = ({ id, selected }) => { if (!selected) { setSelectedId((values) => values.filter((idValue) => idValue !== id)); } if (selected) { setSelectedId((idValues) => [...idValues, id]); } }; useEffect(() => { const elementsArray = [ { type: 'bar', id: 'Campaign Autumn', color: getColor('01'), }, { type: 'bar', id: 'Campaign Winter', color: getColor('02'), }, { type: 'bar', id: 'Campaign Spring', color: getColor('03'), }, ]; const newElements = selectedId .map((idToMap) => elementsArray.filter(({ id }) => id === idToMap)) .flat(); setElements(newElements); }, [selectedId]); return ( <ChartGraph accessibilityLabel="Example of Bar chart" visualPatternSelected={visualPatternSelected} initialTicks={3} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } type="bar" title="Clickthroughs per region" legend="none" data={[ { name: 'NorthWest', 'Campaign Autumn': 100, 'Campaign Winter': 90, 'Campaign Spring': 10, }, { name: 'Sunbelt', 'Campaign Autumn': 200, 'Campaign Winter': 180, 'Campaign Spring': 50, }, { name: 'East Coast', 'Campaign Autumn': 300, 'Campaign Winter': 250, 'Campaign Spring': 100, }, ]} elements={elements} > <TagData id="Campaign Autumn" color="01" text="Campaign Autumn" selected={isSelected('Campaign Autumn')} onTap={({ id, selected }) => handleSelectors({ id, selected })} showCheckbox /> <TagData id="Campaign Winter" color="02" text="Campaign Winter" selected={isSelected('Campaign Winter')} onTap={({ id, selected }) => handleSelectors({ id, selected })} showCheckbox /> <TagData id="Campaign Spring" color="03" text="Campaign Spring" selected={isSelected('Campaign Spring')} onTap={({ id, selected }) => handleSelectors({ id, selected })} showCheckbox /> </ChartGraph> ); }
Tick format
ChartGraph allows to format the values in the axis' ticks. For example, to translate numeric values to shorter ones (1000 => 1k) or to format dates based on locale.
Props: tickFormatter
.
When localizing dates, use tickFormatter.timestamps
as it traslates the values in the tooltip as well. Avoid using comma separators for dates as .csv files use them for cell separation. tickFormatter.xAxisBottom
overrides tickFormatter.timeseries
when both are present, in case tooltip and x axis present different date formats.
import { ChartGraph } from 'gestalt-charts'; export default function Example() { const data = [ { name: 'A', Series_01: 1000300, }, { name: 'B', Series_01: 2000600, }, { name: 'C', Series_01: 3001200, }, ]; return ( <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of Bar chart" visualPatternSelected="disabled" onVisualPatternChange={() => {}} type="bar" legend="none" data={data} tickFormatter={{ yAxisLeft: (value) => `${value / 1000000}M` }} elements={[{ type: 'bar', id: 'Series_01' }]} /> ); }
Time series
ChartGraph supports timeseries. To enable timeseries, set tickFormatter.timeseries
. Time series charts are only supported in vertical layout.
Props: tickFormatter.timeseries
.
import { useState } from 'react'; import { FixedZIndex, Flex, RadioGroup } from 'gestalt'; import { ChartGraph } from 'gestalt-charts'; export default function Example() { const [visualPatternSelected, setVisualPatternSelected] = useState('default'); const [type, setType] = useState('line'); const data = [ { name: new Date(2023, 0, 1).getTime(), Series_01: 1000, Series_02: 100 }, { name: new Date(2023, 0, 2).getTime(), Series_01: 1005, Series_02: 200 }, { name: new Date(2023, 0, 3).getTime(), Series_01: 1003, Series_02: 150 }, { name: new Date(2023, 0, 4).getTime(), Series_01: 1100, Series_02: 130 }, { name: new Date(2023, 0, 5).getTime(), Series_01: 1030, Series_02: 147 }, { name: new Date(2023, 0, 6).getTime(), Series_01: 1089, Series_02: 189 }, { name: new Date(2023, 0, 7).getTime(), Series_01: 1065, Series_02: 118 }, { name: new Date(2023, 0, 8).getTime(), Series_01: 1090, Series_02: 177 }, ]; return ( <Flex height="100%" width="100%" direction="column" gap={2}> <RadioGroup legend="ChartGraph type" direction="row" id="timeseries"> <RadioGroup.RadioButton checked={type === 'bar'} id="timeseries-bar" label="Bar" name="bar" onChange={() => setType('bar')} value="bar" size="sm" /> <RadioGroup.RadioButton checked={type === 'line'} id="timeseries-line" label="Line" name="line" onChange={() => setType('line')} value="line" size="sm" /> <RadioGroup.RadioButton checked={type === 'combo'} id="timeseries-combo" label="Combo" name="combo" onChange={() => setType('combo')} value="combo" size="sm" /> </RadioGroup> <ChartGraph title="ChartGraph" titleDisplay="hidden" accessibilityLabel="Example of time series chart" visualPatternSelected={visualPatternSelected} onVisualPatternChange={() => setVisualPatternSelected((value) => value === 'default' ? 'visualPattern' : 'default' ) } data={data} range={{ xAxisBottom: ['auto', 'auto'], }} elements={[ { type: type === 'combo' ? 'bar' : type, id: 'Series_01' }, { type: type === 'combo' ? 'line' : type, id: 'Series_02' }, ]} type={type} tickFormatter={{ timeseries: (date) => `${new Intl.DateTimeFormat('en-US', { month: 'short' }).format( date )}-${new Intl.DateTimeFormat('en-US', { day: '2-digit' }).format( date )}`, }} modalZIndex={new FixedZIndex(11)} /> </Flex> ); }
Writing
- Keep labels short so that they don’t wrap and make it hard to read data
- Use abbreviations that are commonly understood and can be translated to all supported languages. For more on abbreviations, see our Content standards .
- Create extra-long labels that have to wrap or truncate
- Use abbreviations that are only understood internally or that don't translate well.
Component quality checklist
Quality item | Status | Status description |
---|---|---|
Figma Library | Ready | Component is available in Figma for web and mobile web. |
Responsive Web | Ready | Component responds to changing viewport sizes in web and mobile web. |
Related
Data visualization guidelines
Principles, use cases and guidelines for charts, graphs and micro-visualizations
TagData
TagData enables people to select multiple categories to compare with each other in a graph or chart.
TileData
TileData enables users to select multiple categories to compare with each other in a graph or chart view, while still being able to see all of the data points.
Table
Tables show data that's more complex and granular.