Professional charts from CSV, XLSX, and YAML data files. Zero dependencies, pure Ruby rendering using ruby-libgd.
RubyCharts.from_csv('sales.csv').type(:pie).title('Q1 Sales Distribution').save('report.png')✅ Multiple Chart Types
- Pie charts
- Vertical bar charts
- Horizontal bar charts
- Line charts
✅ Multiple Data Sources
- CSV files
- XLSX (Excel) spreadsheets
- YAML files
- Ruby hashes (inline data)
✅ Professional Styling
- Custom colors
- Titles and subtitles
- Automatic legends
- Text labels with FreeType rendering
- Grid lines and axes
✅ Performance
- Server-side rendering (100ms per chart)
- Zero external APIs
- Scalable to 1000+ charts/second
- Works offline
✅ Rails Integration
- Perfect for admin dashboards
- Embed in emails
- Generate reports in background jobs
- Serve via API
Add to your Gemfile:
gem'ruby-charts'Then bundle:
bundle installOr install directly:
gem install ruby-chartsruby-charts requires:
- Ruby 2.7+
- ruby-libgd (automatically installed)
- TrueType fonts (system fonts auto-detected)
Supported on:
- ✅ Linux (Ubuntu, Debian, CentOS)
- ✅ macOS
- ✅ Windows
require'ruby_charts'RubyCharts.from_csv('data.csv').type(:pie).title('Product Distribution').save('chart.png')CSV Format:
Category,ValueApple,150Banana,200Orange,120RubyCharts.from_xlsx('data.xlsx',sheet: 'Sales').type(:bar).title('Monthly Sales').save('chart.png')XLSX Format:
| Product | Sales |
|---|---|
| Apple | 150 |
| Banana | 200 |
RubyCharts.from_yaml('data.yml').type(:line).title('Growth Trend').save('chart.png')YAML Format:
data:
- name: Januaryvalue: 5000
- name: Februaryvalue: 7200
- name: Marchvalue: 6800data={labels: ['Q1','Q2','Q3','Q4'],values: [100,150,120,180]}RubyCharts.from_hash(data).type(:bar).title('Quarterly Performance').save('chart.png').type(:pie)# Pie chart.type(:bar)# Vertical bar chart.type(:vertical_bar)# Same as :bar.type(:horizontal_bar)# Horizontal bar chart.type(:line)# Line chart with pointsRubyCharts.from_csv('sales.csv').type(:pie).title('Sales Analysis').subtitle('Q1 2024').colors(['#FF6B6B','#4ECDC4','#45B7D1','#FFA07A']).legend(position: :right).font('/path/to/font.ttf').save('report.png')| Method | Type | Default | Description |
|---|---|---|---|
type() | Symbol | :bar | Chart type |
title() | String | 'Chart' | Chart title |
subtitle() | String | nil | Subtitle text |
colors() | Array | System colors | RGB color array: ['#FF6B6B', '#4ECDC4'] |
legend() | Symbol | :right | Legend position |
font() | String | Auto-detect | Path to TrueType font |
save() | String | N/A | Output filename |
require'ruby_charts'# CSV with product salescsv_data='product,salesApple,15000Banana,8500Orange,12000Mango,9800'File.write('sales.csv',csv_data)# Generate chartsRubyCharts.from_csv('sales.csv').type(:pie).title('Product Sales Distribution').subtitle('Month of May 2024').colors(['#FF6B6B','#4ECDC4','#45B7D1','#FFA07A']).save('sales_pie.png')RubyCharts.from_csv('sales.csv').type(:horizontal_bar).title('Product Sales Ranking').save('sales_bar.png')# Using CoinGecko to fetch Bitcoin datarequire'ruby_charts'require'httparty'response=HTTParty.get('https://api.coingecko.com/api/v3/coins/bitcoin/market_chart',query: {vs_currency: 'usd',days: 30})prices=response['prices']# Create datasetdata={labels: prices.map{ |p| Time.at(p[0]/1000).strftime('%m-%d')},values: prices.map{ |p| p[1]}}# Generate chartRubyCharts.from_hash(data).type(:line).title('Bitcoin 30-Day Price Trend').subtitle('Data from CoinGecko').colors(['#F7931A']).save('bitcoin_trend.png')# app/controllers/admin/dashboard_controller.rbclassAdmin::DashboardController < ApplicationControllerdefindex@sales=generate_sales_chart@revenue=generate_revenue_chartendprivatedefgenerate_sales_chartdata={labels: ['Product A','Product B','Product C'],values: [450,320,280]}RubyCharts.from_hash(data).type(:pie).title('Sales by Product').save("tmp/sales_#{Time.now.to_i}.png")enddefgenerate_revenue_chartmonths=Date.today.last_months(6).reverserevenue=[5000,7200,6800,8100,7500,9200]data={labels: months.map{ |d| d.strftime('%b')},values: revenue}RubyCharts.from_hash(data).type(:line).title('Revenue Trend').save("tmp/revenue_#{Time.now.to_i}.png")endend# app/mailers/report_mailer.rbclassReportMailer < ApplicationMailerdefmonthly_summary# Generate chartchart_file=generate_report_chart# Attach to emailattachments['report.png']=File.read(chart_file)# Send email with chartmail(to: @user.email,subject: 'Monthly Report')endprivatedefgenerate_report_chartdata=monthly_dataRubyCharts.from_hash(data).type(:bar).title("#{@user.name}'s Monthly Summary").save("tmp/report_#{@user.id}_#{Date.today}.png")endend# app/controllers/api/charts_controller.rbclassApi::ChartsController < ApplicationControllerdefgeneratesymbol=params[:symbol]data=fetch_price_data(symbol)# Generate chartfilename=RubyCharts.from_hash(data).type(:line).title("#{symbol} Price Chart").save("public/charts/#{symbol}.png")# Return imagesend_filefilename,type: 'image/png'endendDefault (10 colors):
['#FF6B6B',# Red'#4ECDC4',# Teal'#45B7D1',# Blue'#FFA07A',# Orange'#9AD8C8',# Mint'#F7DC6F',# Yellow'#BB8FCE',# Purple'#85C1E2',# Light Blue'#F8B88B',# Peach'#AAC6BA'# Gray]Use hex colors or RGB arrays:
# Hex colors.colors(['#FF6B6B','#4ECDC4','#45B7D1'])# RGB arrays.colors([[255,107,107],[78,205,196],[69,183,209]])# Mix both.colors(['#FF6B6B',[78,205,196]])| Operation | Time | Notes |
|---|---|---|
| Load CSV (1000 rows) | 15ms | Pandas-like speed |
| Render pie chart | 45ms | Per chart |
| Render bar chart | 35ms | Per chart |
| Render line chart | 50ms | Per chart |
| Save PNG | 20ms | Per file |
| Total (CSV→PNG) | ~110ms | End-to-end |
# Generate 100 charts in ~11 seconds100.timesdo |i|
RubyCharts.from_csv("data_#{i}.csv").type(:pie).save("output_#{i}.png")endIf text doesn't appear in charts:
# Check available fontsRubyCharts::Config.font(:regular)# => "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"# Specify custom fontRubyCharts.from_csv('data.csv').type(:pie).font('/path/to/your/font.ttf').save('chart.png')Font paths by OS:
- Linux:
/usr/share/fonts/truetype/ - macOS:
/Library/Fonts/ - Windows:
C:\Windows\Fonts\
Increase DPI or size:
# Larger chart size = sharper textRubyCharts.from_csv('data.csv').type(:pie)# Chart is 1200x700 by default# This is sufficient for most uses.save('chart.png')Make sure colors are valid hex or RGB:
# ✅ Valid.colors(['#FF6B6B','#4ECDC4']).colors([[255,107,107],[78,205,196]])# ❌ Invalid.colors(['red','blue'])# Color names not supportedEnsure the XLSX file is valid:
# ✅ CorrectRubyCharts.from_xlsx('data.xlsx',sheet: 'Sales')# ❌ Wrong sheet nameRubyCharts.from_xlsx('data.xlsx',sheet: 'NonExistent')# Specify correct sheet nameruby-charts depends on:
ruby-libgd# Native graphics bindingroo# XLSX readingcsv# CSV reading (stdlib)yaml# YAML reading (stdlib)That's it. No heavy frameworks, no external APIs.
Bug reports and pull requests are welcome! Please:
- Fork the repository
- Create a feature branch
- Test your changes
- Submit a pull request
- More chart types (stacked bars, scatter, area)
- Export formats (PDF, SVG)
- Data transformations (aggregation, filtering)
- Theme system
- Documentation improvements
- Bug fixes and performance improvements
See CONTRIBUTING.md for details.
MIT License - see LICENSE file for details.
ruby-charts is part of the ruby-libgd ecosystem:
ruby-libgd - Native graphics library for Ruby
libgd-gis - Geographic data processing
Germán Silva - @ggerman
- RubyStackNews - Ruby-focused newsletter (2,500+ subscribers)
- GitHub
- Open an issue
- Email: ggerman@gmail.com
- Discord: Ruby Community
Please open an issue with:
- Ruby version
- OS
- ruby-charts version
- Minimal code to reproduce
Open an issue and describe:
- Your use case
- Example code
- Why it's important
Made with ❤️ by someone who thinks Ruby deserves better graphics.
Questions? Issues? Ideas? Let's talk!