Skip to content

Commit

Permalink
Interpolate colors in ColorRating table column decorator on runtime (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek authored Jan 22, 2023
1 parent af72a2b commit e369e5d
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 127 deletions.
1 change: 0 additions & 1 deletion demos/collection/tablecolumns.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ protected function init(): void
[
'min' => 1,
'max' => 3,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand Down
1 change: 0 additions & 1 deletion src/Card.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Atk4\Core\Factory;
use Atk4\Data\Model;
use Atk4\Ui\Js\Jquery;
use Atk4\Ui\UserAction\ExecutorFactory;

/**
Expand Down
1 change: 0 additions & 1 deletion src/HtmlTemplate/TagTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ private function __clone()
public function clone(HtmlTemplate $newParentTemplate): self
{
$res = new static($newParentTemplate, $this->tag);
$res->children = [];
foreach ($this->children as $k => $v) {
$res->children[$k] = is_string($v) ? $v : clone $v;
}
Expand Down
140 changes: 48 additions & 92 deletions src/Table/Column/ColorRating.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* [ColorRating::class, [
* 'min' => 1,
* 'max' => 3,
* 'steps' => 3,
* 'colors' => [
* '#FF0000',
* '#FFFF00',
Expand All @@ -26,18 +25,12 @@ class ColorRating extends Table\Column
{
/** @var float Minimum value of the gradient. */
public $min;

/** @var float Maximum value of the gradient. */
public $max;
/** @var int Step to be calculated between colors, must be greater than 1. */
public $steps = 1;

/** @var array Hex colors ['#FF0000', '#00FF00'] from red to green. */
/** @var array Hex colors. */
public $colors = ['#FF0000', '#00FF00'];

/** @var array Store the generated Hex color based on the number of steps. */
protected $gradients = [];

/** @var bool Define if values lesser than min have no color. */
public $lessThanMinNoColor = false;

Expand All @@ -52,78 +45,8 @@ protected function init(): void
throw new Exception('Min must be lower than Max');
}

if ($this->steps === 0) {
throw new Exception('Step must be at least 1');
}

if (count($this->colors) < 2) {
throw new Exception('Colors must be more than 1');
}

$this->createGradients();
}

private function createGradients(): void
{
$colorFrom = '';

foreach ($this->colors as $i => $color) {
// skip first
if ($i === 0) {
$colorFrom = $color;

continue;
}

// if already add remove last
// because on first iteraction of ->createGradientSingle
// will create a duplicate
if (count($this->gradients) > 0) {
array_pop($this->gradients);
}

$this->createGradientSingle($this->gradients, $colorFrom, $color, $this->steps + 1);
$colorFrom = $color;
}
}

private function createGradientSingle(array &$gradients, string $hexFrom, string $hexTo, int $steps): void
{
$hexFrom = ltrim($hexFrom, '#');
$hexTo = ltrim($hexTo, '#');

$fromRgb = [
'r' => hexdec(substr($hexFrom, 0, 2)),
'g' => hexdec(substr($hexFrom, 2, 2)),
'b' => hexdec(substr($hexFrom, 4, 2)),
];

$toRgb = [
'r' => hexdec(substr($hexTo, 0, 2)),
'g' => hexdec(substr($hexTo, 2, 2)),
'b' => hexdec(substr($hexTo, 4, 2)),
];

$stepRgb = [
'r' => ($fromRgb['r'] - $toRgb['r']) / $steps,
'g' => ($fromRgb['g'] - $toRgb['g']) / $steps,
'b' => ($fromRgb['b'] - $toRgb['b']) / $steps,
];

for ($i = 0; $i <= $steps; ++$i) {
$rgb = [
'r' => floor($fromRgb['r'] - $stepRgb['r'] * $i),
'g' => floor($fromRgb['g'] - $stepRgb['g'] * $i),
'b' => floor($fromRgb['b'] - $stepRgb['b'] * $i),
];

$hexRgb = [
'r' => sprintf('%02x', $rgb['r']),
'g' => sprintf('%02x', $rgb['g']),
'b' => sprintf('%02x', $rgb['b']),
];

$gradients[] = '#' . implode('', $hexRgb);
throw new Exception('At least 2 colors must be set');
}
}

Expand All @@ -147,11 +70,8 @@ public function getDataCellHtml(Field $field = null, array $attr = []): string
public function getHtmlTags(Model $row, ?Field $field): array
{
$value = $field->get($row);
if ($value === null) {
return parent::getHtmlTags($row, $field);
}

$color = $this->getColorFromValue($value);
$color = $value === null ? null : $this->getColorFromValue($value);

if ($color === null) {
return parent::getHtmlTags($row, $field);
Expand All @@ -164,20 +84,56 @@ public function getHtmlTags(Model $row, ?Field $field): array

private function getColorFromValue(float $value): ?string
{
if ($value <= $this->min) {
return $this->lessThanMinNoColor ? null : $this->gradients[0];
if ($value < $this->min) {
if ($this->lessThanMinNoColor) {
return null;
}

$value = $this->min;
}

if ($value >= $this->max) {
return $this->moreThanMaxNoColor ? null : end($this->gradients);
if ($value > $this->max) {
if ($this->moreThanMaxNoColor) {
return null;
}

$value = $this->max;
}

$gradientsCount = count($this->gradients) - 1;
$refValue = ($value - $this->min) / ($this->max - $this->min);
$refIndex = $gradientsCount * $refValue;
$colorIndex = (count($this->colors) - 1) * ($value - $this->min) / ($this->max - $this->min);

$color = $this->interpolateColor(
$this->colors[floor($colorIndex)],
$this->colors[ceil($colorIndex)],
$colorIndex - floor($colorIndex)
);

return $color;
}

protected function interpolateColor(string $hexFrom, string $hexTo, float $value): string
{
$hexFrom = ltrim($hexFrom, '#');
$hexTo = ltrim($hexTo, '#');

$fromRgb = [
'r' => hexdec(substr($hexFrom, 0, 2)),
'g' => hexdec(substr($hexFrom, 2, 2)),
'b' => hexdec(substr($hexFrom, 4, 2)),
];

$toRgb = [
'r' => hexdec(substr($hexTo, 0, 2)),
'g' => hexdec(substr($hexTo, 2, 2)),
'b' => hexdec(substr($hexTo, 4, 2)),
];

$index = (int) floor($refIndex);
$rgb = [
'r' => round($fromRgb['r'] + $value * ($toRgb['r'] - $fromRgb['r'])),
'g' => round($fromRgb['g'] + $value * ($toRgb['g'] - $fromRgb['g'])),
'b' => round($fromRgb['b'] + $value * ($toRgb['b'] - $fromRgb['b'])),
];

return $this->gradients[$index];
return '#' . sprintf('%02x%02x%02x', $rgb['r'], $rgb['g'], $rgb['b']);
}
}
10 changes: 3 additions & 7 deletions src/Table/Column/Labels.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,16 @@ public function getHtmlTags(Model $row, ?Field $field): array
$v = $field->get($row);
$v = explode(',', $v ?? '');

$labels = [];
$labelsHtml = [];
foreach ($v as $id) {
$id = trim($id);

// if field values is set, then use titles instead of IDs
$id = $values[$id] ?? $id;

if ($id !== '') {
$labels[] = $this->getApp()->getTag('div', ['class' => 'ui label'], $id);
$labelsHtml[] = $this->getApp()->getTag('div', ['class' => 'ui label'], $id);
}
}

$labels = implode('', $labels);

return [$field->shortName => $labels];
return [$field->shortName => implode('', $labelsHtml)];
}
}
28 changes: 3 additions & 25 deletions tests/TableColumnColorRatingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public function testValueGreaterThanMax(): void
$rating = $this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 0,
'max' => 2,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -67,7 +66,6 @@ public function testValueGreaterThanMaxNoColor(): void
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 0,
'max' => 2,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -87,7 +85,6 @@ public function testValueLowerThanMin(): void
$rating = $this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 4,
'max' => 10,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -111,7 +108,6 @@ public function testValueLowerThanMinNoColor(): void
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 4,
'max' => 10,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -126,13 +122,12 @@ public function testValueLowerThanMinNoColor(): void
);
}

public function testExceptionMinGreaterThanMax(): void
public function testMinGreaterThanMaxException(): void
{
$this->expectException(Exception::class);
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 3,
'max' => 1,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -141,13 +136,12 @@ public function testExceptionMinGreaterThanMax(): void
]]);
}

public function testExceptionMinEqualsMax(): void
public function testMinEqualsMaxException(): void
{
$this->expectException(Exception::class);
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 3,
'max' => 3,
'steps' => 3,
'colors' => [
'#FF0000',
'#FFFF00',
Expand All @@ -156,28 +150,12 @@ public function testExceptionMinEqualsMax(): void
]]);
}

public function testExceptionZeroSteps(): void
public function testLessThan2ColorsException(): void
{
$this->expectException(Exception::class);
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 1,
'max' => 3,
'steps' => 0,
'colors' => [
'#FF0000',
'#FFFF00',
'#00FF00',
],
]]);
}

public function testExceptionLessThan2ColorsDefined(): void
{
$this->expectException(Exception::class);
$this->table->addDecorator('rating', [Table\Column\ColorRating::class, [
'min' => 1,
'max' => 3,
'steps' => 3,
'colors' => [
'#FF0000',
],
Expand Down

0 comments on commit e369e5d

Please sign in to comment.