diff --git a/frontend/src/metabase/css/dashboard.css b/frontend/src/metabase/css/dashboard.css
index bfaaa02794393101cdf690faf01124d2efe43e6e..54685ac3faf7cb0c998a1ca5724ec6c0194fb40d 100644
--- a/frontend/src/metabase/css/dashboard.css
+++ b/frontend/src/metabase/css/dashboard.css
@@ -366,6 +366,16 @@
   background-color: color(var(--color-bg-white) alpha(-86%));
 }
 
+.Dashboard--night text.value-label-outline {
+  stroke: var(--night-mode-card);
+}
+
+.Dashboard text.value-label,
+.Dashboard text.value-label-outline,
+.Dashboard .LineAreaBarChart .dc-chart .axis text {
+  font-size: 12px;
+}
+
 .ScalarValue {
   font-weight: 900;
   font-size: 1.8rem;
diff --git a/frontend/src/metabase/css/query_builder.css b/frontend/src/metabase/css/query_builder.css
index 8e7c769f5240b5acca85c5cbea91707417386661..ff08afdcd93b8052137454975021e0b3ce2d87f5 100644
--- a/frontend/src/metabase/css/query_builder.css
+++ b/frontend/src/metabase/css/query_builder.css
@@ -692,23 +692,3 @@
 .ParameterValuePickerNoPopover input::-webkit-input-placeholder {
   color: var(--color-text-medium);
 }
-
-text.value-label-outline {
-  font-weight: 900;
-  stroke-width: 4px;
-  stroke: var(--color-text-white);
-}
-
-text.value-label {
-  fill: var(--color-text-dark);
-  font-weight: 900;
-}
-
-text.value-label-outline,
-text.value-label {
-  pointer-events: none;
-}
-
-.Dashboard--night text.value-label-outline {
-  stroke: var(--night-mode-card);
-}
diff --git a/frontend/src/metabase/visualizations/components/LineAreaBarChart.css b/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
index 2b4cacc6b847c4eb22b3099d7d83e647fab125e0..340c87758df31cf3d79a93c33127fcb5b72948c3 100644
--- a/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
+++ b/frontend/src/metabase/visualizations/components/LineAreaBarChart.css
@@ -206,3 +206,19 @@
 .LineAreaBarChart .dc-chart .trend .line {
   stroke-dasharray: 5, 5;
 }
+
+text.value-label-outline,
+text.value-label {
+  pointer-events: none;
+}
+
+text.value-label-outline {
+  font-weight: 800;
+  stroke-width: 4px;
+  stroke: var(--color-text-white);
+}
+
+text.value-label {
+  fill: var(--color-text-dark);
+  font-weight: 800;
+}
diff --git a/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js b/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
index 910a357ae1237546cabeb32d4923785eb676abd3..3edd8f8c189426b109f9dda05fe4a439370d1341 100644
--- a/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
+++ b/frontend/src/metabase/visualizations/lib/LineAreaBarPostRender.js
@@ -262,15 +262,17 @@ function onRenderValueLabels(chart, formatYValue, [data]) {
 
   // Update `data` to use named x/y and include `showLabelBelow`.
   // We need to do that before data is filtered to show every nth value.
-  data = data.map(([x, y], i) => {
-    const isLocalMin =
-      // first point or prior is greater than y
-      (i === 0 || data[i - 1][1] > y) &&
-      // last point point or next is greater than y
-      (i === data.length - 1 || data[i + 1][1] > y);
-    const showLabelBelow = isLocalMin && display === "line";
-    return { x, y, showLabelBelow };
-  });
+  data = data
+    .map(([x, y], i) => {
+      const isLocalMin =
+        // first point or prior is greater than y
+        (i === 0 || data[i - 1][1] > y) &&
+        // last point point or next is greater than y
+        (i === data.length - 1 || data[i + 1][1] > y);
+      const showLabelBelow = isLocalMin && display === "line";
+      return { x, y, showLabelBelow };
+    })
+    .filter(d => display !== "bar" || d.y !== 0);
 
   const formattingSetting = chart.settings["graph.label_value_formatting"];
   let compact;
diff --git a/frontend/src/metabase/visualizations/lib/apply_axis.js b/frontend/src/metabase/visualizations/lib/apply_axis.js
index 006b4c58c4ffc86f043225ea375fbc19210a70ef..ab07a1688d10512e3d24dcad8946b178e5cf2dfc 100644
--- a/frontend/src/metabase/visualizations/lib/apply_axis.js
+++ b/frontend/src/metabase/visualizations/lib/apply_axis.js
@@ -369,6 +369,33 @@ export function applyChartYAxis(chart, series, yExtent, axisName) {
     scale = d3.scale.linear();
   }
 
+  // This makes non-zero bar values take up at least one pixel.
+  // Ideally, we would just pass a custom interpolate factory to `interpolate`.
+  // However, dc.js passes its own after we give it the scael, so instead we
+  // overwrite the scale's interpolate method. That let's us use theirs but
+  // special case values withing one pixel of the edge.
+  if (series.every(s => s.card.display === "bar")) {
+    const _interpolate = scale.interpolate.bind(scale);
+    scale.interpolate = customInterpolatorFactory =>
+      _interpolate((a, b) => {
+        // dc.js uses a rounding interpolator. We want to use the factory they
+        // pass in, but we also need to create d3's default interpolator. We use
+        // that to see when a value is between 0 and 1. If we just looked at the
+        // rounded value, 0.49 would round to 0 and we wouldn't bump it up to 1.
+        const custom = customInterpolatorFactory(a, b);
+        const unrounded = d3.interpolate(a, b);
+        return t => {
+          const value = unrounded(t);
+          const onePixelUp = custom(0) - 1;
+          // y goes from top to bottom, so "onePixelUp" is actually the largest value
+          if (onePixelUp < value && value < unrounded(0)) {
+            return onePixelUp;
+          }
+          return custom(t);
+        };
+      });
+  }
+
   scale.clamp(true);
 
   if (axis.setting("auto_range")) {