【问题标题】:Creating Google Charts API DataTable From Database of Temperatures and IDs从温度和 ID 数据库创建 Google Charts API DataTable
【发布时间】:2017-04-11 02:50:58
【问题描述】:

我想使用数据库中的水池温度数据创建一个Annotation Chart。您可以查看数据库结构 here on sqlfiddlehere on rextester,但为了节省您的点击,这是我正在使用的结构:

DROP TABLE IF EXISTS `temperatures`;
DROP TABLE IF EXISTS `pools`;

CREATE TABLE `pools` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `temperatures` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `pool_id` int(10) unsigned NOT NULL,
  `temperature` double(8,1) NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `temperatures_pool_id_foreign` (`pool_id`),
  CONSTRAINT `temperatures_pool_id_foreign` FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3173 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `pools` (`id`, `name`, `created_at`)
VALUES
    (1,'Pool #1','2017-04-08 22:48:03'),
    (2,'Pool #2','2017-04-08 22:48:03'),
    (3,'Pool #3','2017-04-08 22:48:03');

INSERT INTO `temperatures` (`id`, `pool_id`, `temperature`, `created_at`)
VALUES
    (31,1,100.1,'2017-04-09 02:44:56'),
    (32,2,104.2,'2017-04-09 02:44:56'),
    (33,3,97.0,'2017-04-09 02:44:56'),
    (34,1,100.1,'2017-04-09 03:00:04'),
    (35,2,98.4,'2017-04-09 03:00:04'),
    (36,3,96.6,'2017-04-09 03:00:04'),
    (37,1,100.1,'2017-04-09 03:37:13'),
    (38,2,101.8,'2017-04-09 03:37:13'),
    (39,3,96.4,'2017-04-09 03:37:13'),
    (40,1,100.1,'2017-04-09 04:00:04'),
    (41,2,101.8,'2017-04-09 04:00:04'),
    (42,3,96.5,'2017-04-09 04:00:04'),
    (43,1,100.1,'2017-04-09 05:00:04'),
    (44,2,101.8,'2017-04-09 05:00:04');

好的,基本上,我创建了一个控制器,它将返回格式正确的 JSON 以用于 ajax 和 google.visualization.DataTable(),如下所示:

var jsonData = $.ajax({
    url: "/data/pool-temperature-timeline",
    dataType: "json",
    async: false
}).responseText;

data = new google.visualization.DataTable(jsonData);
chart.draw(data, options);

当然,看文档,注解图是期望事情遵循这种格式:

var data = new google.visualization.DataTable();
data.addColumn('date', 'Date');
data.addColumn('number', 'Kepler-22b mission');
data.addColumn('string', 'Kepler title');
data.addColumn('string', 'Kepler text');
data.addColumn('number', 'Gliese 163 mission');
data.addColumn('string', 'Gliese title');
data.addColumn('string', 'Gliese text');
data.addRows([
    [new Date(2314, 2, 15), 12400, undefined, undefined,
                            10645, undefined, undefined],
    [new Date(2314, 2, 16), 24045, 'Lalibertines', 'First encounter',
                            12374, undefined, undefined],
    [new Date(2314, 2, 17), 35022, 'Lalibertines', 'They are very tall',
                            15766, 'Gallantors', 'First Encounter'],
    [new Date(2314, 2, 18), 12284, 'Lalibertines', 'Attack on our crew!',
                            34334, 'Gallantors', 'Statement of shared principles'],
    [new Date(2314, 2, 19), 8476, 'Lalibertines', 'Heavy casualties',
                            66467, 'Gallantors', 'Mysteries revealed'],
    [new Date(2314, 2, 20), 0, 'Lalibertines', 'All crew lost',
                            79463, 'Gallantors', 'Omniscience achieved']
]);

var chart = new google.visualization.AnnotationChart(document.getElementById('chart_div'));

好的,这就是设置,现在问题来了。组织数据的最佳方法是什么,以便 1.) 池 1、2 和 3 始终有相同日期时间的温度数据(我担心给定时间戳的数据集可能不完整)?我应该使用一些聪明的查询从 SQL 层开始组织它吗?或者我是否通过使用一堆 foreach 循环在控制器中组织它?这是我正在努力的目标:

$dataTable->addRow(['created_at', 
    'temperature1', 'title1', 'text1',
    'temperature2', 'title2', 'text2',
    'temperature2', 'title2', 'text2',
]);

我可以看到聪明的查询将是避免在控制器中执行一堆逻辑和 foreach 循环的好方法。也许如果数据按列组织,例如:

created_at, pool_1_temperature, pool_2_temperature, pool_3_temperature
------------------------------------------------
2017-04-09 02:44:56, 100.1, 104.2, 97.0
2017-04-09 03:00:04, 100.1, 98.4, 96.6
2017-04-09 03:37:13, 100.1, 101.8, 96.4

然后我可以很容易地完成它并创建 DataTable。我不确定如何在 MySQL 中执行此操作,或者即使这是一个好主意。

感谢您到目前为止抽出宝贵的时间,并提前感谢您提供的任何帮助。我希望我已经足够清楚了。

PS。我想到目前为止我遇到的最接近的事情是Mysql query to dynamically convert rows to columns。我会再玩这个......

【问题讨论】:

    标签: mysql sql google-visualization laravel-5.3 google-datatable


    【解决方案1】:

    只要 x 轴(第一列)是日期,
    你不应该担心...

    池 1、2 和 3 总是有相同日期时间的温度数据

    图表应该可以解决

    因此,您可以使用类似于以下的查询...

    select
      created_at,
      case when
        pool_id = 1
      then
        temperature
      else
        null
      end pool_1,
      case when
        pool_id = 2
      then
        temperature
      else
        null
      end pool_2,
      case when
        pool_id = 3
      then
        temperature
      else
        null
      end pool_3
    from
      temperatures
    

    我无法让提供的任何 SQL 链接正常工作,
    所以我无法验证 sql

    我不确定返回 null 是否有效

    【讨论】:

    • 希望这会有所帮助,作为一般规则,您可以在服务器上处理的越多,与客户端相比,页面加载速度就越快——建议将尽可能多的逻辑推送到 sql 中,而不是 javascript ,尽可能...
    • 我非常感谢。这让我开始思考如何处理我的图表。你说得对,图表能够自己处理一些细节。我没有在需要存储过程的疯狂查询中处理这个问题,而是使用循环和 array_pad() 来组织数据。很快就会回答我自己的问题......再次感谢!它让我走上了正轨。
    【解决方案2】:

    为了确保数据是动态的,以防将来添加另一个池,我决定使用array_pad() 使用填充数组并循环遍历温度数据集,并在我进行时进行排序。我还使用了Lavacharts,因为它使处理 Google DataTables 变得容易。所以,这是我的代码(注意,添加注释字段需要更多工作):

    $dataTable = \Lava::DataTable();
    $dataTable->addDateTimeColumn('DateTime');
    
    // Add data column for each pool
    $pools = \App\Pool::get();
    foreach($pools as $pool) {
        $p = "Pool $pool->id";
        $dataTable->addNumberColumn("$p Temp");
    
        // TODO:  Create annotate fields for min and max temperatures
        // For this, we'll need to do some clever padding using array_pad()
        // and more clever index incrementing in the for() loop below.
        // Perhaps it's best to calculate and prepare in the temperatures query?
        //$dataTable->addStringColumn("$p Title");
        //$dataTable->addStringColumn("$p Text");
    }
    
    // Gather all the temperature data we wish to display.  A year ought to be enough.
    // At one hour updates, that makes for about 8,766 datapoints.
    $temperatures = \App\Temperature::where('created_at', '>=', \Carbon\Carbon::now()->subYear())
        ->orderBy('created_at', 'desc')
        ->orderBy('pool_id', 'asc')->get();
    
    // Grab all the timestamps and organize into an array
    $created_ats = \App\Temperature::groupBy('created_at')->pluck('created_at');
    
    // Let's go through each datetime field and collect all temperatures recorded on that datetime.
    // Then, let's store those temperatures into the appropriate index of the data row.
    foreach($created_ats as $created_at) {
        $dataRow = [$created_at]; // Start the array off by adding date to beginning
        $dataRow = array_pad($dataRow, 1 + count($pools), null); // +1 to account for $created_at column
        //$dataRow = array_pad($dataRow, 1 + (count($pools) * 3), null); // TODO: multiply by 3 for annotation fields
    
        // Start going through each temperature recording and assign to proper spot in dataRow array
        // If temperature is not found for the datetime, the array_pad() above already accounts for null
        // in that index.  Note, the created_at comparison only accounts for the hour, not seconds or minutes.
        // TODO: Implement min and max temperature annotations.
        //$maxTemperature = 0;
        //$minTemperature = 999;
        foreach($temperatures as $temperature) {
            // TODO: Implement min and max temperature annotations.
            //$maxTemperature = ($temperature->temperature >= $maxTemperature) ? $temperature->temperature : $maxTemperature;
            //$minTemperature = ($temperature->temperature <= $minTemperature) ? $temperature->temperature : $minTemperature;
    
            // Compare date and hour, then assign to appropriate index of the data row according to pool id.
            // ie.  Pool ID #1 needs to be placed in [1], Pool ID #2 in [2] and so forth. Remember, [0] is date.
            if ($temperature->created_at->format('Y-m-d H') == $created_at->format('Y-m-d H')) {
                for ($i = 1; $i <= count($pools); $i++) {
                    if($temperature->pool_id == $i) {
                        $dataRow[$i] = $temperature->temperature;
                    }
                }
            }
        }
    
        // We've gone through all temperatures for this created_at datetime.  
        // Add the resulting dataRow to the dataTable.
        $dataTable->addRow($dataRow);
    }   
    
    // What we're left with is a bunch of rows that look like this!
    // TODO: Add annoation fields for min and max temperatures.
    // $dataTable->addRow(['created_at', 
    //  'temperature1',
    //  'temperature2',
    //  'temperature2'
    //  ]);
    $jsonData = $dataTable->toJson();
    
    // At this point, return $jsonData for use with google.visualization.DataTable(jsonData);
    // Or, cache it and then return it, or whatever.
    

    我建议缓存数据,因为在视图中渲染似乎需要一点时间(~1.9s)。所以,也许这不是最快的方法,但它对我有用。进一步挖掘并找到其他优化会很有趣。目前,我对此感到满意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-28
      • 2016-08-13
      • 2011-11-22
      • 1970-01-01
      • 1970-01-01
      • 2012-06-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多