datafield-1.x-dev/src/Plugin/Field/FieldFormatter/Chart.php
src/Plugin/Field/FieldFormatter/Chart.php
<?php
namespace Drupal\datafield\Plugin\Field\FieldFormatter;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\datafield\Plugin\DataFieldFormatterPluginManager;
use Drupal\datafield\Plugin\DataFieldTypePluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementations for 'chart' formatter.
*/
#[FieldFormatter(
id: 'data_field_chart',
label: new TranslatableMarkup('Charts'),
field_types: [
'data_field',
],
)]
class Chart extends Base {
/**
* Constructs a FormatterDetails object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\datafield\Plugin\DataFieldTypePluginManager $pluginType
* Plugin data type service.
* @param \Drupal\datafield\Plugin\DataFieldFormatterPluginManager $pluginFormatter
* Plugin data formatter service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, DataFieldTypePluginManager $pluginType, DataFieldFormatterPluginManager $pluginFormatter, protected RendererInterface $renderer) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $pluginType, $pluginFormatter);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id, $plugin_definition, $configuration['field_definition'],
$configuration['settings'], $configuration['label'],
$configuration['view_mode'], $configuration['third_party_settings'],
$container->get('plugin.manager.data_field.type'),
$container->get('plugin.manager.data_field.formatter'),
$container->get('renderer'),
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'mode' => 'googleCharts',
'chart_type' => 'LineChart',
'chart_width' => 900,
'chart_height' => 300,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$element = parent::settingsForm($form, $form_state);
$element['mode'] = [
'#type' => 'select',
'#options' => [
'googleCharts' => $this->t('Google chart'),
'apexCharts' => $this->t('Apex chart'),
],
'#empty_option' => $this->t('- Select -'),
'#default_value' => $this->getSetting('mode'),
];
$example = [
'googleCharts' => '<a href="https://developers-dot-devsite-v2-prod.appspot.com/chart/interactive/docs/gallery" target="_blank">' . $this->t('Google charts') . '</a>',
'apexCharts' => '<a href="https://apexcharts.com/javascript-chart-demos/" target="_blank">' . $this->t('Apex charts') . '</a>',
];
$element['chart_type'] = [
'#title' => $this->t('Chart type'),
'#description' => $example[$this->getSetting('mode')],
'#type' => 'select',
'#default_value' => $this->getSetting('chart_type'),
'#options' => $this->chartsOptions($this->getSetting('mode')),
];
$element['chart_width'] = [
'#title' => $this->t('Chart width'),
'#type' => 'number',
'#default_value' => $this->getSetting('chart_width'),
'#states' => [
'visible' => [
'select[name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][mode]"]' => ['value' => 'googleCharts'],
],
],
];
$element['chart_height'] = [
'#title' => $this->t('Chart height'),
'#type' => 'number',
'#default_value' => $this->getSetting('chart_height'),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary(): array {
$summary = parent::settingsSummary();
$summary['mode'] = "Mode: " . $this->getSetting('mode');
$summary['chart_type'] = "Type: " . $this->getSetting('chart_type');
return $summary;
}
/**
* {@inheritDoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$field_name = $items->getName();
$setting = $this->getSettings();
$field_definition = $items->getFieldDefinition();
$setting['storage_settings'] = $field_definition->getFieldStorageDefinition()->getSetting('columns');
$setting['field_settings'] = $field_settings = $field_definition->getSetting('field_settings');
$setting['title'] = $field_definition->getLabel();
$subfields = array_keys($setting['storage_settings']);
if (!empty($settings = $setting['formatter_settings'])) {
uasort($settings, [
'Drupal\Component\Utility\SortArray',
'sortByWeightElement',
]);
$subfields = array_keys($settings);
}
$firstSubField = current($subfields);
$setting['storage_type'] = $setting['storage_settings'][$firstSubField]['type'] ?? '';
$entity = $items->getEntity();
$entityId = $entity?->id();
$chartId = $field_name . '-' . $entityId;
$drupalSettings = [
$this->getSetting('mode') => ['#' . $chartId => $setting],
];
$options = $this->chartsOptions($setting['mode'], $setting['chart_type']);
if ($setting["storage_settings"][$firstSubField]["type"] == 'date' && $setting["storage_settings"][$firstSubField]["datetime_type"] != 'year') {
$options['xaxis']['type'] = 'datetime';
}
$options['url'] = FALSE;
if (!empty($setting['caption'])) {
$options['title'] = $setting['caption'];
}
if (is_numeric($setting['chart_width'])) {
$setting['chart_width'] .= 'px';
}
if (is_numeric($setting['chart_height'])) {
$setting['chart_height'] .= 'px';
}
if (empty($setting['chart_width'])) {
$setting['chart_width'] = '100%';
}
$elements = [
'#theme' => 'data_field_chart',
'#settings' => $setting,
'#id_field_name' => $chartId,
'#langcode' => $langcode,
'#attributes' => [
'data-json-field' => $field_name,
'class' => [$this->getSetting('mode')],
],
];
if (!empty($this->getSetting('custom_class'))) {
$elements['#attributes']['class'][] = $this->getSetting('custom_class');
}
if (!empty($this->getSetting('mode'))) {
$elements['#attached'] = [
'library' => ['datafield/' . $this->getSetting('mode')],
'drupalSettings' => $drupalSettings,
];
}
$data = [];
$header = [];
foreach ($items as $delta => $item) {
foreach ($subfields as $subfield) {
if (!$delta) {
$label = $field_settings[$subfield]["label"] ?? $setting['storage_settings'][$subfield]['name'];
// phpcs:ignore
$header[] = new TranslatableMarkup($label);
}
$value = $item->{$subfield};
if (!is_array($value) && !is_object($value)) {
$value = trim(strip_tags($value ?? ''));
}
else {
$value = trim(strip_tags($this->renderer->render($value)));
}
if (is_numeric($value)) {
$data[$delta][] = (float) $value;
}
else {
$data[$delta][] = $value;
}
}
}
$elements['#attached']['drupalSettings'][$this->getSetting('mode')]['#' . $chartId] += [
'id' => $field_name,
'type' => $this->getSetting('chart_type') ?: 'BarChart',
'options' => $options,
'data' => [...[$header], ...$data],
];
if (!empty($this->getSetting('form_format_table'))) {
$hasPermission = $this->checkPermissionOperation($entity, $field_name);
if ($entityId && $hasPermission) {
$dialog_width = '80%';
$elements[] = [
'#type' => 'container',
'add-button' => [
'#type' => 'link',
'#title' => Markup::create('<i class="bi bi-plus" aria-hidden="true"></i> ' . $this->t('Add')),
'#url' => Url::fromRoute('datafield.add_form', $params = [
'entity_type' => $field_definition->getTargetEntityTypeId(),
'field_name' => $field_name,
'entity' => $entityId,
]),
'#attributes' => [
'class' => [
'btn',
'btn-success',
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode(['width' => $dialog_width]),
],
],
'edit-button' => [
'#type' => 'link',
'#title' => Markup::create('<i class="bi bi-pencil-square"></i> ' . $this->t('Edit')),
'#url' => Url::fromRoute('datafield.edit_all_form', $params = [
'entity_type' => $field_definition->getTargetEntityTypeId(),
'field_name' => $field_name,
'entity' => $entityId,
]),
'#attributes' => [
'class' => [
'btn',
'btn-info',
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode(['width' => $dialog_width]),
],
],
];
}
}
return $elements;
}
/**
* {@inheritDoc}
*/
private function chartsOptions($mode, $type = FALSE) {
switch ($mode) {
case 'apexCharts':
return $this->apexChartsOption($type);
default:
return $this->googleChartsOption($type);
}
}
/**
* {@inheritDoc}
*/
private function googleChartsOption($option = FALSE) {
$options = [
'BarChart' => [
'title' => $this->t('Bar'),
'option' => [
'bar' => ['groupWidth' => "95%"],
'legend' => ['position' => "none"],
],
],
'BubbleChart' => [
'title' => $this->t('Bubble'),
'option' => [
'bubble' => ['textStyle' => ['fontSize' => 11]],
],
],
'LineChart' => [
'title' => $this->t('Line'),
'option' => [
'legend' => ['position' => "bottom"],
'curveType' => 'function',
],
],
'ColumnChart' => [
'title' => $this->t('Column'),
'option' => [
'bar' => ['groupWidth' => "95%"],
'legend' => ['position' => "none"],
],
],
'ComboChart' => [
'title' => $this->t('Combo'),
'option' => [
'seriesType' => 'bars',
],
],
'PieChart' => [
'title' => $this->t('Pie'),
'option' => [
'is3D' => TRUE,
],
],
'ScatterChart' => [
'title' => $this->t('Scatter'),
'option' => [
'legend' => ['position' => "none"],
],
],
'SteppedAreaChart' => [
'title' => $this->t('Stepped Area'),
'option' => [
'isStacked' => TRUE,
],
],
'AreaChart' => [
'title' => $this->t('Area'),
'option' => [
'legend' => ['position' => "top", 'maxLines' => 3],
'isStacked' => 'relative',
],
],
'Histogram' => [
'title' => $this->t('Histogram'),
'option' => [
'legend' => ['position' => "top", 'maxLines' => 3],
'interpolateNulls' => FALSE,
],
],
'CandlestickChart' => [
'title' => $this->t('Candlestick'),
'option' => [
'notHeader' => TRUE,
'legend' => 'none',
'bar' => ['groupWidth' => '100%'],
],
],
];
$zoomOption = [
'explorer' => [
'actions' => ['dragToZoom', 'rightClickToReset'],
'axis' => 'horizontal',
'keepInBounds' => TRUE,
'maxZoomIn' => 4.0,
],
];
if ($option && !empty($options[$option]['option'])) {
$options[$option]['option'] += $zoomOption;
return $options[$option]['option'];
}
$titleOptions = [];
foreach ($options as $type => $option) {
$titleOptions[$type] = $option['title'];
}
return $titleOptions;
}
/**
* {@inheritDoc}
*/
private function apexChartsOption($option = FALSE) {
$options = [
'line' => [
'title' => $this->t('Line'),
'chart' => [
'type' => 'line',
'zoom' => [
'type' => 'x',
'enabled' => TRUE,
'autoScaleYaxis' => TRUE,
],
'stacked' => FALSE,
],
'dataLabels' => ['enabled' => FALSE],
'markers' => ['size' => 0],
'tooltip' => ['shared' => FALSE],
],
'area' => [
'title' => $this->t('Area'),
'chart' => [
'type' => 'area',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'dataLabels' => ['enabled' => FALSE],
'stroke' => ['curve' => 'straight'],
],
'bar' => [
'title' => $this->t('Bar'),
'chart' => [
'type' => 'bar',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'dataLabels' => ['enabled' => FALSE],
'plotOptions' => [
'bar' => [
'horizontal' => TRUE,
'endingShape' => 'rounded',
],
],
'stroke' => [
'show' => TRUE,
'width' => 2,
'colors' => ['transparent'],
],
'fill' => ['opacity' => 1],
],
'rangeBar' => [
'title' => $this->t('Range Bar'),
'chart' => [
'type' => 'bar',
'stacked' => TRUE,
'stackType' => '100%',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'plotOptions' => [
'bar' => ['horizontal' => TRUE],
],
],
'column' => [
'title' => $this->t('Column'),
'chart' => [
'type' => 'bar',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'dataLabels' => ['enabled' => FALSE],
'plotOptions' => [
'bar' => [
'horizontal' => FALSE,
'columnWidth' => '55%',
'endingShape' => 'rounded',
],
],
],
'funnel' => [
'title' => $this->t('Funnel'),
'chart' => [
'type' => 'bar',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'plotOptions' => [
'bar' => [
'borderRadius' => 0,
'horizontal' => TRUE,
'barHeight' => '80%',
'isFunnel' => TRUE,
],
],
'dataLabels' => [
'enabled' => TRUE,
'dropShadow' => ['enabled' => TRUE],
],
],
'pyramid' => [
'title' => $this->t('Pyramid'),
'chart' => [
'type' => 'bar',
'toolbar' => ['show' => TRUE],
'zoom' => ['enabled' => TRUE],
],
'plotOptions' => [
'bar' => [
'borderRadius' => 0,
'horizontal' => TRUE,
'distributed' => TRUE,
'barHeight' => '80%',
'isFunnel' => TRUE,
],
],
'dataLabels' => [
'enabled' => TRUE,
'dropShadow' => ['enabled' => TRUE],
],
],
'pie' => [
'title' => $this->t('Pie'),
'chart' => [
'type' => 'pie',
],
],
'donut' => [
'title' => $this->t('Donut'),
'chart' => [
'type' => 'donut',
],
],
'polarArea' => [
'title' => $this->t('Polar Area'),
'chart' => [
'type' => 'polarArea',
],
],
'radialBar' => [
'title' => $this->t('Radial Bar'),
'chart' => [
'type' => 'radialBar',
],
'plotOptions' => [
'radialBar' => [
'offsetY' => 0,
'startAngle' => 0,
'endAngle' => 270,
'hollow' => [
'margin' => 5,
'size' => '30%',
'background' => 'transparent',
'image' => 'undefined',
],
'dataLabels' => [
'total' => [
'show' => TRUE,
'label' => $this->t('Total'),
],
],
],
],
'legend' => [
'show' => TRUE,
'floating' => TRUE,
'position' => 'left',
'labels' => ['useSeriesColors' => TRUE],
'markers' => ['size' => 0],
],
],
'radar' => [
'title' => $this->t('Radar'),
'chart' => [
'type' => 'radar',
'dropShadow' => [
'enabled' => TRUE,
'blur' => 1,
'left' => 1,
'top' => 1,
],
],
'stroke' => ['width' => 2],
'fill' => ['opacity' => 0.1],
'markers' => ['size' => 0],
],
];
if ($option) {
return $options[$option];
}
$titleOptions = [];
foreach ($options as $type => $option) {
$titleOptions[$type] = $option['title'];
}
return $titleOptions;
}
}
