diff --git a/charts.charts_types.yml b/charts.charts_types.yml index c0554483e0a81bdefc3e6bb748231582b5bf0276..8bd3ef286e72f70783237e41fe7616529af7c5f2 100755 --- a/charts.charts_types.yml +++ b/charts.charts_types.yml @@ -8,11 +8,21 @@ bar: axis: 'xy' axis_inverted: true stacking: true +boxplot: + label: 'Boxplot' + axis: 'xy' + axis_inverted: false + stacking: false bubble: label: 'Bubble' axis: 'xy' axis_inverted: false stacking: false +candlestick: + label: 'Candlestick' + axis: 'xy' + axis_inverted: false + stacking: false column: label: 'Column' axis: 'xy' diff --git a/charts.module b/charts.module index 2190b36271e343430400dc64bd60a01a33b9f86d..e698545082634f231529bae506108dbafe3df7dc 100755 --- a/charts.module +++ b/charts.module @@ -30,6 +30,20 @@ function charts_views_data() { 'id' => 'field_charts_fields_bubble', ], ]; + $data['charts_fields']['field_charts_fields_boxplot'] = [ + 'title' => t('Box Plot Field'), + 'help' => t('Use this field for your data field in a box plot chart.'), + 'field' => [ + 'id' => 'field_charts_fields_boxplot', + ], + ]; + $data['charts_fields']['field_charts_fields_candlestick'] = [ + 'title' => t('Candlestick Field'), + 'help' => t('Use this field for your data field in a candlestick chart.'), + 'field' => [ + 'id' => 'field_charts_fields_candlestick', + ], + ]; $data['charts_fields']['field_exposed_chart_type'] = [ 'title' => t('Exposed Chart Type'), 'help' => t('Use this field for exposing chart type.'), diff --git a/modules/charts_billboard/src/Plugin/chart/Library/Billboard.php b/modules/charts_billboard/src/Plugin/chart/Library/Billboard.php index 5880afaab434e0f205b2ec716f54110e773c5dbd..82308235092879cb485992259b5c3f8fba066586 100755 --- a/modules/charts_billboard/src/Plugin/chart/Library/Billboard.php +++ b/modules/charts_billboard/src/Plugin/chart/Library/Billboard.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * "area", * "bar", * "bubble", + * "candlestick", * "column", * "donut", * "gauge", @@ -371,7 +372,7 @@ class Billboard extends ChartBase implements ContainerFactoryPluginInterface { array_shift($datum); } $columns[$columns_key_start][] = array_map(function ($item) { - return isset($item) ? strip_tags($item) : NULL; + return isset($item) ? (float) strip_tags($item) : NULL; }, $datum); } else { diff --git a/modules/charts_google/js/charts_google.js b/modules/charts_google/js/charts_google.js index 01a4aab2f371c0a70bd6dac0dcad13ba39fb038d..ede95600482f66aed95a3d2dd48875622b00aa27 100755 --- a/modules/charts_google/js/charts_google.js +++ b/modules/charts_google/js/charts_google.js @@ -115,10 +115,18 @@ chart = new google.visualization.PieChart(document.getElementById(chartId)); break; + case 'BoxPlot': + chart = new google.visualization.LineChart(document.getElementById(chartId)); + break; + case 'BubbleChart': chart = new google.visualization.BubbleChart(document.getElementById(chartId)); break; + case 'CandlestickChart': + chart = new google.visualization.CandlestickChart(document.getElementById(chartId)); + break; + case 'AreaChart': chart = new google.visualization.AreaChart(document.getElementById(chartId)); break; diff --git a/modules/charts_google/src/Plugin/chart/Library/Google.php b/modules/charts_google/src/Plugin/chart/Library/Google.php index 044c69bf3d5e7d06bda3330b1ea1380b86ea850f..9312ccd5ffba4eb8a7abc95c21c16c8dbf4d8766 100755 --- a/modules/charts_google/src/Plugin/chart/Library/Google.php +++ b/modules/charts_google/src/Plugin/chart/Library/Google.php @@ -24,14 +24,18 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * types = { * "area", * "bar", + * "boxplot", * "bubble", + * "candlestick", * "column", * "donut", * "gauge", + * "geo", * "line", * "pie", * "scatter", * "spline", + * "table", * }, * ) */ @@ -188,6 +192,8 @@ class Google extends ChartBase implements ContainerFactoryPluginInterface { $types = [ 'area' => 'AreaChart', 'bar' => 'BarChart', + 'boxplot' => 'LineChart', + 'candlestick' => 'CandlestickChart', 'column' => 'ColumnChart', 'line' => 'LineChart', 'spline' => 'SplineChart', @@ -567,7 +573,7 @@ class Google extends ChartBase implements ContainerFactoryPluginInterface { array_unshift($data[0], ''); } // Ensure consistent column count. - $column_count = count($data[0]); + $column_count = is_countable($data[0]) ? count($data[0]) : 0; $new_data = []; foreach ($data as $row => $values) { for ($n = 0; $n < $column_count; $n++) { diff --git a/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php b/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php index 235a236f29b1b26ea72893a187dac72e3d160ed1..cb861813baf43176334437e4de889d31fb4808f7 100755 --- a/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php +++ b/modules/charts_highcharts/src/Plugin/chart/Library/Highcharts.php @@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * types = { * "area", * "bar", + * "boxplot", * "bubble", * "column", * "donut", diff --git a/src/Element/BaseSettings.php b/src/Element/BaseSettings.php index c6f283c8950b040fffabea19228d8092715c4f31..9e2548e6cf50824f08be92f0c072f99983185fd2 100755 --- a/src/Element/BaseSettings.php +++ b/src/Element/BaseSettings.php @@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; -use Drupal\Core\Render\Element\FormElementBase; +use Drupal\Core\Render\Element\FormElement; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\charts\ColorHelperTrait; use Drupal\views\Views; @@ -36,7 +36,7 @@ use Drupal\views\Views; * * @FormElement("charts_settings") */ -class BaseSettings extends FormElementBase { +class BaseSettings extends FormElement { use ColorHelperTrait; use ElementFormStateTrait; diff --git a/src/Element/Chart.php b/src/Element/Chart.php index 69e0f803ed4b8722120e656936b2e961ec66919c..7de52c944aba9ff06d0edcb42e48e460e2b320a2 100755 --- a/src/Element/Chart.php +++ b/src/Element/Chart.php @@ -7,7 +7,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Render\Element; -use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\Element\RenderElement; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\charts\ChartManager; use Drupal\charts\Plugin\chart\Library\ChartBase; @@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * * @RenderElement("chart") */ -class Chart extends RenderElementBase implements ContainerFactoryPluginInterface { +class Chart extends RenderElement implements ContainerFactoryPluginInterface { use StringTranslationTrait; use LibraryRetrieverTrait; diff --git a/src/Element/ChartAxisBase.php b/src/Element/ChartAxisBase.php index 05e50651c7570f06c826b65a6181d465abea8ae4..4449f8d34f8ef26a660d56f4183a971a7172c78f 100755 --- a/src/Element/ChartAxisBase.php +++ b/src/Element/ChartAxisBase.php @@ -2,12 +2,12 @@ namespace Drupal\charts\Element; -use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\Element\RenderElement; /** * Provides a chart render element. */ -abstract class ChartAxisBase extends RenderElementBase { +abstract class ChartAxisBase extends RenderElement { /** * {@inheritdoc} diff --git a/src/Element/ChartData.php b/src/Element/ChartData.php index a2d95506bfc0180a5ebae5155ebdf04cd05f85a8..081ef5e95fa4a763a732b4f06d204a4247ad9f74 100755 --- a/src/Element/ChartData.php +++ b/src/Element/ChartData.php @@ -2,14 +2,14 @@ namespace Drupal\charts\Element; -use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\Element\RenderElement; /** * Provides a chart data render element. * * @RenderElement("chart_data") */ -class ChartData extends RenderElementBase { +class ChartData extends RenderElement { /** * {@inheritdoc} diff --git a/src/Element/ChartDataCollectorTable.php b/src/Element/ChartDataCollectorTable.php index c0b5ec7e68c24a0ccac3433c785abad58bbcbbcf..68f2e6e44bc11f12676fce146bc2cbd072abf5d2 100755 --- a/src/Element/ChartDataCollectorTable.php +++ b/src/Element/ChartDataCollectorTable.php @@ -16,7 +16,7 @@ use Drupal\charts\Plugin\chart\Library\ChartInterface; * * @FormElement("chart_data_collector_table") */ -class ChartDataCollectorTable extends FormElementBase { +class ChartDataCollectorTable extends FormElement { use ColorHelperTrait; use ElementFormStateTrait; @@ -868,7 +868,7 @@ class ChartDataCollectorTable extends FormElementBase { 'bubble', 'candlestick', 'boxplot', - ])) { + ]) && is_numeric($cell_value)) { $series[$j]['data'][0][] = $cell_value; } else { @@ -897,6 +897,10 @@ class ChartDataCollectorTable extends FormElementBase { if (is_numeric($value)) { $value = is_int($value) ? (integer) $value : (float) $value; } + // If the value is an array contained within a string. + elseif (preg_match('/^\[.*\]$/', $value)) { + $value = json_decode($value); + } elseif ($value === '') { $value = NULL; } diff --git a/src/Element/ChartDataItem.php b/src/Element/ChartDataItem.php index 9e9acfccf46eea484b3090c7fab7b1a8dc007a86..85b6ed1003724d540b6e7691faa7aa84291487a0 100755 --- a/src/Element/ChartDataItem.php +++ b/src/Element/ChartDataItem.php @@ -2,14 +2,14 @@ namespace Drupal\charts\Element; -use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Render\Element\RenderElement; /** * Provides a chart data item render element. * * @RenderElement("chart_data_item") */ -class ChartDataItem extends RenderElementBase { +class ChartDataItem extends RenderElement { /** * {@inheritdoc} diff --git a/src/Plugin/views/field/BoxPlotField.php b/src/Plugin/views/field/BoxPlotField.php new file mode 100644 index 0000000000000000000000000000000000000000..14da67c99df641d33b38e5b90e697ac7113817fd --- /dev/null +++ b/src/Plugin/views/field/BoxPlotField.php @@ -0,0 +1,162 @@ +<?php + +namespace Drupal\charts\Plugin\views\field; + +use Drupal\charts\ChartViewsFieldInterface; +use Drupal\Component\Serialization\Json; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\views\Plugin\views\field\FieldPluginBase; +use Drupal\views\ResultRow; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @file + * Defines Drupal\charts\Plugin\views\field\BoxPlotField. + */ + +/** + * Field handler to provide values for a box plot chart. + * + * @ingroup views_field_handlers + * @ViewsField("field_charts_fields_boxplot") + */ +class BoxPlotField extends FieldPluginBase implements ContainerFactoryPluginInterface, ChartViewsFieldInterface { + + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param $plugin_id + * The plugin_id for the plugin instance. + * @param $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MessengerInterface $messenger) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->messenger = $messenger; + } + + /** + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param $plugin_id + * The plugin_id for the plugin instance. + * @param $plugin_definition + * The plugin implementation definition. + * + * @return static + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function query() { + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $this->field_alias = 'boxplot_field'; + + // Add fields for the Box Plot values. + $boxplot_values = ['min', 'first_quartile', 'median', 'third_quartile', 'max']; + foreach ($boxplot_values as $value) { + $options["{$value}"]['default'] = NULL; + } + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + $fieldList = $this->displayHandler->getFieldLabels(); + unset($fieldList['field_charts_fields_boxplot']); + + $boxplot_parts = ['min', 'first_quartile', 'median', 'third_quartile', 'max']; + foreach ($boxplot_parts as $part) { + $form[$part] = [ + '#type' => 'radios', + '#title' => $this->t(ucfirst($part) . ' value'), + '#options' => $fieldList, + '#default_value' => $this->options[$part], + ]; + } + + return $form; + } + + /** + * Get the value of a box plot field. + * + * @param \Drupal\views\ResultRow $values + * Row results. + * @param string $part + * The box plot value key (min, first_quartile, etc.). + * + * @return float|NULL + * The field value. + * + * @throws \Exception + */ + protected function getBoxPlotFieldValue(ResultRow $values, string $part) { + $field = $this->options[$part]; + + if (isset($this->view->field[$field])) { + return floatval($this->view->field[$field]->getValue($values)); + } + + return NULL; + } + + /** + * {@inheritdoc} + * + * @throws \Exception + */ + public function getValue(ResultRow $values, $field = NULL) { + parent::getValue($values, $field); + + $boxplot_values = [ + 'min' => $this->getBoxPlotFieldValue($values, 'min'), + 'first_quartile' => $this->getBoxPlotFieldValue($values, 'first_quartile'), + 'median' => $this->getBoxPlotFieldValue($values, 'median'), + 'third_quartile' => $this->getBoxPlotFieldValue($values, 'third_quartile'), + 'max' => $this->getBoxPlotFieldValue($values, 'max'), + ]; + + return JSON::encode(array_values($boxplot_values)); + } + + /** + * {@inheritdoc} + */ + public function getChartFieldDataType(): string { + return 'array'; + } + +} diff --git a/src/Plugin/views/field/CandlestickField.php b/src/Plugin/views/field/CandlestickField.php new file mode 100644 index 0000000000000000000000000000000000000000..f1d5320aaadf53d0a45ad78f7211800298fb3df3 --- /dev/null +++ b/src/Plugin/views/field/CandlestickField.php @@ -0,0 +1,153 @@ +<?php + +namespace Drupal\charts\Plugin\views\field; + +use Drupal\charts\ChartViewsFieldInterface; +use Drupal\Component\Serialization\Json; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\views\Plugin\views\field\FieldPluginBase; +use Drupal\views\ResultRow; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @file + * Defines Drupal\charts\Plugin\views\field\CandlestickField. + */ + +/** + * Field handler to provide values for a candlestick chart. + * + * @ingroup views_field_handlers + * @ViewsField("field_charts_fields_candlestick") + */ +class CandlestickField extends FieldPluginBase implements ContainerFactoryPluginInterface, ChartViewsFieldInterface { + + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param $plugin_id + * The plugin_id for the plugin instance. + * @param $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, MessengerInterface $messenger) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->messenger = $messenger; + } + + /** + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param $plugin_id + * The plugin_id for the plugin instance. + * @param $plugin_definition + * The plugin implementation definition. + * + * @return static + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function query() { + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $this->field_alias = 'candlestick_field'; + $candlestick_values = ['open', 'high', 'low', 'close']; + foreach ($candlestick_values as $value) { + $options["{$value}"]['default'] = NULL; + } + + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + $fieldList = $this->displayHandler->getFieldLabels(); + unset($fieldList['field_charts_fields_candlestick']); + + $candlestick_parts = ['open', 'high', 'low', 'close']; + foreach ($candlestick_parts as $part) { + $form[$part] = [ + '#type' => 'radios', + '#title' => $this->t(ucfirst($part) . ' value'), + '#options' => $fieldList, + '#default_value' => $this->options[$part], + ]; + } + + return $form; + } + + /** + * Get the value of a candlestick field. + * + * @param \Drupal\views\ResultRow $values + * Row results. + * @param string $part + * The box plot value key (min, first_quartile, etc.). + * + * @return float|NULL + * The field value. + * + * @throws \Exception + */ + protected function getCandlestickFieldValue(ResultRow $values, string $part) { + $field = $this->options[$part]; + + if (isset($this->view->field[$field])) { + return floatval($this->view->field[$field]->getValue($values)); + } + + return NULL; + } + + public function getValue(ResultRow $values, $field = NULL) { + parent::getValue($values, $field); + + $candlestick_values = [ + 'open' => $this->getCandlestickFieldValue($values, 'open'), + 'high' => $this->getCandlestickFieldValue($values, 'high'), + 'low' => $this->getCandlestickFieldValue($values, 'low'), + 'close' => $this->getCandlestickFieldValue($values, 'close'), + ]; + + return JSON::encode(array_values($candlestick_values)); + } + + /** + * {@inheritdoc} + */ + public function getChartFieldDataType(): string { + return 'array'; + } +} diff --git a/tests/src/Kernel/ChartTypeSupportTest.php b/tests/src/Kernel/ChartTypeSupportTest.php index 33fce760017f9414b71a9790c23a477a7c1f5965..927d1ac13bb799ba8c1747601fb8864ed87acd62 100644 --- a/tests/src/Kernel/ChartTypeSupportTest.php +++ b/tests/src/Kernel/ChartTypeSupportTest.php @@ -68,7 +68,7 @@ class ChartTypeSupportTest extends ChartsKernelTestBase { 'chart type plugin does not exist' => [ 'not_supported', PluginNotFoundException::class, - 'The "not_supported" plugin does not exist. Valid plugin IDs for Drupal\charts\TypeManager are: area, bar, bubble, column, donut, gauge, line, pie, scatter, spline', + 'The "not_supported" plugin does not exist. Valid plugin IDs for Drupal\charts\TypeManager are: area, bar, boxplot, bubble, candlestick, column, donut, gauge, line, pie, scatter, spline', ], // Asserting that a chart type not supported by a chart library plugin // will throw a logic exception.