You are here

class Chartjs in Charts 5.0.x

Same name and namespace in other branches
  1. 8.4 modules/charts_chartjs/src/Plugin/chart/Library/Chartjs.php \Drupal\charts_chartjs\Plugin\chart\Library\Chartjs

Define a concrete class for a Chart.

Plugin annotation


@Chart(
  id = "chartjs",
  name = @Translation("Chart.js")
)

Hierarchy

Expanded class hierarchy of Chartjs

File

modules/charts_chartjs/src/Plugin/chart/Library/Chartjs.php, line 21

Namespace

Drupal\charts_chartjs\Plugin\chart\Library
View source
class Chartjs extends ChartBase {

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form = parent::buildConfigurationForm($form, $form_state);
    $form['placeholder'] = [
      '#title' => $this
        ->t('Placeholder'),
      '#type' => 'fieldset',
      '#description' => $this
        ->t('This is a placeholder for Chart.js-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.', [
        '@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046984')
          ->toString(),
      ]),
    ];
    $xaxis_configuration = $this->configuration['xaxis'] ?? [];
    $yaxis_configuration = $this->configuration['yaxis'] ?? [];
    $form['xaxis'] = [
      '#title' => $this
        ->t('X-Axis Settings'),
      '#type' => 'fieldset',
      '#tree' => TRUE,
    ];
    $form['xaxis']['autoskip'] = [
      '#title' => $this
        ->t('Enable autoskip'),
      '#type' => 'checkbox',
      '#default_value' => $xaxis_configuration['autoskip'] ?? 1,
    ];
    $form['xaxis']['horizontal_axis_title_align'] = [
      '#title' => $this
        ->t('Align horizontal axis title'),
      '#type' => 'select',
      '#options' => [
        'start' => $this
          ->t('Start'),
        'center' => $this
          ->t('Center'),
        'end' => $this
          ->t('End'),
      ],
      '#default_value' => $xaxis_configuration['horizontal_axis_title_align'] ?? '',
    ];
    $form['yaxis'] = [
      '#title' => $this
        ->t('Y-Axis Settings'),
      '#type' => 'fieldset',
      '#tree' => TRUE,
    ];
    $form['yaxis']['vertical_axis_title_align'] = [
      '#title' => $this
        ->t('Align vertical axis title'),
      '#type' => 'select',
      '#options' => [
        'start' => $this
          ->t('Start'),
        'center' => $this
          ->t('Center'),
        'end' => $this
          ->t('End'),
      ],
      '#default_value' => $yaxis_configuration['vertical_axis_title_align'] ?? '',
    ];
    return $form;
  }

  /**
   * Build configurations.
   *
   * @param array $form
   *   The form element.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    parent::submitConfigurationForm($form, $form_state);
    if (!$form_state
      ->getErrors()) {
      $values = $form_state
        ->getValue($form['#parents']);
      $this->configuration['xaxis'] = $values['xaxis'];
      $this->configuration['yaxis'] = $values['yaxis'];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function preRender(array $element) {
    $chart_definition = [];
    if (!isset($element['#id'])) {
      $element['#id'] = Html::getUniqueId('chartjs-render');
    }
    $chart_definition = $this
      ->populateCategories($element, $chart_definition);
    $chart_definition = $this
      ->populateDatasets($element, $chart_definition);
    $chart_definition = $this
      ->populateOptions($element, $chart_definition);
    $element['#attached']['library'][] = 'charts_chartjs/chartjs';
    $element['#attributes']['class'][] = 'charts-chartjs';
    $element['#chart_definition'] = $chart_definition;
    return $element;
  }

  /**
   * Populate options.
   *
   * @param array $element
   *   The element.
   * @param array $chart_definition
   *   The chart definition.
   *
   * @return array
   *   Return the chart definition.
   */
  private function populateOptions(array $element, array $chart_definition) {
    $chart_type = $this
      ->populateChartType($element);
    $chart_definition['type'] = $chart_type;
    $children = Element::children($element);

    /*
     * Setting defaults based on what Views uses. However, API users may
     * have different keys for their X and Y axes.
     */
    $x_axis_key = 'xaxis';
    $y_axis_key = 'yaxis';
    foreach ($children as $child) {
      $type = $element[$child]['#type'];
      if ($type === 'chart_xaxis') {
        $x_axis_key = $child;
      }
      if ($type === 'chart_yaxis') {
        $y_axis_key = $child;
      }
    }
    $xaxis_configuration = $this->configuration['xaxis'] ?? [];
    $yaxis_configuration = $this->configuration['yaxis'] ?? [];
    if (!in_array($chart_type, [
      'pie',
      'doughnut',
    ])) {
      if (!empty($element['#stacking']) && $element['#stacking'] == 1) {
        $stacking = TRUE;
      }
      else {
        $stacking = FALSE;
      }
      if ($chart_type !== 'radar') {
        $chart_definition['options']['scales']['x'] = [
          'stacked' => $stacking,
          'ticks' => [
            'autoSkip' => $xaxis_configuration['autoskip'] ?? 1,
            'maxRotation' => $element[$x_axis_key]['#labels_rotation'] ?? 0,
            'minRotation' => $element[$x_axis_key]['#labels_rotation'] ?? 0,
          ],
        ];
        $chart_definition['options']['scales']['y'] = [
          'ticks' => [
            'beginAtZero' => NULL,
            'maxRotation' => $element[$y_axis_key]['#labels_rotation'] ?? 0,
            'minRotation' => $element[$y_axis_key]['#labels_rotation'] ?? 0,
          ],
          'maxTicksLimit' => 11,
          'precision' => NULL,
          'stepSize' => NULL,
          'suggestedMax' => NULL,
          'suggestedMin' => NULL,
          'stacked' => $stacking,
        ];
        if (!empty($element[$y_axis_key]['#min'])) {
          $chart_definition['options']['scales']['y']['min'] = $element[$y_axis_key]['#min'];
        }
        if (!empty($element[$y_axis_key]['#max'])) {
          $chart_definition['options']['scales']['y']['max'] = $element[$y_axis_key]['#max'];
        }
        if (!empty($element[$y_axis_key]['#title'])) {
          $chart_definition['options']['scales']['y']['title']['display'] = TRUE;
          $chart_definition['options']['scales']['y']['title']['text'] = $element[$y_axis_key]['#title'];
          $chart_definition['options']['scales']['y']['title']['align'] = $yaxis_configuration['vertical_axis_title_align'];
        }
        if (!empty($element[$x_axis_key]['#title'])) {
          $chart_definition['options']['scales']['x']['title']['display'] = TRUE;
          $chart_definition['options']['scales']['x']['title']['text'] = $element[$x_axis_key]['#title'];
          $chart_definition['options']['scales']['x']['title']['align'] = $xaxis_configuration['horizontal_axis_title_align'];
        }
      }
    }

    // Horizontal bar charts are configured by changing the bar chart indexAxis.
    // See https://www.chartjs.org/docs/latest/charts/bar.html#horizontal-bar-chart
    if ($element['#chart_type'] === 'bar') {
      $chart_definition['options']['indexAxis'] = 'y';
    }
    $chart_definition['options']['plugins']['title'] = $this
      ->buildTitle($element);
    $chart_definition['options']['plugins']['tooltip']['enabled'] = $element['#tooltips'];
    $chart_definition['options']['plugins']['legend'] = $this
      ->buildLegend($element);

    // Merge in chart raw options.
    if (!empty($element['#raw_options'])) {
      $chart_definition = NestedArray::mergeDeepArray([
        $chart_definition,
        $element['#raw_options'],
      ]);
    }
    return $chart_definition;
  }

  /**
   * Populate categories.
   *
   * @param array $element
   *   The element.
   * @param array $chart_definition
   *   The chart definition.
   *
   * @return array
   *   Return the chart definition.
   */
  private function populateCategories(array $element, array $chart_definition) {
    $children = Element::children($element);
    $categories = [];
    foreach ($children as $child) {
      $type = $element[$child]['#type'];
      if ($type === 'chart_xaxis') {
        $categories = array_map('strip_tags', $element[$child]['#labels']);

        // Merge in axis raw options.
        if (!empty($element[$child]['#raw_options'])) {
          $categories = NestedArray::mergeDeepArray([
            $element[$child]['#raw_options'],
            $categories,
          ]);
        }
      }
    }
    $chart_definition['data']['labels'] = $categories;
    return $chart_definition;
  }

  /**
   * Populate Dataset.
   *
   * @param array $element
   *   The element.
   * @param array $chart_definition
   *   The chart definition.
   *
   * @return array
   *   Return the chart definition.
   */
  private function populateDatasets(array $element, array $chart_definition) {
    $chart_type = $this
      ->populateChartType($element);
    $datasets = [];
    foreach (Element::children($element) as $key) {
      if ($element[$key]['#type'] === 'chart_data') {
        $series_data = [];
        $dataset = new \stdClass();

        // Populate the data.
        foreach ($element[$key]['#data'] as $data_index => $data) {
          if (isset($series_data[$data_index])) {
            $series_data[$data_index][] = $data;
          }
          else {
            if ($chart_type === 'scatter') {
              $data = [
                'y' => $data[1],
                'x' => $data[0],
              ];
            }

            /*
             * This is here to account for differences between Views and
             * the API. Will change if someone can find a better way.
             */
            if (in_array($chart_type, [
              'pie',
              'doughnut',
            ]) && !empty($data[1])) {
              $data = $data[1];
            }
            $series_data[$data_index] = $data;
          }
        }
        $dataset->label = $element[$key]['#title'];
        $dataset->data = $series_data;
        if (!in_array($chart_type, [
          'pie',
          'doughnut',
        ])) {
          $dataset->borderColor = $element[$key]['#color'];
        }
        $dataset->backgroundColor = $element[$key]['#color'];
        $series_type = isset($element[$key]['#chart_type']) ? $this
          ->populateChartType($element[$key]) : $chart_type;
        $dataset->type = $series_type;
        if (!empty($element[$key]['#chart_type']) && $element[$key]['#chart_type'] === 'area') {
          $dataset->fill = 'origin';
          $dataset->backgroundColor = $this
            ->getTranslucentColor($element[$key]['#color']);
        }
        elseif ($element['#chart_type'] === 'area') {
          $dataset->fill = 'origin';
          $dataset->backgroundColor = $this
            ->getTranslucentColor($element[$key]['#color']);
        }
        else {
          $dataset->fill = FALSE;
        }
        $datasets[] = $dataset;
      }

      // Merge in axis raw options.
      if (!empty($element[$key]['#raw_options'])) {
        $datasets = NestedArray::mergeDeepArray([
          $datasets,
          $element[$key]['#raw_options'],
        ]);
      }
    }
    $chart_definition['data']['datasets'] = $datasets;
    return $chart_definition;
  }

  /**
   * Outputs a type that can be used by Chart.js.
   *
   * @param array $element
   *   The given element.
   *
   * @return string
   *   The generated type.
   */
  protected function populateChartType(array $element) {
    switch ($element['#chart_type']) {
      case 'bar':
      case 'column':
        $type = 'bar';
        break;
      case 'area':
      case 'spline':
        $type = 'line';
        break;
      case 'donut':
        $type = 'doughnut';
        break;
      case 'gauge':

        // Gauge is currently not supported by Chart.js.
        $type = 'donut';
        break;
      default:
        $type = $element['#chart_type'];
        break;
    }
    if (isset($element['#polar']) && $element['#polar'] == 1) {
      $type = 'radar';
    }
    return $type;
  }

  /**
   * Builds legend based on element properties.
   *
   * @param array $element
   *   The element.
   *
   * @return array
   *   The legend array.
   */
  protected function buildLegend(array $element) {
    $legend = [];

    // Configure the legend display.
    $legend['display'] = (bool) $element['#legend'];

    // Configure legend position.
    if (!empty($element['#legend_position'])) {
      $legend['position'] = $element['#legend_position'];
      if (!empty($element['#legend_font_weight'])) {
        $legend['labels']['font']['weight'] = $element['#legend_font_weight'];
      }
      if (!empty($element['#legend_font_style'])) {
        $legend['labels']['font']['style'] = $element['#legend_font_style'];
      }
      if (!empty($element['#legend_font_size'])) {
        $legend['labels']['font']['size'] = $element['#legend_font_size'];
      }
    }
    return $legend;
  }

  /**
   * Builds title based on element properties.
   *
   * @param array $element
   *   The element.
   *
   * @return array
   *   The title array.
   */
  protected function buildTitle(array $element) {
    $title = [];
    if (!empty($element['#title'])) {
      $title = [
        'display' => TRUE,
        'text' => $element['#title'],
      ];
      if (!empty($element['#title_position'])) {
        if (in_array($element['#title_position'], [
          'in',
          'out',
        ])) {
          $title['position'] = 'top';
        }
        else {
          $title['position'] = $element['#title_position'];
        }
      }
      if (!empty($element['#title_color'])) {
        $title['color'] = $element['#title_color'];
      }
      if (!empty($element['#title_font_weight'])) {
        $title['font']['weight'] = $element['#title_font_weight'];
      }
      if (!empty($element['#title_font_style'])) {
        $title['font']['style'] = $element['#title_font_style'];
      }
      if (!empty($element['#title_font_size'])) {
        $title['font']['size'] = $element['#title_font_size'];
      }
    }
    return $title;
  }

  /**
   * Get translucent color.
   *
   * @param string $color
   *   The color.
   *
   * @return string
   *   The color.
   */
  protected function getTranslucentColor($color) {
    if (!$color) {
      return '';
    }
    $rgb = Color::hexToRgb($color);
    return 'rgba(' . implode(",", $rgb) . ',' . 0.5 . ')';
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ChartBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration 1
ChartBase::getChartName public function Return the name of the chart. Overrides ChartInterface::getChartName
ChartBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
ChartBase::getDefaultColors public static function Gets the default hex colors.
ChartBase::getDefaultSettings public static function Gets defaults settings.
ChartBase::getOptionsFromElementProperties protected function Gets options properties.
ChartBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
ChartBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm
ChartInterface::DUAL_AXIS constant Used to define a dual axis.
ChartInterface::SINGLE_AXIS constant Used to define a single axis.
Chartjs::buildConfigurationForm public function Form constructor. Overrides ChartBase::buildConfigurationForm
Chartjs::buildLegend protected function Builds legend based on element properties.
Chartjs::buildTitle protected function Builds title based on element properties.
Chartjs::getTranslucentColor protected function Get translucent color.
Chartjs::populateCategories private function Populate categories.
Chartjs::populateChartType protected function Outputs a type that can be used by Chart.js.
Chartjs::populateDatasets private function Populate Dataset.
Chartjs::populateOptions private function Populate options.
Chartjs::preRender public function Pre render. Overrides ChartInterface::preRender
Chartjs::submitConfigurationForm public function Build configurations. Overrides ChartBase::submitConfigurationForm
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 2
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginBase::__construct public function Constructs a \Drupal\Component\Plugin\PluginBase object. 98
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.