HorizondalBarChartAppi
A professional horizontal bar chart widget that renders interactive bar charts using Chart.js, perfect for displaying categorical data, comparisons, and rankings in a horizontal layout that's ideal for long category names.
Overview
HorizondalBarChartAppi
provides an elegant solution for visualizing data with horizontal bars. Built on Chart.js and rendered through an iframe, it offers smooth animations, interactive features, and customizable styling that makes it perfect for displaying rankings, comparisons, and categorical data.
Features
- 📊 Horizontal Layout - Perfect for long category names and labels
- 🎨 Customizable Colors - Primary and secondary color schemes
- 🖱️ Interactive Elements - Hover effects and tooltips
- 📱 Responsive Design - Adapts to different screen sizes
- 🎯 Custom Titles - Configurable chart titles with styling
- 🔄 Smooth Animations - Animated bar rendering
- 🌐 Web Optimized - Uses Chart.js for optimal web performance
- 📈 Data Comparison - Excellent for comparing values across categories
- 🎪 Professional Styling - Clean, modern chart appearance
- ⚡ High Performance - Efficient rendering for various data sizes
Basic Usage
HorizondalBarChartAppi(
xData: ['Product A', 'Product B', 'Product C', 'Product D'],
yData: [120, 190, 300, 500],
title: 'Sales by Product',
primaryColor: '#36A2EB',
secondaryColor: '#FF6384',
interaction: true,
)
Properties
Property | Type | Default | Description |
---|---|---|---|
xData | List<String> | required | Category labels for the bars |
yData | List<int> | required | Data values for each category |
title | String? | null | Chart title |
titleStyle | TextStyle? | null | Styling for the chart title |
primaryColor | String? | '#36A2EB' | Primary color for bars (hex format) |
secondaryColor | String? | '#FF6384' | Secondary/accent color (hex format) |
interaction | bool? | true | Enable/disable chart interactions |
Examples
Basic Sales Chart
class SalesBarChart extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
height: 400,
padding: EdgeInsets.all(16),
child: HorizondalBarChartAppi(
xData: ['Q1 Sales', 'Q2 Sales', 'Q3 Sales', 'Q4 Sales'],
yData: [45000, 52000, 61000, 58000],
title: 'Quarterly Sales Performance',
titleStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
primaryColor: '#4CAF50',
secondaryColor: '#81C784',
interaction: true,
),
);
}
}
Employee Performance Dashboard
class EmployeePerformanceChart extends StatefulWidget {
_EmployeePerformanceChartState createState() => _EmployeePerformanceChartState();
}
class _EmployeePerformanceChartState extends State<EmployeePerformanceChart> {
List<String> employees = [
'Alice Johnson',
'Bob Smith',
'Carol Davis',
'David Wilson',
'Emma Brown',
'Frank Miller'
];
List<int> performanceScores = [92, 88, 95, 87, 91, 89];
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Employee Performance',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Q4 2024',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.w500,
fontSize: 12,
),
),
),
],
),
SizedBox(height: 8),
Text(
'Performance scores out of 100',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
SizedBox(height: 20),
Container(
height: 350,
child: HorizondalBarChartAppi(
xData: employees,
yData: performanceScores,
title: 'Performance Scores',
titleStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
primaryColor: '#2196F3',
secondaryColor: '#64B5F6',
interaction: true,
),
),
SizedBox(height: 16),
_buildPerformanceSummary(),
],
),
),
);
}
Widget _buildPerformanceSummary() {
double average = performanceScores.reduce((a, b) => a + b) / performanceScores.length;
int highest = performanceScores.reduce((a, b) => a > b ? a : b);
int lowest = performanceScores.reduce((a, b) => a < b ? a : b);
return Row(
children: [
Expanded(
child: _buildSummaryCard('Average', '${average.toStringAsFixed(1)}', Colors.blue),
),
SizedBox(width: 12),
Expanded(
child: _buildSummaryCard('Highest', '$highest', Colors.green),
),
SizedBox(width: 12),
Expanded(
child: _buildSummaryCard('Lowest', '$lowest', Colors.orange),
),
],
);
}
Widget _buildSummaryCard(String title, String value, Color color) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
}
Product Category Analysis
class ProductCategoryChart extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Product Category Performance',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 8),
Text(
'Units sold this month',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
SizedBox(height: 20),
Container(
height: 450,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 2,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Padding(
padding: EdgeInsets.all(16),
child: HorizondalBarChartAppi(
xData: [
'Electronics & Gadgets',
'Home & Garden',
'Fashion & Apparel',
'Sports & Outdoors',
'Books & Media',
'Health & Beauty',
'Automotive',
'Toys & Games'
],
yData: [1250, 890, 1420, 675, 340, 980, 520, 760],
title: 'Sales by Category',
titleStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
primaryColor: '#FF9800',
secondaryColor: '#FFB74D',
interaction: true,
),
),
),
SizedBox(height: 16),
_buildCategoryInsights(),
],
),
);
}
Widget _buildCategoryInsights() {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange[200]!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.insights, color: Colors.orange),
SizedBox(width: 8),
Text(
'Key Insights',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.orange[800],
),
),
],
),
SizedBox(height: 12),
_buildInsightItem(
'📈 Top Performer',
'Fashion & Apparel leads with 1,420 units sold',
Colors.green,
),
SizedBox(height: 8),
_buildInsightItem(
'📊 Growth Opportunity',
'Books & Media has potential for improvement',
Colors.blue,
),
SizedBox(height: 8),
_buildInsightItem(
'🎯 Target Focus',
'Electronics maintains strong second position',
Colors.purple,
),
],
),
);
}
Widget _buildInsightItem(String title, String description, Color color) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 4,
height: 40,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(2),
),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: color,
),
),
Text(
description,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
],
);
}
}
Survey Results Visualization
class SurveyResultsChart extends StatefulWidget {
_SurveyResultsChartState createState() => _SurveyResultsChartState();
}
class _SurveyResultsChartState extends State<SurveyResultsChart> {
Map<String, List<int>> surveyData = {
'Customer Satisfaction': [45, 32, 18, 12, 8],
'Product Quality': [52, 28, 15, 8, 5],
'Service Speed': [38, 35, 20, 10, 7],
'Value for Money': [42, 30, 22, 12, 6],
};
String selectedCategory = 'Customer Satisfaction';
List<String> ratingLabels = [
'Excellent (5 stars)',
'Good (4 stars)',
'Average (3 stars)',
'Poor (2 stars)',
'Very Poor (1 star)'
];
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Customer Survey Results',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 8),
Text(
'Based on 500 responses',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
SizedBox(height: 20),
// Category selector
Container(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: surveyData.keys.length,
itemBuilder: (context, index) {
String category = surveyData.keys.elementAt(index);
bool isSelected = category == selectedCategory;
return GestureDetector(
onTap: () {
setState(() {
selectedCategory = category;
});
},
child: Container(
margin: EdgeInsets.only(right: 12),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey[200],
borderRadius: BorderRadius.circular(25),
),
child: Text(
category,
style: TextStyle(
color: isSelected ? Colors.white : Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
),
);
},
),
),
SizedBox(height: 20),
Container(
height: 400,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue[50]!, Colors.white],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Padding(
padding: EdgeInsets.all(20),
child: HorizondalBarChartAppi(
xData: ratingLabels,
yData: surveyData[selectedCategory]!,
title: '$selectedCategory Ratings',
titleStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
primaryColor: '#3F51B5',
secondaryColor: '#7986CB',
interaction: true,
),
),
),
SizedBox(height: 20),
_buildSurveyStats(),
],
),
);
}
Widget _buildSurveyStats() {
List<int> currentData = surveyData[selectedCategory]!;
int totalResponses = currentData.reduce((a, b) => a + b);
double satisfactionRate = ((currentData[0] + currentData[1]) / totalResponses * 100);
return Row(
children: [
Expanded(
child: _buildStatCard(
'Total Responses',
'$totalResponses',
Icons.people,
Colors.blue,
),
),
SizedBox(width: 12),
Expanded(
child: _buildStatCard(
'Satisfaction Rate',
'${satisfactionRate.toStringAsFixed(1)}%',
Icons.thumb_up,
Colors.green,
),
),
SizedBox(width: 12),
Expanded(
child: _buildStatCard(
'Top Rating',
'${currentData[0]} votes',
Icons.star,
Colors.orange,
),
),
],
);
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
children: [
Icon(icon, color: color, size: 24),
SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
],
),
);
}
}
Regional Sales Comparison
class RegionalSalesChart extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Regional Sales',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.trending_up, size: 16, color: Colors.green),
SizedBox(width: 4),
Text(
'+12.5%',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
],
),
SizedBox(height: 8),
Text(
'Year-over-year growth by region',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
SizedBox(height: 20),
Container(
height: 400,
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[200]!),
),
child: Padding(
padding: EdgeInsets.all(16),
child: HorizondalBarChartAppi(
xData: [
'North America',
'Europe',
'Asia Pacific',
'Latin America',
'Middle East & Africa',
'Australia & Oceania'
],
yData: [2850, 2340, 3120, 1680, 1240, 890],
title: 'Sales by Region (in thousands)',
titleStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
primaryColor: '#00BCD4',
secondaryColor: '#4DD0E1',
interaction: true,
),
),
),
SizedBox(height: 16),
_buildRegionalLegend(),
],
),
);
}
Widget _buildRegionalLegend() {
List<Map<String, dynamic>> regions = [
{'name': 'Asia Pacific', 'growth': '+18.2%', 'color': Colors.green},
{'name': 'North America', 'growth': '+12.8%', 'color': Colors.blue},
{'name': 'Europe', 'growth': '+8.5%', 'color': Colors.orange},
{'name': 'Latin America', 'growth': '+15.3%', 'color': Colors.purple},
{'name': 'Middle East & Africa', 'growth': '+22.1%', 'color': Colors.red},
{'name': 'Australia & Oceania', 'growth': '+6.7%', 'color': Colors.teal},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Growth Rates',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 8,
children: regions.map((region) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: region['color'].withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: region['color'].withOpacity(0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: region['color'],
shape: BoxShape.circle,
),
),
SizedBox(width: 6),
Text(
region['name'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
SizedBox(width: 4),
Text(
region['growth'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: region['color'],
),
),
],
),
);
}).toList(),
),
],
);
}
}
Budget vs Actual Spending
class BudgetComparisonChart extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Budget vs Actual Spending',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 20),
Container(
height: 450,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red[50]!, Colors.white],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Padding(
padding: EdgeInsets.all(20),
child: HorizondalBarChartAppi(
xData: [
'Marketing & Advertising',
'Research & Development',
'Operations & Logistics',
'Human Resources',
'IT & Technology',
'Facilities & Maintenance',
'Legal & Compliance'
],
yData: [85, 92, 78, 88, 95, 82, 90],
title: 'Budget Utilization (%)',
titleStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
primaryColor: '#F44336',
secondaryColor: '#EF5350',
interaction: true,
),
),
),
SizedBox(height: 20),
_buildBudgetAlerts(),
],
),
);
}
Widget _buildBudgetAlerts() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Budget Alerts',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 12),
_buildAlertCard(
'Over Budget',
'IT & Technology exceeded budget by 5%',
Colors.red,
Icons.warning,
),
SizedBox(height: 8),
_buildAlertCard(
'Under Budget',
'Operations has 22% remaining budget',
Colors.green,
Icons.check_circle,
),
SizedBox(height: 8),
_buildAlertCard(
'On Track',
'Most departments within 10% of budget',
Colors.blue,
Icons.info,
),
],
);
}
Widget _buildAlertCard(String title, String description, Color color, IconData icon) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Row(
children: [
Icon(icon, color: color, size: 20),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
color: color,
),
),
Text(
description,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
],
),
);
}
}
Advanced Features
Dynamic Data Updates
class DynamicBarChart extends StatefulWidget {
_DynamicBarChartState createState() => _DynamicBarChartState();
}
class _DynamicBarChartState extends State<DynamicBarChart> {
Timer? _timer;
List<String> categories = ['Server 1', 'Server 2', 'Server 3', 'Server 4'];
List<int> currentData = [45, 67, 23, 89];
void initState() {
super.initState();
_startDataUpdates();
}
void _startDataUpdates() {
_timer = Timer.periodic(Duration(seconds: 3), (timer) {
setState(() {
currentData = currentData.map((value) {
int change = Random().nextInt(21) - 10; // -10 to +10
return (value + change).clamp(0, 100);
}).toList();
});
});
}
void dispose() {
_timer?.cancel();
super.dispose();
}
Widget build(BuildContext context) {
return Container(
height: 300,
child: HorizondalBarChartAppi(
xData: categories,
yData: currentData,
title: 'Real-time Server Load (%)',
primaryColor: '#9C27B0',
secondaryColor: '#BA68C8',
interaction: true,
),
);
}
}
Custom Color Gradients
class GradientBarChart extends StatelessWidget {
Widget build(BuildContext context) {
return HorizondalBarChartAppi(
xData: ['Excellent', 'Good', 'Average', 'Poor', 'Very Poor'],
yData: [45, 32, 18, 8, 3],
title: 'Customer Satisfaction',
primaryColor: '#4CAF50', // Green for positive
secondaryColor: '#FF5722', // Red for negative
titleStyle: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
interaction: true,
);
}
}
Chart Interactions
The interaction
parameter enables several interactive features:
- Hover Effects: Highlight bars on mouse hover
- Tooltips: Display detailed information for each bar
- Click Events: Handle bar selection (when implemented)
- Responsive Behavior: Adapt to different screen sizes
Color Customization
Hex Color Format
Colors should be provided in hex format:
primaryColor: '#FF5722' // Orange
secondaryColor: '#4CAF50' // Green
Color Schemes
Popular color combinations:
- Professional:
#2196F3
(Blue) +#64B5F6
(Light Blue) - Success:
#4CAF50
(Green) +#81C784
(Light Green) - Warning:
#FF9800
(Orange) +#FFB74D
(Light Orange) - Error:
#F44336
(Red) +#EF5350
(Light Red)
Best Practices
- Label Length: Horizontal layout is perfect for long category names
- Data Range: Ensure data values are appropriate for comparison
- Color Accessibility: Use high contrast colors for better readability
- Chart Height: Provide adequate height for all categories
- Responsive Design: Consider chart sizing for different devices
- Performance: Optimize for the number of categories displayed
Common Use Cases
- Rankings: Employee performance, product ratings, survey results
- Comparisons: Sales by region, budget vs actual, category analysis
- Progress Tracking: Goal completion, project milestones
- Survey Data: Customer satisfaction, feedback analysis
- Financial Data: Revenue by department, expense categories
- Performance Metrics: KPIs, system monitoring, efficiency measures
Related Widgets
- MultiLineChartAppi - For multi-line charts
- BoxAppi - For chart containers
- TextAppi - For chart titles and labels
Migration Notes
When upgrading from vertical bar charts:
- Consider label readability with horizontal layout
- Adjust chart height based on number of categories
- Update color schemes if needed
- Test responsive behavior across devices
Technical Notes
- Built on Chart.js for optimal web performance
- Rendered through iframe for security and isolation
- Supports responsive design out of the box
- Automatically handles data scaling and formatting
- Optimized for web deployment
Ready to visualize your data effectively? Combine with other chart widgets for comprehensive dashboards! for each data point