【问题标题】:Rails App Plotting Zeros in Highchart Graph via Lazy_High_Charts GemRails App 通过 Lazy_High_Charts Gem 在 Highchart 图中绘制零点
【发布时间】:2013-05-05 15:39:49
【问题描述】:

我对 Rails 还是很陌生,但我会说进展相当顺利。因此,为了练习,我正在开发一个简单的应用程序,用户可以在其中输入他们的体重,并使用 Lazy Highcharts gem 通过 Highcharts 折线图向他们显示 30 天内的信息。我跟着 Ryan Bates 的 Railscast #223 开始。

我的问题:
好的,所以输入显示,除了在用户不输入值的那一天,它在图表上显示为“0”(线下降到图表底部),而不是连接到下一个点在任何一天。不知道这一切是否有意义,所以这里有一个截图:

我找到了这个解决方案: Highcharts - Rails Array Includes Empty Date Entries

但是,当我实现在该页面上找到的第一个选项时(将 0 转换为 null):

(30.days.ago.to_date..Date.today).map { |date| wt = Weight.pounds_on(date).to_f; wt.zero? ? "null" : wt }

点显示,但线没有,工具提示也没有......这让我认为有些东西正在破坏 js。不过,控制台中显然没有任何问题..?从这里我认为这可能是使用 Highchart 的 'connectNulls' 选项的问题,但这也不起作用。实现第二个选项(拒绝方法)时:

(30.days.ago.to_date..Date.today).map { |date| Weight.pounds_on(date).to_f}.reject(&:zero?)

它从图表中完全删除所有为空的日期,这完全搞乱了结构,因为这些值应该根据它们的 created_at 日期显示。

所以回到第一个方格,这就是我剩下的(图表在没有输入的日子里绘制零)。

型号:

class Weight < ActiveRecord::Base
  attr_accessible :notes, :weight_input

  validates :weight_input, presence: true
  validates :user_id, presence: true

  belongs_to :user

  def self.pounds_on(date)
    where("date(created_at) = ?", date).pluck(:weight_input).last
  end
end

控制器:

def index
  @weights = current_user.weights.all

  @startdate = 30.days.ago.to_date
  @pounds = (30.days.ago.to_date..Date.today).map { |date| Weight.pounds_on(date).to_f }

  @h = LazyHighCharts::HighChart.new('graph') do |f|
    f.options[:title][:text] = " "
    f.options[:chart][:defaultSeriesType] = "area"
    f.options[:chart][:inverted] = false
    f.options[:chart][:zoomType] = 'x'
    f.options[:legend][:layout] = "horizontal"
    f.options[:legend][:borderWidth] = "0"
    f.series(:pointInterval => 1.day, :pointStart => @startdate, :name => 'Weight (lbs)', :color => "#2cc9c5", :data => @pounds )
    f.options[:xAxis] = {:minTickInterval => 1, :type => "datetime", :dateTimeLabelFormats => { day: "%b %e"}, :title => { :text => nil }, :labels => { :enabled => true } }
  end

  respond_to do |format|
    format.html # index.html.erb
    format.json { render json: @weights }
  end
end

有人对此有解决方案吗?我想我可能做错了,所以非常感谢任何帮助。

【问题讨论】:

    标签: ruby-on-rails highcharts lazy-high-charts


    【解决方案1】:

    这是 HighCharts 特定的。您需要将时间戳传递到您的数据数组中,而不是为数据集定义它。

    对于每个数据点,我将 [time,value] 设置为一个元组。
    [ "Date.UTC(#{date.year}, #{date.month}, #{date.day})" , Weight.pounds_on(date).to_f ]

    您仍需要以您喜欢的方式删除零,但数据将保留正确的日期值。

    我认为你需要删除 :pointInterval =&gt; 1.day

    一般提示 您还应该考虑优化您对 pound_on 的查询,因为您正在对图表上的每个点进行数据库调用。执行Weight.where(:created_at =&gt; date_start..date_end ).group("Date(created_at)").sum(:weight_input),它将为您提供一组 created_at 日期以及每天的总和。

    添加

    改进的 SQL 查询

    这依靠 sql 来做它最擅长的事情。首先,使用 where 将查询缩减为所需的记录(在本例中为过去 30 天)。选择您需要的字段(created_at 和 weight_input)。然后启动运行 sub_query 以按天对记录进行分组的内部联接,选择 created_at 的最大值。当它们匹配时,它会退回给定日期的最大(也就是最后输入的)重量输入。

    @last_weight_per_day = Weight.where(:created_at => 30.days.ago.beginning_of_day..Time.now.end_of_day)
    select("weights.created_at , weights.weight_input").
    joins(" 
      inner join (  
          SELECT weights.weight_input, max(weights.created_at) as max_date 
          FROM weights 
          GROUP BY weights.weight_input , date(weights.created_at) 
      ) weights_dates on weights.created_at =  weights_dates.max_date
    ")
    

    有了这个,你应该可以@last_weight_per_day 这样。假设您已经在数据库中验证了它们,那么这不应该有 0 / nil 值。而且也应该很快。

      @pounds = @last_weight_per_day.map{|date| date.weight_input.to_f}
    

    【讨论】:

    • 好的,开始了解它的设置。我觉得我很亲近!这是我根据您的建议所做的事情:@pounds = (30.days.ago.to_date..Date.today).map { |date| ["#{date.year}, #{date.month}, #{date.day}", Weight.pounds_on(date).to_i.zero? ? nil : Weight.pounds_on(date).to_f] }.compact 我确信这可以重构,但是通过这条线,点显示在正确的日期并且工具提示正在工作,但这条线没有从点到点显示。我使用紧凑方法删除 nil 但控制台显示 ["2013, 5, 4", null](not nil) 所以我不知道这是不是问题..
    • 关闭,但日期需要是实际的 Javascript 日期调用。您将需要对输出进行非转义,以便评估日期。调用 raw() 或 .html_safe。至于空。我会以不同的方式拉日期。如果你真的不想要这些日期......不要问他们。从数据库调用记录与迭代日期。 (指数更快)。我会为你挖掘一些代码。
    • 我在主要答案上为您添加了一个查询。它的主要目标是调用每天输入的最新值(重量输入),并将它们全部返回到重量对象数组中。
    • 谢谢,你帮了大忙!我玩了一下,最后得到了这个:@pounds = @last_weight_per_day.map { |date| [date.created_at.to_date.to_datetime.to_i * 1000,date.weight_input.to_f] }
    • 对于任何感兴趣的人,经过一点重构之后,这里是最终生成的块,正是我正在寻找的东西:@last_weight_per_day = current_user.weights. where(:created_at =&gt; 30.days.ago.beginning_of_day..Time.zone.now.end_of_day). group("date(weights.created_at)"). select("weights.created_at, weights.weight_input")@pounds = @last_weight_per_day.map { |date| [date.created_at.to_date.to_datetime.to_i * 1000,date.weight_input.to_f] } 感谢@burningpony 的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多