require 'json' require 'time' require 'erb' module Swagchart module Helper def default_chart_options(opts={}) @default_chart_options = opts end def chart(type, data, opts={}, &block) opts[:options] ||= {} opts[:options] = (@default_chart_options || {}).merge(opts[:options]) @google_visualization_included ||= false @chart_counter ||= 0 type = camelize(type.to_s) opts[:columns] = opts[:columns].split(',').map(&:strip) if opts[:columns].is_a?(String) data = data.to_a if data.is_a?(Hash) if data.respond_to?(:first) && data.first.is_a?(Hash) data = hash_array_to_data_table(data) opts.delete(:columns) elsif data.respond_to?(:unshift) && data.respond_to?(:first) && opts[:columns] data.unshift opts.delete(:columns) end chart_id = ERB::Util.html_escape(opts.delete(:chart_id) || "chart_#{@chart_counter += 1}") style = 'height:320px;' #dirty hack right here .. you can override that with your style though style << ERB::Util.html_escape(opts.delete(:style)) if opts[:style] html = '' unless @google_visualization_included #we only need to include this once html << jsapi_includes_template @google_visualization_included = true end options = opts.delete(:options) || {} html << chart_template(id: chart_id, type: type, style: style, options: options, data: data) html.respond_to?(:html_safe) ? html.html_safe : html end private def hash_array_to_data_table(ha) cols = ha.first.keys rows = ha.map{|r| cols.reduce([]){|s,e| s<" html << "google.setOnLoadCallback(function(){" chart_js = chart_js_template(opts) html << (opts[:data].is_a?(String) ? async_ajax_load_wrapper(opts[:data], chart_js) : chart_js) html << "});" html end def chart_js_template(opts={}) js = '' js << "var data = #{opts[:data]};" unless opts[:data].is_a?(String) js << autocast_data_template js << "var dt = google.visualization.arrayToDataTable(data);" js << "new google.visualization.#{opts[:type]}(document.getElementById('#{opts[:id]}')).draw(dt, #{opts[:options].to_json});" js end def async_ajax_load_wrapper(url, js_code) js = "var xhr = new XMLHttpRequest();" js << "xhr.onreadystatechange = function(){" js << "if(xhr.readyState === 4) { if(xhr.status>=200 && xhr.status<400){" js << "var data = JSON.parse(xhr.responseText);" js << js_code js << "}else{ console.log('Could not load data from #{url}. Status ' + xhr.status); }}};" js << "xhr.open('GET', '#{url}'); xhr.send(null);" js end def jsapi_includes_template html = "" html end end end