diff --git a/frontend/src/metabase/internal/pages/StaticVizPage.jsx b/frontend/src/metabase/internal/pages/StaticVizPage.jsx
index 1dc03e51fa1fafed3e87f169c3f5a2a40dd640db..8c986c56c0047040d0abb05d9661a06578765830 100644
--- a/frontend/src/metabase/internal/pages/StaticVizPage.jsx
+++ b/frontend/src/metabase/internal/pages/StaticVizPage.jsx
@@ -17,7 +17,181 @@ export default function StaticVizPage() {
           /static-viz/ and see the effects. You might need to hard refresh to
           see updates.
         </Text>
+        <Box py={3}>
+          <Subhead>Line chart with timeseries data</Subhead>
+          <StaticChart
+            type="timeseries/line"
+            options={{
+              data: [
+                ["2020-01-10", 10],
+                ["2020-06-10", 60],
+                ["2020-12-10", 80],
+              ],
+              accessors: {
+                x: row => new Date(row[0]).valueOf(),
+                y: row => row[1],
+              },
+              labels: {
+                left: "Count",
+                bottom: "Created At",
+              },
+            }}
+          />
+        </Box>
+        <Box py={3}>
+          <Subhead>Area chart with timeseries data</Subhead>
+          <StaticChart
+            type="timeseries/area"
+            options={{
+              data: [
+                ["2020-01-10", 10],
+                ["2020-06-10", 60],
+                ["2020-12-10", 80],
+              ],
+              accessors: {
+                x: row => new Date(row[0]).valueOf(),
+                y: row => row[1],
+              },
+              settings: {
+                x: {
+                  date_style: "MMM",
+                },
+              },
+              labels: {
+                left: "Count",
+                bottom: "Created At",
+              },
+              colors: {
+                brand: "#88BF4D",
+              },
+            }}
+          />
+        </Box>
+        <Box py={3}>
+          <Subhead>Bar chart with timeseries data</Subhead>
+          <StaticChart
+            type="timeseries/bar"
+            options={{
+              data: [
+                ["2020-10-21", 20],
+                ["2020-10-22", 30],
+                ["2020-10-23", 25],
+                ["2020-10-24", 10],
+                ["2020-10-25", 15],
+              ],
+              accessors: {
+                x: row => new Date(row[0]).valueOf(),
+                y: row => row[1],
+              },
+              settings: {
+                x: {
+                  date_style: "MM/DD/YYYY",
+                },
+                y: {
+                  number_style: "currency",
+                  currency: "USD",
+                  currency_style: "symbol",
+                  decimals: 0,
+                },
+              },
+              labels: {
+                left: "Price",
+                bottom: "Created At",
+              },
+            }}
+          />
+        </Box>
 
+        <Box py={3}>
+          <Subhead>Line chart with categorical data</Subhead>
+          <StaticChart
+            type="categorical/line"
+            options={{
+              data: [
+                ["Alden Sparks", 70],
+                ["Areli Guerra", 30],
+                ["Arturo Hopkins", 80],
+                ["Beatrice Lane", 120],
+                ["Brylee Davenport", 100],
+                ["Cali Nixon", 60],
+                ["Dane Terrell", 150],
+                ["Deshawn Rollins", 40],
+                ["Isabell Bright", 70],
+                ["Kaya Rowe", 20],
+                ["Roderick Herman", 50],
+                ["Ruth Dougherty", 75],
+              ],
+              accessors: {
+                x: row => row[0],
+                y: row => row[1],
+              },
+              labels: {
+                left: "Tasks",
+                bottom: "People",
+              },
+            }}
+          />
+        </Box>
+        <Box py={3}>
+          <Subhead>Area chart with categorical data</Subhead>
+          <StaticChart
+            type="categorical/area"
+            options={{
+              data: [
+                ["Alden Sparks", 70],
+                ["Areli Guerra", 30],
+                ["Arturo Hopkins", 80],
+                ["Beatrice Lane", 120],
+                ["Brylee Davenport", 100],
+                ["Cali Nixon", 60],
+                ["Dane Terrell", 150],
+                ["Deshawn Rollins", 40],
+                ["Isabell Bright", 70],
+                ["Kaya Rowe", 20],
+                ["Roderick Herman", 50],
+                ["Ruth Dougherty", 75],
+              ],
+              accessors: {
+                x: row => row[0],
+                y: row => row[1],
+              },
+              labels: {
+                left: "Tasks",
+                bottom: "People",
+              },
+            }}
+          />
+        </Box>
+        <Box py={3}>
+          <Subhead>Bar chart with categorical data</Subhead>
+          <StaticChart
+            type="categorical/bar"
+            options={{
+              data: [
+                ["Alden Sparks", 70],
+                ["Areli Guerra", 30],
+                ["Arturo Hopkins", 80],
+                ["Beatrice Lane", 120],
+                ["Brylee Davenport", 100],
+                ["Cali Nixon", 60],
+                ["Dane Terrell", 150],
+                ["Deshawn Rollins", 40],
+                ["Isabell Bright", 70],
+                ["Kaya Rowe", 20],
+                ["Roderick Herman", 50],
+                ["Ruth Dougherty", 75],
+              ],
+              accessors: {
+                x: row => row[0],
+                y: row => row[1],
+              },
+              labels: {
+                left: "Tasks",
+                bottom: "People",
+              },
+            }}
+          />
+        </Box>
         <Box py={3}>
           <Subhead>Donut chart with categorical data</Subhead>
           <StaticChart
diff --git a/frontend/src/metabase/static-viz/components/CategoricalAreaChart/CategoricalAreaChart.jsx b/frontend/src/metabase/static-viz/components/CategoricalAreaChart/CategoricalAreaChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5df42bd7b48a765b6c895ba861d1c20a7616729
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalAreaChart/CategoricalAreaChart.jsx
@@ -0,0 +1,163 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { GridRows } from "@visx/grid";
+import { scaleBand, scaleLinear } from "@visx/scale";
+import { AreaClosed, LinePath } from "@visx/shape";
+import { Text } from "@visx/text";
+import {
+  getXTickWidth,
+  getXTickLabelProps,
+  getYTickLabelProps,
+  getYTickWidth,
+  getRotatedXTickHeight,
+  getLabelProps,
+} from "../../lib/axes";
+import { formatNumber } from "../../lib/numbers";
+import { truncateText } from "../../lib/text";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func.isRequired,
+    y: PropTypes.func.isRequired,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  barPadding: 0.2,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  maxTickWidth: 100,
+  areaOpacity: 0.2,
+  strokeDasharray: "4",
+};
+
+const CategoricalAreaChart = ({ data, accessors, settings, labels }) => {
+  const colors = settings?.colors;
+  const isVertical = data.length > 10;
+  const xTickWidth = getXTickWidth(
+    data,
+    accessors,
+    layout.maxTickWidth,
+    layout.font.size,
+  );
+  const xTickHeight = getRotatedXTickHeight(xTickWidth);
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const xLabelOffset = xTickHeight + layout.labelPadding + layout.font.size;
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMin = isVertical ? xLabelOffset : layout.margin.bottom;
+  const yMax = layout.height - yMin;
+  const innerWidth = xMax - xMin;
+  const textBaseline = Math.floor(layout.font.size / 2);
+  const leftLabel = labels?.left;
+  const bottomLabel = !isVertical ? labels?.bottom : undefined;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleBand({
+    domain: data.map(accessors.x),
+    range: [xMin, xMax],
+    round: true,
+    padding: layout.barPadding,
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  const getXTickProps = ({ x, y, formattedValue, ...props }) => {
+    const textWidth = isVertical ? xTickWidth : xScale.bandwidth();
+    const truncatedText = truncateText(
+      formattedValue,
+      textWidth,
+      layout.font.size,
+    );
+    const transform = isVertical
+      ? `rotate(45, ${x} ${y}) translate(-${textBaseline} 0)`
+      : undefined;
+
+    return { ...props, x, y, transform, children: truncatedText };
+  };
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      <AreaClosed
+        data={data}
+        yScale={yScale}
+        fill={palette.brand}
+        opacity={layout.areaOpacity}
+        x={d => xScale(accessors.x(d)) + xScale.bandwidth() / 2}
+        y={d => yScale(accessors.y(d))}
+      />
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <LinePath
+        data={data}
+        stroke={palette.brand}
+        strokeWidth={layout.strokeWidth}
+        x={d => xScale(accessors.x(d)) + xScale.bandwidth() / 2}
+        y={d => yScale(accessors.y(d))}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={data.length}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickComponent={props => <Text {...getXTickProps(props)} />}
+        tickLabelProps={() => getXTickLabelProps(layout, isVertical)}
+      />
+    </svg>
+  );
+};
+
+CategoricalAreaChart.propTypes = propTypes;
+
+export default CategoricalAreaChart;
diff --git a/frontend/src/metabase/static-viz/components/CategoricalAreaChart/index.js b/frontend/src/metabase/static-viz/components/CategoricalAreaChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f57673cb6a5794ded8347e5090413b7be93e9905
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalAreaChart/index.js
@@ -0,0 +1 @@
+export { default } from "./CategoricalAreaChart";
diff --git a/frontend/src/metabase/static-viz/components/CategoricalBarChart/CategoricalBarChart.jsx b/frontend/src/metabase/static-viz/components/CategoricalBarChart/CategoricalBarChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..bcd586c60dff2c911d153283aa494d427fd2306a
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalBarChart/CategoricalBarChart.jsx
@@ -0,0 +1,160 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { GridRows } from "@visx/grid";
+import { scaleBand, scaleLinear } from "@visx/scale";
+import { Bar } from "@visx/shape";
+import { Text } from "@visx/text";
+import {
+  getXTickWidth,
+  getXTickLabelProps,
+  getYTickLabelProps,
+  getYTickWidth,
+  getRotatedXTickHeight,
+  getLabelProps,
+} from "../../lib/axes";
+import { formatNumber } from "../../lib/numbers";
+import { truncateText } from "../../lib/text";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func.isRequired,
+    y: PropTypes.func.isRequired,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  barPadding: 0.2,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  maxTickWidth: 100,
+  strokeDasharray: "4",
+};
+
+const CategoricalBarChart = ({ data, accessors, settings, labels }) => {
+  const colors = settings?.colors;
+  const isVertical = data.length > 10;
+  const xTickWidth = getXTickWidth(
+    data,
+    accessors,
+    layout.maxTickWidth,
+    layout.font.size,
+  );
+  const xTickHeight = getRotatedXTickHeight(xTickWidth);
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const xLabelOffset = xTickHeight + layout.labelPadding + layout.font.size;
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMin = isVertical ? xLabelOffset : layout.margin.bottom;
+  const yMax = layout.height - yMin;
+  const innerWidth = xMax - xMin;
+  const innerHeight = yMax - layout.margin.top;
+  const textBaseline = Math.floor(layout.font.size / 2);
+  const leftLabel = labels?.left;
+  const bottomLabel = !isVertical ? labels?.bottom : undefined;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleBand({
+    domain: data.map(accessors.x),
+    range: [xMin, xMax],
+    round: true,
+    padding: layout.barPadding,
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  const getBarProps = d => {
+    const width = xScale.bandwidth();
+    const height = innerHeight - yScale(accessors.y(d));
+    const x = xScale(accessors.x(d));
+    const y = yMax - height;
+
+    return { x, y, width, height, fill: palette.brand };
+  };
+
+  const getXTickProps = ({ x, y, formattedValue, ...props }) => {
+    const textWidth = isVertical ? xTickWidth : xScale.bandwidth();
+    const truncatedText = truncateText(
+      formattedValue,
+      textWidth,
+      layout.font.size,
+    );
+    const transform = isVertical
+      ? `rotate(45, ${x} ${y}) translate(-${textBaseline} 0)`
+      : undefined;
+
+    return { ...props, x, y, transform, children: truncatedText };
+  };
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      {data.map((d, index) => (
+        <Bar key={index} {...getBarProps(d)} />
+      ))}
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={data.length}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickComponent={props => <Text {...getXTickProps(props)} />}
+        tickLabelProps={() => getXTickLabelProps(layout, isVertical)}
+      />
+    </svg>
+  );
+};
+
+CategoricalBarChart.propTypes = propTypes;
+
+export default CategoricalBarChart;
diff --git a/frontend/src/metabase/static-viz/components/CategoricalBarChart/index.js b/frontend/src/metabase/static-viz/components/CategoricalBarChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..74a0f3cb5bcf04aa23becf9afa26ea01741fbf4b
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalBarChart/index.js
@@ -0,0 +1 @@
+export { default } from "./CategoricalBarChart";
diff --git a/frontend/src/metabase/static-viz/components/CategoricalLineChart/CategoricalLineChart.jsx b/frontend/src/metabase/static-viz/components/CategoricalLineChart/CategoricalLineChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..feff35025dd3bd79d1b193f9c31d3f6f4ed5a534
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalLineChart/CategoricalLineChart.jsx
@@ -0,0 +1,154 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { GridRows } from "@visx/grid";
+import { scaleBand, scaleLinear } from "@visx/scale";
+import { LinePath } from "@visx/shape";
+import { Text } from "@visx/text";
+import {
+  getXTickWidth,
+  getXTickLabelProps,
+  getYTickLabelProps,
+  getYTickWidth,
+  getRotatedXTickHeight,
+  getLabelProps,
+} from "../../lib/axes";
+import { formatNumber } from "../../lib/numbers";
+import { truncateText } from "../../lib/text";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func.isRequired,
+    y: PropTypes.func.isRequired,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  barPadding: 0.2,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  maxTickWidth: 100,
+  strokeDasharray: "4",
+};
+
+const CategoricalLineChart = ({ data, accessors, settings, labels }) => {
+  const colors = settings?.colors;
+  const isVertical = data.length > 10;
+  const xTickWidth = getXTickWidth(
+    data,
+    accessors,
+    layout.maxTickWidth,
+    layout.font.size,
+  );
+  const xTickHeight = getRotatedXTickHeight(xTickWidth);
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const xLabelOffset = xTickHeight + layout.labelPadding + layout.font.size;
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMin = isVertical ? xLabelOffset : layout.margin.bottom;
+  const yMax = layout.height - yMin;
+  const innerWidth = xMax - xMin;
+  const textBaseline = Math.floor(layout.font.size / 2);
+  const leftLabel = labels?.left;
+  const bottomLabel = !isVertical ? labels?.bottom : undefined;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleBand({
+    domain: data.map(accessors.x),
+    range: [xMin, xMax],
+    round: true,
+    padding: layout.barPadding,
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  const getXTickProps = ({ x, y, formattedValue, ...props }) => {
+    const textWidth = isVertical ? xTickWidth : xScale.bandwidth();
+    const truncatedText = truncateText(
+      formattedValue,
+      textWidth,
+      layout.font.size,
+    );
+    const transform = isVertical
+      ? `rotate(45, ${x} ${y}) translate(-${textBaseline} 0)`
+      : undefined;
+
+    return { ...props, x, y, transform, children: truncatedText };
+  };
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      <LinePath
+        data={data}
+        stroke={palette.brand}
+        strokeWidth={layout.strokeWidth}
+        x={d => xScale(accessors.x(d)) + xScale.bandwidth() / 2}
+        y={d => yScale(accessors.y(d))}
+      />
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={data.length}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickComponent={props => <Text {...getXTickProps(props)} />}
+        tickLabelProps={() => getXTickLabelProps(layout, isVertical)}
+      />
+    </svg>
+  );
+};
+
+CategoricalLineChart.propTypes = propTypes;
+
+export default CategoricalLineChart;
diff --git a/frontend/src/metabase/static-viz/components/CategoricalLineChart/index.js b/frontend/src/metabase/static-viz/components/CategoricalLineChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..c6ab45b493ba70c1456a68f583d756115b8d988b
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/CategoricalLineChart/index.js
@@ -0,0 +1 @@
+export { default } from "./CategoricalLineChart";
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/TimeSeriesAreaChart.jsx b/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/TimeSeriesAreaChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dab4072cf1cb0a61f085dcbd74255eb38c0c4184
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/TimeSeriesAreaChart.jsx
@@ -0,0 +1,139 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import { GridRows } from "@visx/grid";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { AreaClosed, LinePath } from "@visx/shape";
+import {
+  getLabelProps,
+  getXTickLabelProps,
+  getYTickLabelProps,
+  getYTickWidth,
+} from "../../lib/axes";
+import { formatDate } from "../../lib/dates";
+import { formatNumber } from "../../lib/numbers";
+import { sortTimeSeries } from "../../lib/sort";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func,
+    y: PropTypes.func,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    brandLight: "#DDECFA",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  numTicks: 5,
+  strokeWidth: 2,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  areaOpacity: 0.2,
+  strokeDasharray: "4",
+};
+
+const TimeSeriesAreaChart = ({ data, accessors, settings, labels }) => {
+  data = sortTimeSeries(data);
+  const colors = settings?.colors;
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMax = layout.height - layout.margin.bottom;
+  const innerWidth = xMax - xMin;
+  const leftLabel = labels?.left;
+  const bottomLabel = labels?.bottom;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleTime({
+    domain: [
+      Math.min(...data.map(accessors.x)),
+      Math.max(...data.map(accessors.x)),
+    ],
+    range: [xMin, xMax],
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      <AreaClosed
+        data={data}
+        yScale={yScale}
+        fill={palette.brand}
+        opacity={layout.areaOpacity}
+        x={d => xScale(accessors.x(d))}
+        y={d => yScale(accessors.y(d))}
+      />
+      <LinePath
+        data={data}
+        stroke={palette.brand}
+        strokeWidth={layout.strokeWidth}
+        x={d => xScale(accessors.x(d))}
+        y={d => yScale(accessors.y(d))}
+      />
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={layout.numTicks}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatDate(value, settings?.x)}
+        tickLabelProps={() => getXTickLabelProps(layout)}
+      />
+    </svg>
+  );
+};
+
+TimeSeriesAreaChart.propTypes = propTypes;
+
+export default TimeSeriesAreaChart;
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/index.js b/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..97b1d21fca2e79a02f01faa17c661e4c35057c23
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesAreaChart/index.js
@@ -0,0 +1 @@
+export { default } from "./TimeSeriesAreaChart";
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/TimeSeriesBarChart.jsx b/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/TimeSeriesBarChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ef188346ab458db0908dc227e84de5c33b43caf
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/TimeSeriesBarChart.jsx
@@ -0,0 +1,134 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { GridRows } from "@visx/grid";
+import { scaleBand, scaleLinear } from "@visx/scale";
+import { Bar } from "@visx/shape";
+import {
+  getLabelProps,
+  getXTickLabelProps,
+  getYTickLabelProps,
+  getYTickWidth,
+} from "../../lib/axes";
+import { formatDate } from "../../lib/dates";
+import { formatNumber } from "../../lib/numbers";
+import { sortTimeSeries } from "../../lib/sort";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func.isRequired,
+    y: PropTypes.func.isRequired,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  numTicks: 5,
+  barPadding: 0.2,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  strokeDasharray: "4",
+};
+
+const TimeSeriesBarChart = ({ data, accessors, settings, labels }) => {
+  data = sortTimeSeries(data);
+  const colors = settings?.colors;
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMax = layout.height - layout.margin.bottom;
+  const innerWidth = xMax - xMin;
+  const innerHeight = yMax - layout.margin.top;
+  const leftLabel = labels?.left;
+  const bottomLabel = labels?.bottom;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleBand({
+    domain: data.map(accessors.x),
+    range: [xMin, xMax],
+    round: true,
+    padding: layout.barPadding,
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  const getBarProps = d => {
+    const width = xScale.bandwidth();
+    const height = innerHeight - yScale(accessors.y(d));
+    const x = xScale(accessors.x(d));
+    const y = yMax - height;
+
+    return { x, y, width, height, fill: palette.brand };
+  };
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      {data.map((d, index) => (
+        <Bar key={index} {...getBarProps(d)} />
+      ))}
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={layout.numTicks}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatDate(value, settings?.x)}
+        tickLabelProps={() => getXTickLabelProps(layout)}
+      />
+    </svg>
+  );
+};
+
+TimeSeriesBarChart.propTypes = propTypes;
+
+export default TimeSeriesBarChart;
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/index.js b/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..b105a05e679569c5abadb5b391442481800caca6
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesBarChart/index.js
@@ -0,0 +1 @@
+export { default } from "./TimeSeriesBarChart";
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/TimeSeriesLineChart.jsx b/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/TimeSeriesLineChart.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..eb3dfdc50afe246ec8bb872a2dec766d001a2316
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/TimeSeriesLineChart.jsx
@@ -0,0 +1,129 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import { GridRows } from "@visx/grid";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+import { LinePath } from "@visx/shape";
+import {
+  getXTickLabelProps,
+  getYTickWidth,
+  getYTickLabelProps,
+  getLabelProps,
+} from "../../lib/axes";
+import { formatDate } from "../../lib/dates";
+import { formatNumber } from "../../lib/numbers";
+import { sortTimeSeries } from "../../lib/sort";
+
+const propTypes = {
+  data: PropTypes.array.isRequired,
+  accessors: PropTypes.shape({
+    x: PropTypes.func,
+    y: PropTypes.func,
+  }).isRequired,
+  settings: PropTypes.shape({
+    x: PropTypes.object,
+    y: PropTypes.object,
+    colors: PropTypes.object,
+  }),
+  labels: PropTypes.shape({
+    left: PropTypes.string,
+    bottom: PropTypes.string,
+  }),
+};
+
+const layout = {
+  width: 540,
+  height: 300,
+  margin: {
+    top: 0,
+    left: 55,
+    right: 40,
+    bottom: 40,
+  },
+  font: {
+    size: 11,
+    family: "Lato, sans-serif",
+  },
+  colors: {
+    brand: "#509ee3",
+    textLight: "#b8bbc3",
+    textMedium: "#949aab",
+  },
+  numTicks: 5,
+  labelFontWeight: 700,
+  labelPadding: 12,
+  strokeWidth: 2,
+  strokeDasharray: "4",
+};
+
+const TimeSeriesLineChart = ({ data, accessors, settings, labels }) => {
+  data = sortTimeSeries(data);
+  const colors = settings?.colors;
+  const yTickWidth = getYTickWidth(data, accessors, settings, layout.font.size);
+  const yLabelOffset = yTickWidth + layout.labelPadding;
+  const xMin = yLabelOffset + layout.font.size * 1.5;
+  const xMax = layout.width - layout.margin.right;
+  const yMax = layout.height - layout.margin.bottom;
+  const innerWidth = xMax - xMin;
+  const leftLabel = labels?.left;
+  const bottomLabel = labels?.bottom;
+  const palette = { ...layout.colors, ...colors };
+
+  const xScale = scaleTime({
+    domain: [
+      Math.min(...data.map(accessors.x)),
+      Math.max(...data.map(accessors.x)),
+    ],
+    range: [xMin, xMax],
+  });
+
+  const yScale = scaleLinear({
+    domain: [0, Math.max(...data.map(accessors.y))],
+    range: [yMax, 0],
+    nice: true,
+  });
+
+  return (
+    <svg width={layout.width} height={layout.height}>
+      <GridRows
+        scale={yScale}
+        left={xMin}
+        width={innerWidth}
+        strokeDasharray={layout.strokeDasharray}
+      />
+      <LinePath
+        data={data}
+        stroke={palette.brand}
+        strokeWidth={layout.strokeWidth}
+        x={d => xScale(accessors.x(d))}
+        y={d => yScale(accessors.y(d))}
+      />
+      <AxisLeft
+        scale={yScale}
+        left={xMin}
+        label={leftLabel}
+        labelOffset={yLabelOffset}
+        hideTicks
+        hideAxisLine
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatNumber(value, settings?.y)}
+        tickLabelProps={() => getYTickLabelProps(layout)}
+      />
+      <AxisBottom
+        scale={xScale}
+        top={yMax}
+        label={bottomLabel}
+        numTicks={layout.numTicks}
+        stroke={palette.textLight}
+        tickStroke={palette.textLight}
+        labelProps={getLabelProps(layout)}
+        tickFormat={value => formatDate(value, settings?.x)}
+        tickLabelProps={() => getXTickLabelProps(layout)}
+      />
+    </svg>
+  );
+};
+
+TimeSeriesLineChart.propTypes = propTypes;
+
+export default TimeSeriesLineChart;
diff --git a/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/index.js b/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..d23115fed8d48444c9258a97b9bb5f2011290cfa
--- /dev/null
+++ b/frontend/src/metabase/static-viz/components/TimeSeriesLineChart/index.js
@@ -0,0 +1 @@
+export { default } from "./TimeSeriesLineChart";
diff --git a/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.jsx b/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.jsx
index 8722e4840f1e1f57dac7d5246e90c8a1aa9c8b6e..742d84aae226d14dff52d928c392415df53d4447 100644
--- a/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.jsx
+++ b/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.jsx
@@ -1,7 +1,13 @@
 import React from "react";
 import PropTypes from "prop-types";
+import CategoricalAreaChart from "../../components/CategoricalAreaChart";
+import CategoricalBarChart from "../../components/CategoricalBarChart";
 import CategoricalDonutChart from "../../components/CategoricalDonutChart";
+import CategoricalLineChart from "../../components/CategoricalLineChart";
 import CategoricalWaterfallChart from "../../components/CategoricalWaterfallChart";
+import TimeSeriesAreaChart from "../../components/TimeSeriesAreaChart";
+import TimeSeriesBarChart from "../../components/TimeSeriesBarChart";
+import TimeSeriesLineChart from "../../components/TimeSeriesLineChart";
 import ProgressBar from "../../components/ProgressBar";
 import Funnel from "../../components/FunnelChart";
 import TimeSeriesWaterfallChart from "../../components/TimeSeriesWaterfallChart";
@@ -9,8 +15,14 @@ import LineAreaBarChart from "../../components/LineAreaBarChart";
 
 const propTypes = {
   type: PropTypes.oneOf([
+    "categorical/area",
+    "categorical/bar",
     "categorical/donut",
+    "categorical/line",
     "categorical/waterfall",
+    "timeseries/area",
+    "timeseries/bar",
+    "timeseries/line",
     "timeseries/waterfall",
     "progress",
     "combo-chart",
@@ -21,10 +33,22 @@ const propTypes = {
 
 const StaticChart = ({ type, options }) => {
   switch (type) {
+    case "categorical/area":
+      return <CategoricalAreaChart {...options} />;
+    case "categorical/bar":
+      return <CategoricalBarChart {...options} />;
     case "categorical/donut":
       return <CategoricalDonutChart {...options} />;
+    case "categorical/line":
+      return <CategoricalLineChart {...options} />;
     case "categorical/waterfall":
       return <CategoricalWaterfallChart {...options} />;
+    case "timeseries/area":
+      return <TimeSeriesAreaChart {...options} />;
+    case "timeseries/bar":
+      return <TimeSeriesBarChart {...options} />;
+    case "timeseries/line":
+      return <TimeSeriesLineChart {...options} />;
     case "timeseries/waterfall":
       return <TimeSeriesWaterfallChart {...options} />;
     case "progress":
diff --git a/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.unit.spec.js b/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.unit.spec.js
index d5aa8326d544e68f52f5e73fc17b2a9d6cd7bcfb..93d5dd8c25abcc59cce899fd97613fd8f0e0b7b5 100644
--- a/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.unit.spec.js
+++ b/frontend/src/metabase/static-viz/containers/StaticChart/StaticChart.unit.spec.js
@@ -3,6 +3,108 @@ import { render, screen } from "@testing-library/react";
 import StaticChart from "./StaticChart";
 
 describe("StaticChart", () => {
+  it("should render categorical/line", () => {
+    render(
+      <StaticChart
+        type="categorical/line"
+        options={{
+          data: [
+            ["Gadget", 20],
+            ["Widget", 31],
+          ],
+          accessors: {
+            x: row => row[0],
+            y: row => row[1],
+          },
+          settings: {
+            y: {
+              number_style: "currency",
+              currency: "USD",
+              currency_style: "symbol",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Category",
+          },
+        }}
+      />,
+    );
+
+    screen.getByText("Gadget");
+    screen.getByText("Widget");
+    screen.getAllByText("Count");
+    screen.getAllByText("Category");
+  });
+
+  it("should render categorical/area", () => {
+    render(
+      <StaticChart
+        type="categorical/area"
+        options={{
+          data: [
+            ["Gadget", 20],
+            ["Widget", 31],
+          ],
+          accessors: {
+            x: row => row[0],
+            y: row => row[1],
+          },
+          settings: {
+            y: {
+              number_style: "currency",
+              currency: "USD",
+              currency_style: "symbol",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Category",
+          },
+        }}
+      />,
+    );
+
+    screen.getByText("Gadget");
+    screen.getByText("Widget");
+    screen.getAllByText("Count");
+    screen.getAllByText("Category");
+  });
+
+  it("should render categorical/bar", () => {
+    render(
+      <StaticChart
+        type="categorical/bar"
+        options={{
+          data: [
+            ["Gadget", 20],
+            ["Widget", 31],
+          ],
+          accessors: {
+            x: row => row[0],
+            y: row => row[1],
+          },
+          settings: {
+            y: {
+              number_style: "currency",
+              currency: "USD",
+              currency_style: "symbol",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Category",
+          },
+        }}
+      />,
+    );
+
+    screen.getByText("Gadget");
+    screen.getByText("Widget");
+    screen.getAllByText("Count");
+    screen.getAllByText("Category");
+  });
+
   it("should render categorical/donut", () => {
     render(
       <StaticChart
@@ -34,4 +136,94 @@ describe("StaticChart", () => {
     screen.getByText("$5,100.00");
     screen.getAllByText("TOTAL");
   });
+
+  it("should render timeseries/line", () => {
+    render(
+      <StaticChart
+        type="timeseries/line"
+        options={{
+          data: [
+            ["2010-11-07", 20],
+            ["2020-11-08", 30],
+          ],
+          accessors: {
+            x: row => new Date(row[0]).valueOf(),
+            y: row => row[1],
+          },
+          settings: {
+            x: {
+              date_style: "dddd",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Time",
+          },
+        }}
+      />,
+    );
+
+    screen.getAllByText("Count");
+    screen.getAllByText("Time");
+  });
+
+  it("should render timeseries/area", () => {
+    render(
+      <StaticChart
+        type="timeseries/area"
+        options={{
+          data: [
+            ["2010-11-07", 20],
+            ["2020-11-08", 30],
+          ],
+          accessors: {
+            x: row => new Date(row[0]).valueOf(),
+            y: row => row[1],
+          },
+          settings: {
+            x: {
+              date_style: "MMM",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Time",
+          },
+        }}
+      />,
+    );
+
+    screen.getAllByText("Count");
+    screen.getAllByText("Time");
+  });
+
+  it("should render timeseries/bar", () => {
+    render(
+      <StaticChart
+        type="timeseries/bar"
+        options={{
+          data: [
+            ["2010-11-07", 20],
+            ["2020-11-08", 30],
+          ],
+          accessors: {
+            x: row => new Date(row[0]).valueOf(),
+            y: row => row[1],
+          },
+          settings: {
+            x: {
+              date_style: "dddd",
+            },
+          },
+          labels: {
+            left: "Count",
+            bottom: "Time",
+          },
+        }}
+      />,
+    );
+
+    screen.getAllByText("Count");
+    screen.getAllByText("Time");
+  });
 });
diff --git a/frontend/test/metabase-visual/internal/static-viz.cy.spec.js b/frontend/test/metabase-visual/internal/static-viz.cy.spec.js
index fa63560ce7b3ec5fab84796c23426fbdfe44089a..b5f372308b5d8ac5167388bc4b77ab57a7287bbc 100644
--- a/frontend/test/metabase-visual/internal/static-viz.cy.spec.js
+++ b/frontend/test/metabase-visual/internal/static-viz.cy.spec.js
@@ -9,7 +9,8 @@ describe("visual tests > internal > static-viz", () => {
   it("basic charts", () => {
     cy.visit("/_internal/static-viz");
 
-    cy.findByText("Waterfall chart with categorical data and total");
+    cy.findByText("Bar chart with timeseries data");
+    cy.findByText("Line chart with timeseries data");
     cy.findByText("Donut chart with categorical data");
 
     cy.percySnapshot();