From d8dba26a08f4664081aba129d4f71cfc420b7093 Mon Sep 17 00:00:00 2001
From: epriestley <git@epriestley.com>
Date: Wed, 17 Apr 2019 07:17:28 -0700
Subject: [PATCH] Add a "sin()" function to charts

Summary:
Depends on D20443. Ref T13279. This is probably not terribly useful on its own, but is mostly a function which takes another function as an argument, and a step toward more useful functions like arithmetic and drawing a picture of an owl.

The only structural change here is that functions now read data parameters (domain, sample limit) using a more tailored "ChartDataQuery" instead of reading the actual axis. Mostly, I want a more cohesive representation of query state that can be easily passed to sub-functions, as here.

Test Plan: {F6382432}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20444
---
 src/__phutil_library_map__.php                |  4 ++
 .../fact/chart/PhabricatorChartDataQuery.php  | 37 ++++++++++++++++
 .../PhabricatorConstantChartFunction.php      |  7 ++-
 .../chart/PhabricatorFactChartFunction.php    | 26 ++++++-----
 .../chart/PhabricatorSinChartFunction.php     | 44 +++++++++++++++++++
 .../fact/chart/PhabricatorXChartFunction.php  |  8 ++--
 .../PhabricatorFactChartController.php        | 15 ++++---
 7 files changed, 116 insertions(+), 25 deletions(-)
 create mode 100644 src/applications/fact/chart/PhabricatorChartDataQuery.php
 create mode 100644 src/applications/fact/chart/PhabricatorSinChartFunction.php

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 2ceb024377..f27f81b13d 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2650,6 +2650,7 @@ phutil_register_library_map(array(
     'PhabricatorChangesetCachePurger' => 'applications/cache/purger/PhabricatorChangesetCachePurger.php',
     'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php',
     'PhabricatorChartAxis' => 'applications/fact/chart/PhabricatorChartAxis.php',
+    'PhabricatorChartDataQuery' => 'applications/fact/chart/PhabricatorChartDataQuery.php',
     'PhabricatorChartFunction' => 'applications/fact/chart/PhabricatorChartFunction.php',
     'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php',
     'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php',
@@ -4564,6 +4565,7 @@ phutil_register_library_map(array(
     'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php',
     'PhabricatorShowFiletreeSetting' => 'applications/settings/setting/PhabricatorShowFiletreeSetting.php',
     'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php',
+    'PhabricatorSinChartFunction' => 'applications/fact/chart/PhabricatorSinChartFunction.php',
     'PhabricatorSite' => 'aphront/site/PhabricatorSite.php',
     'PhabricatorSlackAuthProvider' => 'applications/auth/provider/PhabricatorSlackAuthProvider.php',
     'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php',
@@ -8625,6 +8627,7 @@ phutil_register_library_map(array(
     'PhabricatorChangesetCachePurger' => 'PhabricatorCachePurger',
     'PhabricatorChangesetResponse' => 'AphrontProxyResponse',
     'PhabricatorChartAxis' => 'Phobject',
+    'PhabricatorChartDataQuery' => 'Phobject',
     'PhabricatorChartFunction' => 'Phobject',
     'PhabricatorChatLogApplication' => 'PhabricatorApplication',
     'PhabricatorChatLogChannel' => array(
@@ -10860,6 +10863,7 @@ phutil_register_library_map(array(
     'PhabricatorShortSite' => 'PhabricatorSite',
     'PhabricatorShowFiletreeSetting' => 'PhabricatorSelectSetting',
     'PhabricatorSimpleEditType' => 'PhabricatorEditType',
+    'PhabricatorSinChartFunction' => 'PhabricatorChartFunction',
     'PhabricatorSite' => 'AphrontSite',
     'PhabricatorSlackAuthProvider' => 'PhabricatorOAuth2AuthProvider',
     'PhabricatorSlowvoteApplication' => 'PhabricatorApplication',
diff --git a/src/applications/fact/chart/PhabricatorChartDataQuery.php b/src/applications/fact/chart/PhabricatorChartDataQuery.php
new file mode 100644
index 0000000000..15708341f7
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorChartDataQuery.php
@@ -0,0 +1,37 @@
+<?php
+
+final class PhabricatorChartDataQuery
+  extends Phobject {
+
+  private $limit;
+  private $minimumValue;
+  private $maximumValue;
+
+  public function setMinimumValue($minimum_value) {
+    $this->minimumValue = $minimum_value;
+    return $this;
+  }
+
+  public function getMinimumValue() {
+    return $this->minimumValue;
+  }
+
+  public function setMaximumValue($maximum_value) {
+    $this->maximumValue = $maximum_value;
+    return $this;
+  }
+
+  public function getMaximumValue() {
+    return $this->maximumValue;
+  }
+
+  public function setLimit($limit) {
+    $this->limit = $limit;
+    return $this;
+  }
+
+  public function getLimit() {
+    return $this->limit;
+  }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorConstantChartFunction.php b/src/applications/fact/chart/PhabricatorConstantChartFunction.php
index f170491bce..b5dc334dd8 100644
--- a/src/applications/fact/chart/PhabricatorConstantChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorConstantChartFunction.php
@@ -27,10 +27,9 @@ final class PhabricatorConstantChartFunction
     $this->value = $arguments[0];
   }
 
-  public function getDatapoints($limit) {
-    $axis = $this->getXAxis();
-    $x_min = $axis->getMinimumValue();
-    $x_max = $axis->getMaximumValue();
+  public function getDatapoints(PhabricatorChartDataQuery $query) {
+    $x_min = $query->getMinimumValue();
+    $x_max = $query->getMaximumValue();
 
     $points = array();
     $steps = $this->newLinearSteps($x_min, $x_max, 2);
diff --git a/src/applications/fact/chart/PhabricatorFactChartFunction.php b/src/applications/fact/chart/PhabricatorFactChartFunction.php
index 0ae1548c91..fbeec9c81b 100644
--- a/src/applications/fact/chart/PhabricatorFactChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorFactChartFunction.php
@@ -77,15 +77,15 @@ final class PhabricatorFactChartFunction
     $this->datapoints = $points;
   }
 
-  public function getDatapoints($limit) {
+  public function getDatapoints(PhabricatorChartDataQuery $query) {
     $points = $this->datapoints;
     if (!$points) {
       return array();
     }
 
-    $axis = $this->getXAxis();
-    $x_min = $axis->getMinimumValue();
-    $x_max = $axis->getMaximumValue();
+    $x_min = $query->getMinimumValue();
+    $x_max = $query->getMaximumValue();
+    $limit = $query->getLimit();
 
     if ($x_min !== null) {
       foreach ($points as $key => $point) {
@@ -104,14 +104,16 @@ final class PhabricatorFactChartFunction
     }
 
     // If we have too many data points, throw away some of the data.
-    $count = count($points);
-    if ($count > $limit) {
-      $ii = 0;
-      $every = ceil($count / $limit);
-      foreach ($points as $key => $point) {
-        $ii++;
-        if (($ii % $every) && ($ii != $count)) {
-          unset($points[$key]);
+    if ($limit !== null) {
+      $count = count($points);
+      if ($count > $limit) {
+        $ii = 0;
+        $every = ceil($count / $limit);
+        foreach ($points as $key => $point) {
+          $ii++;
+          if (($ii % $every) && ($ii != $count)) {
+            unset($points[$key]);
+          }
         }
       }
     }
diff --git a/src/applications/fact/chart/PhabricatorSinChartFunction.php b/src/applications/fact/chart/PhabricatorSinChartFunction.php
new file mode 100644
index 0000000000..876a153a59
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorSinChartFunction.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PhabricatorSinChartFunction
+  extends PhabricatorChartFunction {
+
+  const FUNCTIONKEY = 'sin';
+
+  private $argument;
+
+  protected function newArguments(array $arguments) {
+    if (count($arguments) !== 1) {
+      throw new Exception(
+        pht(
+          'Chart function "sin(..)" expects one argument, got %s.',
+          count($arguments)));
+    }
+
+    $argument = $arguments[0];
+
+    if (!($argument instanceof PhabricatorChartFunction)) {
+      throw new Exception(
+        pht(
+          'Argument to chart function should be a function, got %s.',
+          phutil_describe_type($argument)));
+    }
+
+    $this->argument = $argument;
+  }
+
+  public function getDatapoints(PhabricatorChartDataQuery $query) {
+    $points = $this->argument->getDatapoints($query);
+
+    foreach ($points as $key => $point) {
+      $points[$key]['y'] = sin(deg2rad($points[$key]['y']));
+    }
+
+    return $points;
+  }
+
+  public function hasDomain() {
+    return false;
+  }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorXChartFunction.php b/src/applications/fact/chart/PhabricatorXChartFunction.php
index 05c5bf7040..6e2eae9535 100644
--- a/src/applications/fact/chart/PhabricatorXChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorXChartFunction.php
@@ -14,10 +14,10 @@ final class PhabricatorXChartFunction
     }
   }
 
-  public function getDatapoints($limit) {
-    $axis = $this->getXAxis();
-    $x_min = $axis->getMinimumValue();
-    $x_max = $axis->getMaximumValue();
+  public function getDatapoints(PhabricatorChartDataQuery $query) {
+    $x_min = $query->getMinimumValue();
+    $x_max = $query->getMaximumValue();
+    $limit = $query->getLimit();
 
     $points = array();
     $steps = $this->newLinearSteps($x_min, $x_max, $limit);
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php
index b7c3611e1f..9a5a492d21 100644
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/controller/PhabricatorFactChartController.php
@@ -20,25 +20,30 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
     $functions[] = id(new PhabricatorFactChartFunction())
       ->setArguments(array('tasks.open-count.create'));
 
-    $functions[] = id(new PhabricatorConstantChartFunction())
-      ->setArguments(array(256));
-
-    $functions[] = id(new PhabricatorXChartFunction())
+    $x_function = id(new PhabricatorXChartFunction())
       ->setArguments(array());
 
+    $functions[] = id(new PhabricatorSinChartFunction())
+      ->setArguments(array($x_function));
+
     list($domain_min, $domain_max) = $this->getDomain($functions);
 
     $axis = id(new PhabricatorChartAxis())
       ->setMinimumValue($domain_min)
       ->setMaximumValue($domain_max);
 
+    $data_query = id(new PhabricatorChartDataQuery())
+      ->setMinimumValue($domain_min)
+      ->setMaximumValue($domain_max)
+      ->setLimit(2000);
+
     $datasets = array();
     foreach ($functions as $function) {
       $function->setXAxis($axis);
 
       $function->loadData();
 
-      $points = $function->getDatapoints(2000);
+      $points = $function->getDatapoints($data_query);
 
       $x = array();
       $y = array();