dvf-2.x-dev/src/Plugin/Visualisation/Style/AxisChart.php

src/Plugin/Visualisation/Style/AxisChart.php
<?php

namespace Drupal\dvf\Plugin\Visualisation\Style;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\dvf\FormElementAttributesTrait;

/**
 * Provides a base class for visualisation plugins with axis.
 */
abstract class AxisChart extends TableVisualisationStyleBase {

  use FormElementAttributesTrait;

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'axis' => [
        'styles' => [
          'rotated' => FALSE,
        ],
        'x' => [
          'type' => '',
          'label' => [
            'text' => '',
          ],
          'tick' => [
            'count' => '',
            'culling' => '',
            'values' => [
              'field' => '',
              'custom' => '',
            ],
            'format' => [
              'indexed' => '',
              'timeseries' => [
                'input' => '',
                'output' => '',
              ],
            ],
          ],
          'styles' => [
            'padding' => [
              'left' => '',
              'right' => '',
            ],
            'label' => [
              'position' => '',
            ],
            'tick' => [
              'rotate' => '',
              'width' => '',
              'centered' => FALSE,
              'multiline' => TRUE,
            ],
          ],
        ],
        'y' => [
          'type' => '',
          'label' => [
            'text' => '',
          ],
          'tick' => [
            'count' => '',
            'values' => [
              'custom' => '',
            ],
            'rounding' => '',
            'format' => [
              'indexed' => '',
            ],
          ],
          'styles' => [
            'padding' => [
              'top' => '',
              'bottom' => '',
            ],
            'label' => [
              'position' => '',
            ],
          ],
        ],
      ],
      'grid' => [
        'x' => [
          'show' => FALSE,
        ],
        'y' => [
          'show' => FALSE,
        ],
        'lines' => [],
      ],
      'chart' => [
        'title' => [
          'show' => TRUE,
          'text' => '',
        ],
        'interaction' => TRUE,
        'table' => [
          'disable' => FALSE,
          'datatable' => FALSE,
        ],
        'data' => [
          'labels' => [
            'show' => FALSE,
          ],
          'legends' => [
            'interaction' => TRUE,
          ],
        ],
        'styles' => [
          'width' => '',
          'height' => '',
          'padding' => [
            'top' => '',
            'right' => '',
            'bottom' => '',
            'left' => '',
          ],
        ],
        'component' => 'table-chart',
      ],
    ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $form = parent::settingsForm($form, $form_state);

    $form['axis'] = [
      '#type' => 'details',
      '#title' => $this->t('Axis settings'),
      '#tree' => TRUE,
    ];

    $form['axis']['styles'] = [
      '#type' => 'details',
      '#title' => $this->t('Axis styles'),
      '#tree' => TRUE,
    ];

    $form['axis']['styles']['rotated'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Rotate orientation'),
      '#description' => $this->t('Check to switch the X and Y axis position.'),
      '#default_value' => $this->config('axis', 'styles', 'rotated'),
    ];

    $form['axis']['x'] = [
      '#type' => 'details',
      '#title' => $this->t('X axis settings'),
      '#tree' => TRUE,
    ];

    $form['axis']['x']['type'] = [
      '#type' => 'select',
      '#title' => $this->t('Axis type'),
      '#description' => $this->t('Set the data type for the X axis values. E.g. If the X axis contains numbers, select "Indexed (numeric)".'),
      '#options' => [
        '' => $this->t('Automatic (Indexed if numeric, category if not)'),
        'indexed' => $this->t('Indexed (numeric)'),
        'category' => $this->t('Category (non-numeric)'),
        'timeseries' => $this->t('Date or time'),
      ],
      '#default_value' => $this->config('axis', 'x', 'type'),
    ];

    $form['axis']['x']['label']['text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Axis title'),
      '#description' => $this->t('Add a X axis title to describe what the data values relate to.'),
      '#default_value' => $this->config('axis', 'x', 'label', 'text'),
    ];

    $form['axis']['x']['tick']['count'] = [
      '#type' => 'number',
      '#title' => $this->t('Tick count'),
      '#description' => $this->t('Limit the number of ticks on the X axis.'),
      '#default_value' => $this->config('axis', 'x', 'tick', 'count'),
    ];

    $form['axis']['x']['tick']['culling'] = [
      '#type' => 'number',
      '#title' => $this->t('Tick culling'),
      '#description' => $this->t('Limit the number of ticks on the X axis by rounding to the nearest whole number.'),
      '#default_value' => $this->config('axis', 'x', 'tick', 'culling'),
    ];

    $ajaxWrapperId = $form['#attributes']['id'] . '--x-axis-grouping';

    $form['axis']['x']['tick']['values']['field'] = [
      '#type' => 'select',
      '#title' => $this->t('Tick values field'),
      '#description' => $this->t('Override the individual X axis data-label values using data from a field.'),
      '#options' => $this->getSourceFieldOptions(),
      '#empty_option' => $this->t('- None -'),
      '#empty_value' => '',
      '#default_value' => $this->config('axis', 'x', 'tick', 'values', 'field'),
      '#ajax' => [
        'callback' => [$this, 'updateAxisGrouping'],
        'wrapper' => $ajaxWrapperId,
      ],
    ];

    $form['axis']['x']['tick']['values']['custom'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tick values'),
      '#description' => $this->t('Override the individual X axis data-label values manually. Separate values with a comma.'),
      '#default_value' => $this->config('axis', 'x', 'tick', 'values', 'custom'),
    ];

    $form['axis']['x']['x_axis_grouping'] = [
      '#prefix' => "<div id=$ajaxWrapperId>",
      '#suffix' => '</div>',
      '#type' => 'select',
      '#title' => $this->t('X Axis Grouping'),
      '#options' => [
        'keys' => $this->t('Group by keys'),
        'values' => $this->t('Group by label values'),
      ],
      '#default_value' => $this->config('axis', 'x', 'x_axis_grouping'),
      '#description' => $this->t(
        'The X axis can use <em>keys</em> or the <em>label key value</em> as tick values. Changing this swaps what is displayed on the X axis (or table header if viewed as a table). <em>Tick values field</em> must be set for "labels" options to work correctly. @help.',
        ['@help' => $this->dvfHelpers->getHelpPageLink('x-axis-grouping')]),
      '#ajax' => [
        'callback' => [$this, 'updateColumnOverrides'],
        'wrapper' => 'column-overrides',
      ],
    ];

    if (!$this->config('axis', 'x', 'tick', 'values', 'field')) {
      $form['axis']['x']['x_axis_grouping']['#attributes']['disabled'] = 'disabled';
    } else {
      unset($form['axis']['x']['x_axis_grouping']['#attributes']['disabled']);
    }

    $form['axis']['x']['tick']['format']['indexed'] = [
      '#type' => 'select',
      '#title' => $this->t('Tick label format'),
      '#description' => $this->t('Format the X axis tick label.'),
      '#options' => [
        '' => $this->t('None'),
        ' ' => $this->t('Space separated Eg. 10 000'),
        ',' => $this->t('Comma separated Eg. 10,000'),
      ],
      '#default_value' => $this->config('axis', 'x', 'tick', 'format', 'indexed'),
    ];

    $timeseries_legend = '%d = day, %m = month, %Y = year, %H = hour, %M = min, %S = second';

    $form['axis']['x']['tick']['format']['timeseries']['input'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tick label input format'),
      '#description' => $this->t('How the date is formatted in the dataset. Replacements: %replacements', ['%replacements' => $timeseries_legend]),
      '#default_value' => $this->config('axis', 'x', 'tick', 'format', 'timeseries', 'input'),
    ];

    $form['axis']['x']['tick']['format']['timeseries']['output'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tick label output format'),
      '#description' => $this->t('How the date is formatted on the X axis. Replacements: %replacements', ['%replacements' => $timeseries_legend]),
      '#default_value' => $this->config('axis', 'x', 'tick', 'format', 'timeseries', 'output'),
    ];

    $form['axis']['x']['styles'] = [
      '#type' => 'details',
      '#title' => $this->t('X axis styles'),
      '#description' => $this->t('Enter whole numbers (E.g. 320). Values are set as pixels unless otherwise indicated.<br/><br/>'),
      '#tree' => TRUE,
    ];

    foreach (['left', 'right'] as $edge) {
      $form['axis']['x']['styles']['padding'][$edge] = [
        '#type' => 'number',
        '#title' => $this->t('Axis padding @edge', ['@edge' => $edge]),
        '#description' => $this->t('Define the padding at the @edge end of the X axis.', ['@edge' => $edge]),
        '#default_value' => $this->config('axis', 'x', 'styles', 'padding', $edge),
      ];
    }

    $form['axis']['x']['styles']['label']['position'] = [
      '#type' => 'select',
      '#title' => $this->t('Axis title position'),
      '#description' => $this->t('Define the title position on the X axis.'),
      '#options' => [
        'inner-right' => $this->t('Inner right'),
        'inner-center' => $this->t('Inner center'),
        'inner-left' => $this->t('Inner left'),
        'outer-right' => $this->t('Outer right'),
        'outer-center' => $this->t('Outer center'),
        'outer-left' => $this->t('Outer left'),
      ],
      '#default_value' => $this->config('axis', 'x', 'styles', 'label', 'position'),
      '#empty_option' => $this->t('- Select -'),
      '#empty_option_value' => '',
    ];

    $form['axis']['x']['styles']['tick']['rotate'] = [
      '#type' => 'number',
      '#title' => $this->t('Tick label rotation'),
      '#description' => $this->t('Optionally rotate the X axis labels, by setting how many degrees the X axis tick label should be rotated here.'),
      '#default_value' => $this->config('axis', 'x', 'styles', 'tick', 'rotate'),
    ];

    $form['axis']['x']['styles']['tick']['width'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tick label width'),
      '#description' => $this->t('Define the width of the X axis tick labels.'),
      '#size' => 20,
      '#default_value' => $this->config('axis', 'x', 'styles', 'tick', 'width'),
    ];

    $form['axis']['x']['styles']['tick']['centered'] = [
      '#title' => $this->t('Tick label centered'),
      '#description' => $this->t('Check to display tick directly above the label on the X axis.'),
      '#type' => 'checkbox',
      '#default_value' => $this->config('axis', 'x', 'styles', 'tick', 'centered'),
    ];

    $form['axis']['x']['styles']['tick']['multiline'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Tick label multiline'),
      '#description' => $this->t('Check to allow the X axis label to be wrapped onto multiple lines.'),
      '#default_value' => $this->config('axis', 'x', 'styles', 'tick', 'multiline'),
    ];

    $form['axis']['y'] = [
      '#type' => 'details',
      '#title' => $this->t('Y axis settings'),
      '#tree' => TRUE,
    ];

    $form['axis']['y']['type'] = [
      '#type' => 'select',
      '#title' => $this->t('Axis type'),
      '#description' => $this->t('Set the data type for the Y axis values. E.g. If the Y axis contains numbers, select "Indexed (numeric)".'),
      '#options' => [
        '' => $this->t('Automatic (Indexed if numeric, category if not)'),
        'indexed' => $this->t('Indexed (numeric)'),
        'category' => $this->t('Category (non-numeric)'),
        'timeseries' => $this->t('Date or time'),
      ],
      '#default_value' => $this->config('axis', 'y', 'type'),
    ];

    $form['axis']['y']['label']['text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Axis title'),
      '#description' => $this->t('Add a Y axis title to describe what the data values relate to.'),
      '#default_value' => $this->config('axis', 'y', 'label', 'text'),
    ];

    $form['axis']['y']['tick']['count'] = [
      '#type' => 'number',
      '#title' => $this->t('Tick count'),
      '#description' => $this->t('Limit the number of ticks on the Y axis.'),
      '#default_value' => $this->config('axis', 'y', 'tick', 'count'),
    ];

    $form['axis']['y']['tick']['values']['custom'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Tick values'),
      '#description' => $this->t('Override the individual Y axis data-label values manually. Separate values with a comma.'),
      '#default_value' => $this->config('axis', 'y', 'tick', 'values', 'custom'),
    ];

    $form['axis']['y']['tick']['rounding'] = [
      '#type' => 'select',
      '#title' => $this->t('Tick label rounding'),
      '#description' => $this->t('Format the Y axis tick label by rounding to a specific decimal place.'),
      '#options' => [
        '' => $this->t('Automatic'),
        '1' => $this->t('1 place'),
        '2' => $this->t('2 places'),
        '3' => $this->t('3 places'),
        '4' => $this->t('4 places'),
      ],
      '#default_value' => $this->config('axis', 'y', 'tick', 'rounding'),
    ];

    $form['axis']['y']['tick']['format']['indexed'] = [
      '#type' => 'select',
      '#title' => $this->t('Tick label format'),
      '#description' => $this->t('Format the Y axis tick label.'),
      '#options' => [
        '' => $this->t('None'),
        ' ' => $this->t('Space separated Eg. 10 000'),
        ',' => $this->t('Comma separated Eg. 10,000'),
      ],
      '#default_value' => $this->config('axis', 'y', 'tick', 'format', 'indexed'),
    ];

    $form['axis']['y']['styles'] = [
      '#type' => 'details',
      '#title' => $this->t('Y axis styles'),
      '#description' => $this->t('Enter whole numbers (E.g. 320). Values are set as pixels unless otherwise indicated.<br/><br/>'),
      '#tree' => TRUE,
    ];

    foreach (['top', 'bottom'] as $edge) {
      $form['axis']['y']['styles']['padding'][$edge] = [
        '#type' => 'number',
        '#title' => $this->t('Axis padding @edge', ['@edge' => $edge]),
        '#description' => $this->t('Define the padding at the @edge end of the Y axis.', ['@edge' => $edge]),
        '#default_value' => $this->config('axis', 'y', 'styles', 'padding', $edge),
      ];
    }

    $form['axis']['y']['styles']['label']['position'] = [
      '#type' => 'select',
      '#title' => $this->t('Axis title position'),
      '#description' => $this->t('Define the title position on the Y axis.'),
      '#options' => [
        'inner-top' => $this->t('Inner top'),
        'inner-middle' => $this->t('Inner middle'),
        'inner-bottom' => $this->t('Inner bottom'),
        'outer-top' => $this->t('Outer top'),
        'outer-middle' => $this->t('Outer middle'),
        'outer-bottom' => $this->t('Outer bottom'),
      ],
      '#default_value' => $this->config('axis', 'y', 'styles', 'label', 'position'),
      '#empty_option' => $this->t('- Select -'),
      '#empty_option_value' => '',
    ];

    $form['grid'] = [
      '#type' => 'details',
      '#title' => $this->t('Grid settings'),
      '#tree' => TRUE,
    ];

    $form['grid']['x']['show'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show X axis grid'),
      '#description' => $this->t('Check to show thin dashed lines along the X axis grid points.'),
      '#default_value' => $this->config('grid', 'x', 'show'),
    ];

    $form['grid']['y']['show'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show Y axis grid'),
      '#description' => $this->t('Check to show thin dashed lines along the Y axis grid points.'),
      '#default_value' => $this->config('grid', 'y', 'show'),
    ];

    $grid_lines_count = $form_state->get('grid_lines_count');

    if (is_null($grid_lines_count)) {
      $grid_lines_values = $this->config('grid', 'lines');
      $grid_lines_count = count($grid_lines_values);
    }

    $form_state->set('grid_lines_count', $grid_lines_count);

    $form['grid']['lines'] = [
      '#type' => 'details',
      '#title' => $this->t('Grid lines'),
      '#description' => $this->t('Show additional grid lines along X or Y axis. @help',
        ['@help' => $this->dvfHelpers->getHelpPageLink('grid-lines')]),
      '#open' => ($grid_lines_count > 0),
      '#tree' => TRUE,
      '#attributes' => [
        'id' => Html::getUniqueId('dvf-options-grid-lines'),
        'title' => $this->t('E.g. If a dataset contains values between 1 & 40, indicate the average numeric value by adding a Y axis line with a value of 20, and a label "Average number".'),
      ],
    ];

    for ($i = 0; $i < $grid_lines_count; $i++) {
      $form['grid']['lines'][$i] = [
        '#type' => 'fieldset',
        '#title' => $this->t('Grid line @num', ['@num' => ($i + 1)]),
        '#tree' => TRUE,
        '#attributes' => ['class' => ['container-inline']],
      ];

      $form['grid']['lines'][$i]['axis'] = [
        '#type' => 'select',
        '#title' => $this->t('Axis'),
        '#options' => [
          'x' => $this->t('X axis'),
          'y' => $this->t('Y axis'),
        ],
        '#default_value' => $this->config('grid', 'lines', $i, 'axis'),
      ];

      $form['grid']['lines'][$i]['value'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Value'),
        '#size' => 20,
        '#default_value' => $this->config('grid', 'lines', $i, 'value'),
      ];

      $form['grid']['lines'][$i]['text'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Label'),
        '#size' => 20,
        '#default_value' => $this->config('grid', 'lines', $i, 'text'),
      ];

      $form['grid']['lines'][$i]['position'] = [
        '#title' => $this->t('Label position'),
        '#type' => 'select',
        '#options' => [
          'start' => $this->t('Start'),
          'middle' => $this->t('Middle'),
          'end' => $this->t('End'),
        ],
        '#default_value' => $this->config('grid', 'lines', $i, 'position'),
      ];

      $form['grid']['lines'][$i]['class'] = [
        '#title' => $this->t('CSS Class (optional)'),
        '#type' => 'textfield',
        '#size' => 20,
        '#default_value' => $this->config('grid', 'lines', $i, 'class'),
      ];
    }

    $form['grid']['lines']['actions'] = [
      '#type' => 'actions',
      '#tree' => FALSE,
    ];

    $form['grid']['lines']['actions']['add'] = [
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#value' => $this->t('Add line'),
      '#submit' => [[$this, 'addGridLine']],
      '#ajax' => [
        'callback' => [$this, 'updateGridLines'],
        'wrapper' => $form['grid']['lines']['#attributes']['id'],
      ],
    ];

    $form['grid']['lines']['actions']['remove'] = [
      '#type' => 'submit',
      '#value' => $this->t('Remove line'),
      '#submit' => [[$this, 'removeGridLine']],
      '#ajax' => [
        'callback' => [$this, 'updateGridLines'],
        'wrapper' => $form['grid']['lines']['#attributes']['id'],
      ],
      '#access' => ($grid_lines_count > 0),
    ];

    $form['chart'] = [
      '#type' => 'details',
      '#title' => $this->t('Chart settings'),
      '#tree' => TRUE,
    ];

    $form['chart']['title']['text'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Chart title'),
      '#description' => $this->t('Embed a title in the chart. NOTE: if this is used with a split chart, the same title will display on each chart'),
      '#default_value' => $this->config('chart', 'title', 'text'),
    ];

    $form['chart']['interaction'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable chart interaction'),
      '#description' => $this->t('Check to enable all of interactions (E.g. Showing / hiding the tooltip when hovering on labels, etc).'),
      '#default_value' => $this->config('chart', 'interaction'),
    ];

    $form['chart']['table']['disable'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Disable fallback table'),
      '#description' => $this->t('Disable the option to view data as a table via a "Show table" button'),
      '#default_value' => $this->config('chart', 'table', 'disable'),
    ];

    $form['chart']['table']['datatable'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Fallback table uses datatables'),
      '#description' => $this->t('Render the fallback table using datatables.js. This may reduce accessibility as Javascript is required'),
      '#default_value' => $this->config('chart', 'table', 'datatable'),
    ];

    $form['chart']['data']['labels']['show'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Show data labels'),
      '#description' => $this->t('Check to show the data labels above the points.'),
      '#default_value' => $this->config('chart', 'data', 'labels', 'show'),
    ];

    $form['chart']['data']['legends']['interaction'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable legends interaction'),
      '#description' => $this->t('Check to enable interactions for the legend (E.g. Hover on legend labels).'),
      '#default_value' => $this->config('chart', 'data', 'legends', 'interaction'),
    ];

    $form['chart']['styles'] = [
      '#type' => 'details',
      '#title' => $this->t('Chart styles'),
      '#description' => $this->t('Enter whole numbers (E.g. 320). Values are set as pixels unless otherwise indicated.<br/><br/>'),
      '#tree' => TRUE,
    ];

    foreach (['width', 'height'] as $measure) {
      $form['chart']['styles'][$measure] = [
        '#type' => 'number',
        '#title' => ucwords($measure),
        '#description' => $this->t('Define the @measure of the chart.', ['@measure' => $measure]),
        '#default_value' => $this->config('chart', 'styles', $measure),
      ];
    }

    foreach (['top', 'right', 'bottom', 'left'] as $edge) {
      $form['chart']['styles']['padding'][$edge] = [
        '#type' => 'number',
        '#title' => $this->t('Padding @edge', ['@edge' => $edge]),
        '#description' => $this->t('Define the amount of padding that will be applied to the @edge of the chart.', ['@edge' => $edge]),
        '#default_value' => $this->config('chart', 'styles', 'padding', $edge),
      ];
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public static function afterBuildSettingsForm(array $element, FormStateInterface $form_state) {
    $element = parent::afterBuildSettingsForm($element, $form_state);

    $selectors = [
      'axis_x_type' => self::formElementSelector($element['axis']['x']['type'], 'select'),
      'axis_x_tick_values_field' => self::formElementSelector($element['axis']['x']['tick']['values']['field'], 'select'),
      'axis_y_type' => self::formElementSelector($element['axis']['y']['type'], 'select'),
    ];

    $element['axis']['x']['tick']['culling']['#states'] = [
      'visible' => [$selectors['axis_x_type'] => ['value' => 'indexed']],
    ];

    $element['axis']['x']['tick']['values']['custom']['#states'] = [
      'visible' => [$selectors['axis_x_tick_values_field'] => ['value' => '']],
    ];

    $element['axis']['x']['tick']['format']['indexed']['#states'] = [
      'visible' => [$selectors['axis_x_type'] => ['value' => 'indexed']],
    ];

    $element['axis']['x']['tick']['format']['timeseries']['input']['#states'] = [
      'visible' => [$selectors['axis_x_type'] => ['value' => 'timeseries']],
    ];

    $element['axis']['x']['tick']['format']['timeseries']['output']['#states'] = [
      'visible' => [$selectors['axis_x_type'] => ['value' => 'timeseries']],
    ];

    $element['axis']['y']['tick']['rounding']['#states'] = [
      'visible' => [$selectors['axis_y_type'] => ['value' => 'indexed']],
    ];

    $element['axis']['y']['tick']['format']['indexed']['#states'] = [
      'visible' => [$selectors['axis_y_type'] => ['value' => 'indexed']],
    ];

    return $element;
  }

  /**
   * Updates the grid lines options.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The updated form element.
   */
  public function updateGridLines(array $form, FormStateInterface $form_state) {
    $triggering_element = $form_state->getTriggeringElement();
    $elements = NestedArray::getValue($form, array_slice($triggering_element['#array_parents'], 0, -3));

    return $elements['lines'];
  }

  /**
   * Submit handler for adding a new grid line.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public function addGridLine(array &$form, FormStateInterface $form_state) {
    $form_state->set('grid_lines_count', $form_state->get('grid_lines_count') + 1);
    $form_state->setRebuild();
  }

  /**
   * Submit handler for removing a grid line.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public function removeGridLine(array &$form, FormStateInterface $form_state) {
    $grid_lines_count = $form_state->get('grid_lines_count');

    if ($grid_lines_count > 0) {
      $form_state->set('grid_lines_count', $grid_lines_count - 1);
    }

    $form_state->setRebuild();
  }

  /**
   * Ajax callback that updates the column overrides options.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The updated form element.
   */
  public function updateColumnOverrides(array $form, FormStateInterface $form_state) {
    $field_name = self::formElementCallbackParentName($form_state);
    return $form[$field_name]['widget'][0]['options']['visualisation_style_options']['data']['column_overrides'];
  }

  /**
   * Ajax callback that updates the x axis grouping options.
   *
   * @param array $form
   *   The form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The updated form element.
   */
  public function updateAxisGrouping(array $form, FormStateInterface $form_state) {
    $field_name = self::formElementCallbackParentName($form_state);
    return $form[$field_name]['widget'][0]['options']['visualisation_style_options']['axis']['x']['x_axis_grouping'];
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $build = [];

    foreach ($this->getSourceRecords() as $group_key => $group_records) {
      $group_id = strtolower(Html::cleanCssIdentifier($group_key));

      $build[$group_id] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => [
            'dvf--wrapper',
            'dvf-chart--wrapper',
            'dvf-chart--wrapper--' . $group_id,
          ],
        ],
      ];

      $build[$group_id]['chart'] = [
        '#type' => 'container',
        '#attributes' => ['class' => ['dvf-chart']],
        'heading' => $this->buildSplitHeading($group_key),
        'content' => $this->buildChart($group_records),
      ];

      // Accessible version of the chart.
      if (!$this->config('chart', 'table', 'disable')) {
        $build[$group_id]['table'] = [
          '#type' => 'container',
          '#attributes' => ['class' => ['dvf-table', 'visually-hidden']],
          'content' => $this->buildTable($group_records, (bool) $this->config('chart', 'table', 'datatable')),
        ];
      }

      // A wrapper for the action buttons (toggle, download etc).
      $build[$group_id]['actions'] = [
        '#type' => 'container',
        '#attributes' => ['class' => ['table-chart--actions']],
      ];

      // If $file_uri is empty/false, do not display download data button.
      $file_uri = $this->getDatasetDownloadUri();
      if (!empty($file_uri)) {
        $build[$group_id]['actions']['file_uri'] = [
          '#type' => 'html_tag',
          '#tag' => 'button',
          '#value' => $this->t('Download data'),
          '#attributes' => [
            'class' => ['download-data'],
            'data-file-uri' => $file_uri,
          ],
        ];
      }

    }

    return $build;
  }

  /**
   * Builds and returns a chart renderable array for this plugin.
   *
   * @param array $records
   *   The records.
   *
   * @return array
   *   A chart renderable array.
   */
  protected function buildChart(array $records) {
    $chart_id = hash('sha256', time() . mt_rand());

    $chart = [
      '#type' => 'html_tag',
      '#tag' => 'div',
      '#attributes' => ['data-dvfcharts' => $chart_id],
    ];

    $chart['#attached']['library'] = ['dvf/dvfCharts'];
    $chart['#attached']['drupalSettings']['dvf']['charts'][$chart_id] = $this->chartBuildSettings($records);

    return $chart;
  }

  /**
   * Returns the chart build settings for this plugin.
   *
   * @param array $records
   *   The records.
   *
   * @return array
   *   An array of chart build settings.
   */
  protected function chartBuildSettings(array $records) {
    $settings = [
      'axis' => $this->config('axis'),
      'grid' => $this->config('grid'),
      'chart' => $this->config('chart'),
    ];

    if (empty($records)) {
      $this->messenger->addError($this->t('Invalid records.'));
      return [];
    }

    // Axis X and Y tick values from user input.
    foreach (['x', 'y'] as $axis) {
      if ($this->config('axis', $axis, 'tick', 'values', 'custom')) {
        $settings['axis'][$axis]['tick']['values']['custom'] = array_map('trim', explode(',', $this->config('axis', $axis, 'tick', 'values', 'custom')));
      }
    }

    // Axis X tick values from source data, override user input.
    if ($this->config('axis', 'x', 'tick', 'values', 'field')) {
      $settings['axis']['x']['tick']['values']['custom'] = [];

      foreach ($records as $record) {
        if (property_exists($record, $this->config('axis', 'x', 'tick', 'values', 'field'))) {
          $settings['axis']['x']['tick']['values']['custom'][] = trim($record->{$this->config('axis', 'x', 'tick', 'values', 'field')});
        }
      }
    }

    // Get the row headers.
    $ticks_custom = $settings['axis']['x']['tick']['values']['custom'] ?? [];

    // Data columns.
    foreach ($this->fields() as $field) {
      $settings['chart']['data']['columns'][] = array_merge([$field], $this->getSourceFieldValues($field, $records));
    }

    // Override fields labels if set in chart options.
    $settings['chart']['data']['names'] = $this->fieldLabels();

    // Set the chart title.
    $settings['chart']['title']['text'] = $this->config('chart', 'title', 'text');

    // Column overrides.
    $settings['chart']['data']['column_overrides'] = $this->getColumnOverrides();

    // If X axis grouping occurs on labels, flip the values and the labels.
    if ($this->config('axis', 'x', 'x_axis_grouping') === 'values') {
      $column_labels = array_map('reset', $settings['chart']['data']['columns']);
      $flipped_columns = [];

      foreach ($records as $i => $record) {
        $flipped_columns[] = array_merge(
          [$settings['axis']['x']['tick']['values']['custom'][$i]],
          array_values(array_intersect_key((array) $record, array_flip($column_labels)))
        );
      }

      $settings['chart']['data']['columns'] = $flipped_columns;
      $settings['axis']['x']['tick']['values']['custom'] = $column_labels;
    }

    // Check if values are auto.
    $tick_values_field = $this->config('axis', 'x', 'tick', 'values', 'field');

    if ($settings['axis']['x']['type'] === '' && !empty($record) && property_exists($record, $tick_values_field)) {
      // Check whether $tick_values_field is a number and $tick_values_field
      // cannot be one of the row header values.
      is_numeric($record->{$tick_values_field}) && !in_array($record->{$tick_values_field}, $ticks_custom) ?
        $settings['axis']['x']['type'] = 'indexed' :
        $settings['axis']['x']['type'] = 'category';
    }

    if ($settings['axis']['y']['type'] === '' && !empty($record) && property_exists($record, $field)) {
      is_numeric($record->{$field}) ?
        $settings['axis']['y']['type'] = 'indexed' :
        $settings['axis']['y']['type'] = 'category';
    }

    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  protected function tableHeaderField() {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  protected function rowHeaderField() {
    return $this->config('axis', 'x', 'tick', 'values', 'field');
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc