Skip to content

Diverging Stacked Bar Charts ​

This example demonstrates how to create stacked bar charts for Likert-scale survey data using vertical bars. The original Observable Plot example (https://observablehq.com/@observablehq/plot-diverging-stacked-bar) uses horizontal bars with custom offset functions.

Note: These examples use vertical stacked bars (type: bar with stack: y) because Observable Plot's horizontal bar stacking (barX) requires different data structures that don't work well with pre-aggregated count data. For survey visualizations, vertical stacking provides equivalent functionality and follows the proven pattern from DataGlass's working examples.

Limitations and Workarounds ​

Features from the original Observable Plot example NOT currently supported in DataGlass:

  1. Custom offset functions - The original uses a custom "Likert" offset that centers neutral responses around the midpoint. DataGlass only supports built-in offsets: expand, center, normalize, wiggle, silhouette.

  2. groupZ transform with custom reducers - The original uses Plot.groupZ({x: "count"}, {...}) to automatically aggregate and count responses. DataGlass requires pre-aggregated data with explicit counts.

  3. Custom tick formatters - The original uses x: {tickFormat: Math.abs} to show absolute values on both sides of zero. DataGlass uses standard tick formatting.

Workarounds available in DataGlass:

  • Pre-aggregate your data to count responses per category
  • Use offset: "center" or offset: "normalize" for symmetric/proportion effects
  • Use offset: "expand" for 100% normalized stacks (same as normalize)
  • Use faceting (fy:) to show multiple survey questions as small multiples
  • Apply diverging color scales with manual color ranges to emphasize positive vs negative sentiment

Important Note: Observable Plot's barX automatically stacks bars horizontally when you provide a fill channel that creates groups. The offset parameter controls how the stacks are positioned.

Basic Survey Data Structure ​

For Likert-scale surveys, data should have:

  • A question identifier (e.g., "Q1", "Q2")
  • A response category ("Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree")
  • A count of responses in each category

Example 1: Simple Vertical Stacked Bar (Pre-aggregated Data) ​

This shows a single survey question with response counts stacked vertically.

View Source
data:
  source: [
    {"question": "Q1", "response": "Strongly Disagree", "count": 12},
    {"question": "Q1", "response": "Disagree", "count": 18},
    {"question": "Q1", "response": "Neutral", "count": 15},
    {"question": "Q1", "response": "Agree", "count": 32},
    {"question": "Q1", "response": "Strongly Agree", "count": 23}
  ]
engine: plot
title: Employee Satisfaction Survey (Q1)
width: 800
height: 500
marks:
  - type: bar
    configuration:
      x: question
      y: count
      fill: response
      stack: y
      tip: true
scales:
  color:
    type: ordinal
    domain: ["Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"]
    range: ["#d73027", "#fc8d59", "#fee090", "#91bfdb", "#4575b4"]
marginLeft: 80
marginRight: 50

Example 2: Normalized Vertical Stacked Bar (100%) ​

Shows proportions to compare response distributions.

View Source
data:
  source: [
    {"question": "Q1: Job Satisfaction", "response": "Strongly Disagree", "count": 12},
    {"question": "Q1: Job Satisfaction", "response": "Disagree", "count": 18},
    {"question": "Q1: Job Satisfaction", "response": "Neutral", "count": 15},
    {"question": "Q1: Job Satisfaction", "response": "Agree", "count": 32},
    {"question": "Q1: Job Satisfaction", "response": "Strongly Agree", "count": 23},
    {"question": "Q2: Work-Life Balance", "response": "Strongly Disagree", "count": 8},
    {"question": "Q2: Work-Life Balance", "response": "Disagree", "count": 22},
    {"question": "Q2: Work-Life Balance", "response": "Neutral", "count": 20},
    {"question": "Q2: Work-Life Balance", "response": "Agree", "count": 28},
    {"question": "Q2: Work-Life Balance", "response": "Strongly Agree", "count": 22},
    {"question": "Q3: Career Growth", "response": "Strongly Disagree", "count": 15},
    {"question": "Q3: Career Growth", "response": "Disagree", "count": 25},
    {"question": "Q3: Career Growth", "response": "Neutral", "count": 18},
    {"question": "Q3: Career Growth", "response": "Agree", "count": 24},
    {"question": "Q3: Career Growth", "response": "Strongly Agree", "count": 18}
  ]

engine: plot
title: Employee Survey Responses (Normalized)
width: 800
height: 500
marks:
  - type: bar
    configuration:
      x: question
      y: count
      fill: response
      stack: y
      offset: normalize
      tip: true
scales:
  color:
    type: ordinal
    domain: ["Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"]
    range: ["#d73027", "#fc8d59", "#fee090", "#91bfdb", "#4575b4"]
  y:
    label: Proportion of Responses
marginLeft: 180
marginRight: 50
marginBottom: 80

Example 3: Faceted Survey Questions ​

Shows multiple questions using small multiples with chart-level faceting.

View Source
engine: plot
title: Employee Engagement Survey Results
width: 800
height: 750
facet:
  y: question
scales:
  color:
    type: ordinal
    domain: ["Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"]
    range: ["#d73027", "#fc8d59", "#fee090", "#91bfdb", "#4575b4"]
    legend: true
  x:
    label: Number of Responses
marks:
  - type: barX
    configuration:
      x: count
      y: response
      fill: response
      tip: true
      offset: normalize
marginLeft: 50
marginRight: 50
data:
  source: [
    {"question": "Job Satisfaction", "response": "Strongly Disagree", "count": 12},
    {"question": "Job Satisfaction", "response": "Disagree", "count": 18},
    {"question": "Job Satisfaction", "response": "Neutral", "count": 15},
    {"question": "Job Satisfaction", "response": "Agree", "count": 32},
    {"question": "Job Satisfaction", "response": "Strongly Agree", "count": 23},
    {"question": "Work-Life Balance", "response": "Strongly Disagree", "count": 8},
    {"question": "Work-Life Balance", "response": "Disagree", "count": 22},
    {"question": "Work-Life Balance", "response": "Neutral", "count": 20},
    {"question": "Work-Life Balance", "response": "Agree", "count": 28},
    {"question": "Work-Life Balance", "response": "Strongly Agree", "count": 22},
    {"question": "Management Quality", "response": "Strongly Disagree", "count": 10},
    {"question": "Management Quality", "response": "Disagree", "count": 15},
    {"question": "Management Quality", "response": "Neutral", "count": 22},
    {"question": "Management Quality", "response": "Agree", "count": 30},
    {"question": "Management Quality", "response": "Strongly Agree", "count": 23},
    {"question": "Career Growth", "response": "Strongly Disagree", "count": 15},
    {"question": "Career Growth", "response": "Disagree", "count": 25},
    {"question": "Career Growth", "response": "Neutral", "count": 18},
    {"question": "Career Growth", "response": "Agree", "count": 24},
    {"question": "Career Growth", "response": "Strongly Agree", "count": 18},
    {"question": "Company Culture", "response": "Strongly Disagree", "count": 6},
    {"question": "Company Culture", "response": "Disagree", "count": 12},
    {"question": "Company Culture", "response": "Neutral", "count": 16},
    {"question": "Company Culture", "response": "Agree", "count": 35},
    {"question": "Company Culture", "response": "Strongly Agree", "count": 31}
  ]

Example 4: Customer Satisfaction Vertical Bars ​

This example uses vertical stacked bars with diverging colors to emphasize positive vs negative sentiment.

View Source
data:
  source: [
    {"category": "Product Quality", "response": "Very Dissatisfied", "count": 8},
    {"category": "Product Quality", "response": "Dissatisfied", "count": 12},
    {"category": "Product Quality", "response": "Neutral", "count": 15},
    {"category": "Product Quality", "response": "Satisfied", "count": 42},
    {"category": "Product Quality", "response": "Very Satisfied", "count": 23},
    {"category": "Customer Service", "response": "Very Dissatisfied", "count": 15},
    {"category": "Customer Service", "response": "Dissatisfied", "count": 20},
    {"category": "Customer Service", "response": "Neutral", "count": 18},
    {"category": "Customer Service", "response": "Satisfied", "count": 28},
    {"category": "Customer Service", "response": "Very Satisfied", "count": 19},
    {"category": "Value for Money", "response": "Very Dissatisfied", "count": 12},
    {"category": "Value for Money", "response": "Dissatisfied", "count": 18},
    {"category": "Value for Money", "response": "Neutral", "count": 22},
    {"category": "Value for Money", "response": "Satisfied", "count": 32},
    {"category": "Value for Money", "response": "Very Satisfied", "count": 16},
    {"category": "Delivery Speed", "response": "Very Dissatisfied", "count": 5},
    {"category": "Delivery Speed", "response": "Dissatisfied", "count": 10},
    {"category": "Delivery Speed", "response": "Neutral", "count": 12},
    {"category": "Delivery Speed", "response": "Satisfied", "count": 38},
    {"category": "Delivery Speed", "response": "Very Satisfied", "count": 35}
  ]
engine: plot
title: Customer Satisfaction Survey
width: 800
height: 500
marks:
  - type: bar
    configuration:
      x: category
      y: count
      fill: response
      stack: y
      offset: normalize
      tip: true
scales:
  color:
    type: ordinal
    domain: ["Very Dissatisfied", "Dissatisfied", "Neutral", "Satisfied", "Very Satisfied"]
    range: ["#ca0020", "#f4a582", "#f7f7f7", "#92c5de", "#0571b0"]
    legend: true
  y:
    label: Response Distribution
marginLeft: 150
marginRight: 50
marginBottom: 80

Example 5: Product Feedback Vertical Bars with Centered Offset ​

Using vertical stacked bars with the center offset to create a more symmetric visualization.

View Source
data:
  source: [
    {"feature": "Ease of Use", "rating": "Poor", "count": 5},
    {"feature": "Ease of Use", "rating": "Below Average", "count": 8},
    {"feature": "Ease of Use", "rating": "Average", "count": 12},
    {"feature": "Ease of Use", "rating": "Good", "count": 35},
    {"feature": "Ease of Use", "rating": "Excellent", "count": 40},
    {"feature": "Performance", "rating": "Poor", "count": 12},
    {"feature": "Performance", "rating": "Below Average", "count": 15},
    {"feature": "Performance", "rating": "Average", "count": 18},
    {"feature": "Performance", "rating": "Good", "count": 30},
    {"feature": "Performance", "rating": "Excellent", "count": 25},
    {"feature": "Design", "rating": "Poor", "count": 3},
    {"feature": "Design", "rating": "Below Average", "count": 7},
    {"feature": "Design", "rating": "Average", "count": 15},
    {"feature": "Design", "rating": "Good", "count": 38},
    {"feature": "Design", "rating": "Excellent", "count": 37},
    {"feature": "Documentation", "rating": "Poor", "count": 18},
    {"feature": "Documentation", "rating": "Below Average", "count": 22},
    {"feature": "Documentation", "rating": "Average", "count": 20},
    {"feature": "Documentation", "rating": "Good", "count": 25},
    {"feature": "Documentation", "rating": "Excellent", "count": 15}
  ]
engine: plot
title: Product Feature Ratings (Centered)
width: 800
height: 500
marks:
  - type: bar
    configuration:
      x: feature
      y: count
      fill: rating
      stack: y
      offset: center
      tip: true
scales:
  color:
    type: ordinal
    domain: ["Poor", "Below Average", "Average", "Good", "Excellent"]
    range: ["#b2182b", "#ef8a62", "#fddbc7", "#67a9cf", "#2166ac"]
    legend: true
  y:
    label: Number of Responses
marginLeft: 150
marginRight: 50
marginBottom: 100

Tips for Effective Diverging Bar Charts ​

  1. Pre-aggregate your data - Count responses for each category before visualization
  2. Use consistent ordering - Always maintain the same order for Likert scales (negative to positive)
  3. Choose appropriate color schemes:
    • Red-Blue for temperature/sentiment (red = negative, blue = positive)
    • Red-Green for agree/disagree (avoid if color-blind accessibility is important)
    • Neutral colors in the middle (gray, yellow, white)
  4. Consider normalization - Use offset: "normalize" when comparing questions with different response counts
  5. Use faceting for multiple questions - The fy: parameter creates small multiples for easier comparison
  6. Add reference lines - Include a rule mark at the neutral point if needed
  7. Provide context - Include the number of total responses in titles or annotations
  • See stacked-areas.md for examples of stack transforms with areas
  • See diverging-color-examples.md for diverging color scale patterns
  • See complete-mark-showcase.md for more bar chart variations

Future Enhancements ​

When DataGlass adds support for custom offset functions, we'll be able to implement the exact Likert-style centering from the original Observable Plot example. For now, these workarounds provide effective alternatives for visualizing survey data.

Released under the MIT License. Built by Boundary Lab.