Commit cb7b8dac authored by Nia Kathoni's avatar Nia Kathoni Committed by Daniel Cothran
Browse files

Issue #3094417 by nikathone, jdmoreno: Highcharts pie default colors

parent 0e82d507
Loading
Loading
Loading
Loading
+20 −0
Changes for src/ColorHelperTrait.php: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\charts;

/**
 * Contains helper method to help with the generation of random color.
 */
trait ColorHelperTrait {

  /**
   * Provide a random color.
   *
   * @return string
   *   A random color.
   */
  public static function randomColor(): string {
    return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
  }

}
+172 −29
Changes for src/Element/BaseSettings.php: 172 added lines, 29 removed lines.
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\charts\Element;

use Drupal\charts\ColorHelperTrait;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityPublishedInterface;
@@ -37,6 +38,9 @@ use Drupal\views\Views;
 */
class BaseSettings extends FormElement {

  use ColorHelperTrait;
  use ElementFormStateTrait;

  /**
   * {@inheritdoc}
   */
@@ -191,7 +195,7 @@ class BaseSettings extends FormElement {
      $element = self::processViewForm($element, $options, $complete_form, $form_state);
    }
    elseif ($used_in === 'config_form') {
      $element = self::processConfigForm($element, $options);
      $element = self::processConfigForm($element, $options, $form_state);
    }

    $element['display']['title_position'] = [
@@ -608,7 +612,10 @@ class BaseSettings extends FormElement {
    // The #validate callbacks of the complete form run last.
    // That allows executeElementSubmitHandlers() to be completely certain that
    // the form has passed validation before proceeding.
    $complete_form['#validate'][] = [get_class(), 'executeLibraryElementSubmitHandlers'];
    $complete_form['#validate'][] = [
      get_class(),
      'executeLibraryElementSubmitHandlers',
    ];
    $complete_form['#charts_library_settings_element_submit_attached'] = TRUE;

    return $element;
@@ -796,6 +803,8 @@ class BaseSettings extends FormElement {
   *   The current element.
   * @param array $options
   *   The options.
   * @param array $complete_form
   *   The complete form where the element is attached to.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
@@ -966,20 +975,19 @@ class BaseSettings extends FormElement {
      '#required' => TRUE,
      '#options' => [
        'by_entities_on_entity_reference' => new TranslatableMarkup('Set color by entities on entity reference'),
        // @todo remove if no plan to support this use case.
        //        'by_entities_on_current_entity' => new TranslatableMarkup('Set color by entities on current entity'),
      ],
      '#default_value' => $selected_method,
      '#ajax' => [
        'wrapper' => $selection_method_wrapper_id,
        'callback' => [get_called_class(), 'groupingChartSettingsSelectedMethodAjaxCallback'],
        'callback' => [
          get_called_class(),
          'groupingChartSettingsSelectedMethodAjaxCallback',
        ],
      ],
      '#limit_validation_errors' => [],
    ];
    if ($module_handler->moduleExists('color_field')) {
      $element['fields']['entity_grouping']['color_selection_method']['#options']['by_field_on_referenced_entity'] = new TranslatableMarkup('Set color based on a color field on entity reference');
      // @todo remove if no plan to support this use case.
      //      $element['fields']['entity_grouping']['color_selection_method']['#options']['by_field_on_current_entity'] = new TranslatableMarkup('Set color based on a color field on the current entity');
    }
    $element['fields']['entity_grouping']['selected_method'] = [
      '#type' => 'container',
@@ -1011,8 +1019,9 @@ class BaseSettings extends FormElement {
    switch ($selected_method) {
      case 'by_entities_on_entity_reference':
        $metadata['colors'] = $options['fields']['entity_grouping']['selected_method']['colors'] ?? [];
        $element['fields']['entity_grouping']['selected_method']['colors'] = static::buildColorsSelectionSubFormByEntities($element, $metadata, $entity_type_manager);
        $element['fields']['entity_grouping']['selected_method']['colors'] = static::buildColorsSelectionSubFormByEntities($metadata, $entity_type_manager);
        break;

      case 'by_field_on_referenced_entity':
        $metadata['color_field_name'] = $options['fields']['entity_grouping']['selected_method']['color_field_name'] ?? '';
        $element['fields']['entity_grouping']['selected_method']['color_field_name'] = static::buildColorsSelectionSubFormByFieldOnReferencedEntity($metadata, $entity_type_manager);
@@ -1029,13 +1038,16 @@ class BaseSettings extends FormElement {
   *   The current element.
   * @param array $options
   *   Options.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array
   *   The element.
   */
  private static function processConfigForm(array $element, array $options) {
    $tab_group = implode('][', array_merge($element['#parents'], ['defaults']));
    $display_parents = array_merge($element['#parents'], ['display']);
  private static function processConfigForm(array $element, array $options, FormStateInterface $form_state): array {
    $parents = $element['#parents'];
    $tab_group = implode('][', array_merge($parents, ['defaults']));
    $display_parents = array_merge($parents, ['display']);
    $element['defaults'] = [
      '#type' => 'vertical_tabs',
      '#default_tab' => 'edit-' . implode('-', $display_parents),
@@ -1052,25 +1064,87 @@ class BaseSettings extends FormElement {
    $element['yaxis']['#weight'] = 3;
    $element['yaxis']['#group'] = $tab_group;

    $id_prefix = implode('-', $parents);
    $element_state = static::getElementState($parents, $form_state);
    $state_color_indexes_key = $id_prefix . '__default_display_color_indexes';
    $options_display_colors = $options['display']['colors'];
    if (!$element_state) {
      $element_state = $options;
      $options_display_colors_indexes = array_keys($options_display_colors);
      $element_state[$state_color_indexes_key] = $options_display_colors_indexes;
      static::setElementState($parents, $form_state, $element_state);
    }
    else {
      $options_display_colors_indexes = $element_state[$state_color_indexes_key];
    }

    $wrapper_id = $id_prefix . '--display-colors';
    $element['display']['colors'] = [
      '#title' => new TranslatableMarkup('Chart colors'),
      '#theme_wrappers' => ['form_element'],
      '#prefix' => '<div class="chart-colors">',
      '#type' => 'table',
      '#header' => [
        new TranslatableMarkup('Colors'),
        new TranslatableMarkup('Operations'),
      ],
      '#prefix' => '<div id="' . $wrapper_id . '">',
      '#suffix' => '</div>',
    ];

    // Using the default colors in the settings to populate the colors.
    foreach ($options['display']['colors'] as $color_index => $color) {
      $element['display']['colors'][$color_index] = [
    foreach ($options_display_colors_indexes as $color_index => $position) {
      $element['display']['colors'][$color_index]['color'] = [
        '#type' => 'textfield',
        '#title' => new TranslatableMarkup('Color'),
        '#title_display' => 'invisible',
        '#attributes' => ['TYPE' => 'color'],
        '#size' => 10,
        '#maxlength' => 7,
        '#theme_wrappers' => [],
        '#suffix' => ' ',
        '#default_value' => $color,
      ];
      if ($position !== '_new') {
        $element['display']['colors'][$color_index]['color']['#default_value'] = $options_display_colors[$color_index];
      }
      else {
        // Generating a random color for the new default color.
        $element['display']['colors'][$color_index]['color']['#default_value'] = static::randomColor();
      }
      $element['display']['colors'][$color_index]['remove'] = [
        '#type' => 'submit',
        '#name' => 'remove_value_display_colors_color' . $color_index,
        '#value' => new TranslatableMarkup('Remove'),
        '#limit_validation_errors' => [],
        '#submit' => [
          [get_called_class(), 'removeDefaultDisplayColorItemSubmit'],
        ],
        '#color_index' => $color_index,
        '#ajax' => [
          'callback' => [get_called_class(), 'defaultDisplayColorItemsAjax'],
          'wrapper' => $wrapper_id,
        ],
        '#operation' => 'remove',
        '#state_color_indexes_key' => $state_color_indexes_key,
      ];
    }

    $element['display']['colors']['_add_new'] = [
      '#tree' => FALSE,
    ];
    $element['display']['colors']['_add_new']['add_item'] = [
      '#type' => 'container',
      '#wrapper_attributes' => ['colspan' => 2],
      '#tree' => FALSE,
    ];
    $element['display']['colors']['_add_new']['add_item']['submit'] = [
      '#type' => 'submit',
      '#value' => new TranslatableMarkup('Add a new default color'),
      '#submit' => [[get_called_class(), 'addDefaultDisplayColorItemSubmit']],
      '#limit_validation_errors' => [],
      '#ajax' => [
        'callback' => [get_called_class(), 'defaultDisplayColorItemsAjax'],
        'wrapper' => $wrapper_id,
      ],
      '#operation' => 'add',
      '#state_color_indexes_key' => $state_color_indexes_key,
    ];

    return $element;
  }
@@ -1202,6 +1276,47 @@ class BaseSettings extends FormElement {
    return $entity_grouping_element['selected_method'];
  }

  /**
   * Submit callback for adding a new default display color item value.
   */
  public static function addDefaultDisplayColorItemSubmit(array $form, FormStateInterface $form_state): void {
    $triggering_element = $form_state->getTriggeringElement();
    $element_parents = array_slice($triggering_element['#array_parents'], 0, -5);
    $element_state = static::getElementState($element_parents, $form_state);

    // Adding a new default color color item index.
    $element_state[$triggering_element['#state_color_indexes_key']][] = '_new';
    // Updating form state storage.
    static::setElementState($element_parents, $form_state, $element_state);
    $form_state->setRebuild();
  }

  /**
   * Submit callback for removing a value.
   */
  public static function removeDefaultDisplayColorItemSubmit(array $form, FormStateInterface $form_state): void {
    $triggering_element = $form_state->getTriggeringElement();
    $element_parents = array_slice($triggering_element['#array_parents'], 0, -4);
    $element_state = static::getElementState($element_parents, $form_state);
    $color_index = $triggering_element['#color_index'];

    // Removing the color item.
    unset($element_state[$triggering_element['#state_color_indexes_key']][$color_index]);
    // Updating form state storage.
    static::setElementState($element_parents, $form_state, $element_state);
    $form_state->setRebuild();
  }

  /**
   * Ajax callback for then default display colors operations.
   */
  public static function defaultDisplayColorItemsAjax(array $form, FormStateInterface $form_state): array {
    $triggering_element = $form_state->getTriggeringElement();
    $slice_length = $triggering_element['#operation'] === 'add' ? -4 : -3;
    $element = NestedArray::getValue($form, array_slice($triggering_element['#array_parents'], 0, $slice_length));
    return $element['colors'];
  }

  /**
   * Builds the chart library configuration form into the settings.
   *
@@ -1240,13 +1355,15 @@ class BaseSettings extends FormElement {
  /**
   * Helper method to retrieve the referenced entity type id.
   *
   * @param $field_info
   * @param array $field_info
   *   The field info.
   * @param string $selection_method
   *   The selection method.
   *
   * @return string
   *   The entity type id.
   */
  private static function getReferenceEntityTypeId($field_info, string $selection_method) {
  private static function getReferenceEntityTypeId(array $field_info, string $selection_method): string {
    if (!$selection_method || empty($field_info['type']) || $field_info['type'] !== 'entity_reference_label' || empty($field_info['field'])) {
      return '';
    }
@@ -1255,8 +1372,23 @@ class BaseSettings extends FormElement {
    return $table[$field_machine_name]['relationship']['entity type'] ?? '';
  }

  private static function buildColorsSelectionSubFormByEntities(array $element, array $metadata, EntityTypeManagerInterface $entity_type_manager) {
    $empty_entity_colors = new TranslatableMarkup('No grouping by an entity reference field was detected or the selected field didn\'t have any entity or color field attached.');
  /**
   * Helper method to build a color selection element by entities.
   *
   * @param array $metadata
   *   The metadata information to use to retrieve the color options.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   *
   * @return array
   *   The color selection table or a markup message when the provided metadata
   *   are incomplete.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private static function buildColorsSelectionSubFormByEntities(array $metadata, EntityTypeManagerInterface $entity_type_manager): array {
    $empty_entity_colors = new TranslatableMarkup("No grouping by an entity reference field was detected or the selected field didnt have any entity or color field attached.");
    $colors = [
      '#markup' => '<p>' . $empty_entity_colors . '</p>',
    ];
@@ -1282,12 +1414,8 @@ class BaseSettings extends FormElement {
    }

    // Load the entities by bundle.
    try {
    $entity_storage = $entity_type_manager->getStorage($entity_type_id);
    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
    } catch (\Exception $e) {
      return $colors;
    }

    $colors = [
      '#type' => 'table',
@@ -1334,7 +1462,7 @@ class BaseSettings extends FormElement {
        '#title_display' => 'invisible',
        '#attributes' => [
          'TYPE' => 'color',
          'style' => 'min-width:50px;'
          'style' => 'min-width:50px;',
        ],
        '#size' => 10,
        '#maxlength' => 7,
@@ -1345,7 +1473,22 @@ class BaseSettings extends FormElement {
    return $colors;
  }

  private static function buildColorsSelectionSubFormByFieldOnReferencedEntity(array $metadata, EntityTypeManagerInterface $entity_type_manager) {
  /**
   * Method to build a color selection element by field on referenced entity.
   *
   * @param array $metadata
   *   The metadata information to use to retrieve the color options.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   *
   * @return array
   *   The select element or the status message when no color options was
   *   found.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private static function buildColorsSelectionSubFormByFieldOnReferencedEntity(array $metadata, EntityTypeManagerInterface $entity_type_manager): array {
    // Identifying the vocabulary this field could belong to.
    $field_config_storage = $entity_type_manager->getStorage('field_config');
    /** @var \Drupal\field\FieldConfigInterface[] $grouping_field_configs */
@@ -1379,7 +1522,7 @@ class BaseSettings extends FormElement {
    }

    if (!$color_field_options) {
      $empty_entity_field_name = new TranslatableMarkup('You can\'t set the color using the selected method because the referenced entity doesn\'t have any color field type configured.');
      $empty_entity_field_name = new TranslatableMarkup("You can't set the color using the selected method because the referenced entity doesn't have any color field type configured.");
      return [
        '#theme' => 'status_messages',
        '#message_list' => ['warning' => [$empty_entity_field_name]],
+13 −43
Changes for src/Element/ChartDataCollectorTable.php: 13 added lines, 43 removed lines.
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\charts\Element;

use Drupal\charts\ColorHelperTrait;
use Drupal\charts\Plugin\chart\Library\ChartInterface;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\Html;
@@ -16,6 +17,10 @@ use Drupal\Core\Render\Element\FormElement;
 * @FormElement("chart_data_collector_table")
 */
class ChartDataCollectorTable extends FormElement {

  use ColorHelperTrait;
  use ElementFormStateTrait;

  const FIRST_COLUMN = 'first_column';
  const FIRST_ROW = 'first_row';

@@ -469,37 +474,6 @@ class ChartDataCollectorTable extends FormElement {
    $form_state->setRebuild();
  }

  /**
   * Gets the element state.
   *
   * @param array $parents
   *   The element parents.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The element state.
   */
  public static function getElementState(array $parents, FormStateInterface $form_state) {
    $parents = array_merge(['element_state', '#parents'], $parents);
    return NestedArray::getValue($form_state->getStorage(), $parents);
  }

  /**
   * Sets the element state.
   *
   * @param array $parents
   *   The element parents.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $element_state
   *   The element state.
   */
  public static function setElementState(array $parents, FormStateInterface $form_state, array $element_state) {
    $parents = array_merge(['element_state', '#parents'], $parents);
    NestedArray::setValue($form_state->getStorage(), $parents, $element_state);
  }

  /**
   * Utility method to build a button render array for the various data table.
   *
@@ -817,10 +791,16 @@ class ChartDataCollectorTable extends FormElement {
          // Get all the data in this column and break out of this loop.
          if ($is_single_axis) {
            if (is_numeric($column) || is_string($column)) {
              $series[$i]['data'][] = [$series[$i]['name'], self::castValueToNumeric($column)];
              $series[$i]['data'][] = [
                $series[$i]['name'],
                self::castValueToNumeric($column),
              ];
            }
            elseif (is_array($column) && isset($column['data'])) {
              $series[$i]['data'][] = [$series[$i]['name'], self::castValueToNumeric($column['data'])];
              $series[$i]['data'][] = [
                $series[$i]['name'],
                self::castValueToNumeric($column['data']),
              ];
            }
          }
          else {
@@ -888,14 +868,4 @@ class ChartDataCollectorTable extends FormElement {
    return $value;
  }

  /**
   * Provide a random color.
   *
   * @return string
   *   A random color.
   */
  public static function randomColor() {
    return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
  }

}
+45 −0
Changes for src/Element/ElementFormStateTrait.php: 45 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\charts\Element;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;

/**
 * Contains useful form state related methods to use by element plugins.
 */
trait ElementFormStateTrait {

  /**
   * Gets the element state.
   *
   * @param array $parents
   *   The element parents.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array|null
   *   The element state. Possibly NULL if the value is NULL or not all
   *   nested parent keys exist.
   */
  public static function getElementState(array $parents, FormStateInterface $form_state): ?array {
    $parents = array_merge(['element_state', '#parents'], $parents);
    return NestedArray::getValue($form_state->getStorage(), $parents);
  }

  /**
   * Sets the element state.
   *
   * @param array $parents
   *   The element parents.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param array $element_state
   *   The element state.
   */
  public static function setElementState(array $parents, FormStateInterface $form_state, array $element_state): void {
    $parents = array_merge(['element_state', '#parents'], $parents);
    NestedArray::setValue($form_state->getStorage(), $parents, $element_state);
  }

}
+5 −0
Changes for src/Form/ChartsConfigForm.php: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -93,6 +93,11 @@ class ChartsConfigForm extends ConfigFormBase {
      unset($settings['defaults']);
    }

    // Process the default colors to remove unneeded data.
    foreach ($settings['display']['colors'] as $color_index => $color_item) {
      $settings['display']['colors'][$color_index] = $color_item['color'];
    }

    // Save the main settings.
    $config = $this->config('charts.settings');
    $config->set('charts_default_settings', $settings)
Loading