PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.5
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.5
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 3.10.4 All 148 releases
visualizer / classes / Visualizer / Module / Utility.php

Utility.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.5, at classes/Visualizer/Module/Utility.php

472 lines 14.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // +----------------------------------------------------------------------+
3 // | Copyright 2013 Madpixels (email : visualizer@madpixels.net) |
4 // +----------------------------------------------------------------------+
5 // | This program is free software; you can redistribute it and/or modify |
6 // | it under the terms of the GNU General Public License, version 2, as |
7 // | published by the Free Software Foundation. |
8 // | |
9 // | This program is distributed in the hope that it will be useful, |
10 // | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 // | GNU General Public License for more details. |
13 // | |
14 // | You should have received a copy of the GNU General Public License |
15 // | along with this program; if not, write to the Free Software |
16 // | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, |
17 // | MA 02110-1301 USA |
18 // +----------------------------------------------------------------------+
19 // | Author: Eugene Manuilov <eugene@manuilov.org> |
20 // +----------------------------------------------------------------------+
21 /**
22 * Utilities module class.
23 *
24 * @category Visualizer
25 * @package Module
26 *
27 * @since 3.3.0
28 */
29 class Visualizer_Module_Utility extends Visualizer_Module {
30
31 const NAME = __CLASS__;
32
33 /**
34 * Some default distinct colors.
35 *
36 * @since 3.3.0
37 *
38 * @access private
39 * @var string[]
40 */
41 private static $_CHART_COLORS = array(
42 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080',
43 );
44
45
46 /**
47 * Constructor.
48 *
49 * @since 3.3.0
50 *
51 * @access public
52 *
53 * @param Visualizer_Plugin $plugin The instance of the plugin.
54 */
55 public function __construct( Visualizer_Plugin $plugin ) {
56 parent::__construct( $plugin );
57 $this->_addFilter( Visualizer_Plugin::FILTER_GET_CHART_SETTINGS, 'apply_global_style_settings', 999, 3 );
58 }
59
60
61 /**
62 * Convert hexdec color string to rgb(a) string.
63 *
64 * Props to https://mekshq.com/how-to-convert-hexadecimal-color-code-to-rgb-or-rgba-using-php/
65 *
66 * @since 3.3.0
67 *
68 * @access private
69 */
70 private static function hex2rgba( $color, $opacity = false ) {
71 $default = 'rgb(0,0,0)';
72
73 // Return default if no color provided
74 if ( empty( $color ) ) {
75 return $default;
76 }
77
78 // Sanitize $color if "#" is provided
79 if ( strpos( $color, '#' ) === 0 ) {
80 $color = substr( $color, 1 );
81 }
82
83 // Check if color has 6 or 3 characters and get values
84 if ( strlen( $color ) === 6 ) {
85 $hex = array( $color[0] . $color[1], $color[2] . $color[3], $color[4] . $color[5] );
86 } elseif ( strlen( $color ) === 3 ) {
87 $hex = array( $color[0] . $color[0], $color[1] . $color[1], $color[2] . $color[2] );
88 } else {
89 return $default;
90 }
91
92 // Convert hexadec to rgb
93 $rgb = array_map( 'hexdec', $hex );
94
95 // Check if opacity is set(rgba or rgb)
96 if ( $opacity ) {
97 if ( abs( $opacity ) > 1 ) {
98 $opacity = 1.0;
99 }
100 $output = 'rgba(' . implode( ',', $rgb ) . ',' . $opacity . ')';
101 } else {
102 $output = 'rgb(' . implode( ',', $rgb ) . ')';
103 }
104
105 // Return rgb(a) color string
106 return $output;
107 }
108
109 /**
110 * Returns a color at a specific index from the palette, with its transparent equivalent.
111 *
112 * @return array{string, string}
113 * @access private
114 */
115 private static function get_color_at( int $index ): array {
116 $colors = self::get_color_palette();
117 $color = $colors[ $index % count( $colors ) ];
118 return array( self::hex2rgba( $color, 0.5 ), $color );
119 }
120
121 /**
122 * Mixes a hex color toward white by the given factor (0 = original, 1 = white).
123 *
124 * @access private
125 */
126 private static function tint_color( string $hex, float $factor ): string {
127 $hex = ltrim( $hex, '#' );
128 if ( strlen( $hex ) === 3 ) {
129 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
130 }
131 $r = (int) round( hexdec( substr( $hex, 0, 2 ) ) + ( 255 - hexdec( substr( $hex, 0, 2 ) ) ) * $factor );
132 $g = (int) round( hexdec( substr( $hex, 2, 2 ) ) + ( 255 - hexdec( substr( $hex, 2, 2 ) ) ) * $factor );
133 $b = (int) round( hexdec( substr( $hex, 4, 2 ) ) + ( 255 - hexdec( substr( $hex, 4, 2 ) ) ) * $factor );
134 return sprintf( '#%02x%02x%02x', $r, $g, $b );
135 }
136
137 /**
138 * Returns the color palette to use for charts.
139 *
140 * When global primary/secondary colors are configured, generates a palette of
141 * alternating tints: [primary, secondary, primary@25%, secondary@25%, ...].
142 * Falls back to the built-in colors otherwise.
143 *
144 * @return string[]
145 * @access private
146 */
147 private static function get_color_palette(): array {
148 $global = self::get_global_style_defaults();
149 $primary = $global['color_primary'];
150 $secondary = $global['color_secondary'];
151
152 if ( empty( $primary ) && empty( $secondary ) ) {
153 return self::$_CHART_COLORS;
154 }
155
156 $bases = array_filter( array( $primary, $secondary ) );
157 $factors = array( 0, 0.25, 0.5, 0.75 );
158 $palette = array();
159
160 foreach ( $factors as $factor ) {
161 foreach ( $bases as $base ) {
162 $palette[] = $factor === 0 ? $base : self::tint_color( $base, $factor );
163 }
164 }
165
166 return $palette;
167 }
168
169 /**
170 * Returns the global style defaults stored in the plugin settings.
171 *
172 * @return array<string, string>
173 * @access public
174 * @static
175 */
176 public static function get_global_style_defaults(): array {
177 $option = get_option( Visualizer_Module_Admin::OPTION_GLOBAL_SETTINGS, array() );
178 return wp_parse_args(
179 $option,
180 array(
181 'color_primary' => '',
182 'color_secondary' => '',
183 'apply_existing' => '0',
184 )
185 );
186 }
187
188 /**
189 * Applies global styles to existing charts at render time.
190 *
191 * @param array<string, mixed>|mixed $settings Chart settings.
192 * @param int $chart_id Chart ID.
193 * @param string $type Chart type.
194 * @return array<string, mixed>
195 * @access public
196 */
197 public function apply_global_style_settings( $settings, $chart_id, $type ): array {
198 $settings = is_array( $settings ) ? $settings : array();
199 $global = self::get_global_style_defaults();
200
201 if ( empty( $global['apply_existing'] ) ) {
202 return $settings;
203 }
204
205 if ( empty( $global['color_primary'] ) && empty( $global['color_secondary'] ) ) {
206 return $settings;
207 }
208
209 if ( empty( $chart_id ) ) {
210 return $settings;
211 }
212
213 $library = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_LIBRARY, true );
214 $library = is_string( $library ) ? strtolower( $library ) : '';
215 if ( empty( $type ) ) {
216 $type = get_post_meta( $chart_id, Visualizer_Plugin::CF_CHART_TYPE, true );
217 }
218 $type = is_string( $type ) ? strtolower( $type ) : '';
219
220 if ( empty( $library ) ) {
221 $library = in_array( $type, array( 'datatable', 'tabular' ), true ) ? 'datatable' : 'googlecharts';
222 }
223
224 if ( 'googlecharts' === $library || 'google' === $library ) {
225 if ( 'geo' !== $type ) {
226 $has_explicit = false;
227 if ( ! empty( $settings['colors'] ) ) {
228 $has_explicit = true;
229 }
230 if ( ! $has_explicit && isset( $settings['series'] ) && is_array( $settings['series'] ) ) {
231 foreach ( $settings['series'] as $series_settings ) {
232 if ( ! empty( $series_settings['color'] ) ) {
233 $has_explicit = true;
234 break;
235 }
236 }
237 }
238 if ( ! $has_explicit && isset( $settings['slices'] ) && is_array( $settings['slices'] ) ) {
239 foreach ( $settings['slices'] as $slice_settings ) {
240 if ( ! empty( $slice_settings['color'] ) ) {
241 $has_explicit = true;
242 break;
243 }
244 }
245 }
246 if ( ! $has_explicit ) {
247 $settings['colors'] = self::get_color_palette();
248 }
249 }
250 return $settings;
251 }
252
253 if ( 'chartjs' === $library ) {
254 $has_explicit = false;
255 if ( isset( $settings['series'] ) && is_array( $settings['series'] ) ) {
256 foreach ( $settings['series'] as $series_settings ) {
257 if ( ! empty( $series_settings['backgroundColor'] ) || ! empty( $series_settings['borderColor'] ) || ! empty( $series_settings['hoverBackgroundColor'] ) ) {
258 $has_explicit = true;
259 break;
260 }
261 }
262 }
263 if ( ! $has_explicit && isset( $settings['slices'] ) && is_array( $settings['slices'] ) ) {
264 foreach ( $settings['slices'] as $slice_settings ) {
265 if ( ! empty( $slice_settings['backgroundColor'] ) || ! empty( $slice_settings['borderColor'] ) || ! empty( $slice_settings['hoverBackgroundColor'] ) ) {
266 $has_explicit = true;
267 break;
268 }
269 }
270 }
271 if ( $has_explicit ) {
272 return $settings;
273 }
274 $series = get_post_meta( $chart_id, Visualizer_Plugin::CF_SERIES, true );
275 if ( is_array( $series ) ) {
276 $settings = self::apply_chartjs_palette( $settings, $type, $series, $chart_id );
277 }
278 }
279
280 return $settings;
281 }
282
283 /**
284 * Applies the global palette to ChartJS settings for a chart.
285 *
286 * @param array<string, mixed> $settings Current chart settings.
287 * @param string $type Chart type.
288 * @param array<int, mixed> $series Series definitions.
289 * @param int $chart_id Chart ID.
290 * @return array<string, mixed>
291 * @access private
292 * @static
293 */
294 private static function apply_chartjs_palette( array $settings, string $type, array $series, int $chart_id ): array {
295 $attributes = array();
296 $name = 'series';
297 $count = count( $series );
298 $max = $count - 1;
299
300 switch ( $type ) {
301 case 'polarArea':
302 // fall through.
303 case 'pie':
304 $chart = get_post( $chart_id );
305 $data = $chart instanceof WP_Post ? maybe_unserialize( $chart->post_content ) : array();
306 $name = 'slices';
307 $max = is_array( $data ) ? count( $data ) : $count;
308 // fall through.
309 case 'column':
310 // fall through.
311 case 'bar':
312 for ( $i = 0; $i < $max; $i++ ) {
313 $colors = self::get_color_at( $i );
314 $attributes[] = array( 'backgroundColor' => $colors[0], 'hoverBackgroundColor' => $colors[1] );
315 }
316 break;
317 case 'radar':
318 // fall through.
319 case 'line':
320 // fall through.
321 case 'area':
322 for ( $i = 0; $i < $max; $i++ ) {
323 $colors = self::get_color_at( $i );
324 $attributes[] = array( 'borderColor' => $colors[0] );
325 }
326 break;
327 }
328
329 if ( $attributes ) {
330 $settings[ $name ] = $attributes;
331 }
332
333 return $settings;
334 }
335
336 /**
337 * Sets some defaults in the chart.
338 *
339 * @since 3.3.0
340 *
341 * @access public
342 */
343 public static function set_defaults( $chart, $post_status = 'auto-draft' ) {
344 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
345 $library = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_LIBRARY, true );
346
347 // if post_status is null, operate on the chart irrespective of the post_status
348 if ( ( ! is_null( $post_status ) && $chart->post_status !== $post_status ) ) {
349 return;
350 }
351
352 $series = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true );
353 if ( ! $series || ! is_array( $series ) ) {
354 return;
355 }
356
357 switch ( $library ) {
358 case 'ChartJS':
359 self::set_defaults_chartjs( $chart, $post_status );
360 break;
361 case 'GoogleCharts':
362 self::set_defaults_google( $chart, $post_status );
363 break;
364 }
365 }
366
367 /**
368 * Sets some defaults in the chart for Google charts.
369 *
370 * @since 3.3.0
371 *
372 * @access private
373 */
374 private static function set_defaults_google( $chart, $post_status ) {
375 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
376 $series = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true );
377
378 $attributes = array();
379 if ( $post_status === 'auto-draft' ) {
380 switch ( $type ) {
381 case 'combo':
382 // chart type 'bars' and randomly choose the type of each series.
383 $types = array( 'area', 'line', 'steppedArea', 'bar' );
384 $attributes['seriesType'] = 'bars';
385 for ( $x = 0; $x < count( $series ); $x++ ) {
386 $attributes['series'][ $x ]['type'] = $types[ rand( 0, count( $types ) - 1 ) ];
387 }
388 break;
389 case 'candlestick':
390 // add stroke and fill color so that behavior is consistent.
391 $attributes['candlestick']['fallingColor']['stroke'] = '#3366cc';
392 $attributes['candlestick']['fallingColor']['fill'] = '#fff';
393 $attributes['candlestick']['risingColor']['stroke'] = '#3366cc';
394 $attributes['candlestick']['risingColor']['fill'] = '#3366cc';
395 break;
396 }
397
398 // Apply global color defaults to new Google Charts (not Geo — it uses colorAxis).
399 if ( 'geo' !== $type ) {
400 $global = self::get_global_style_defaults();
401 if ( ! empty( $global['color_primary'] ) || ! empty( $global['color_secondary'] ) ) {
402 $attributes['colors'] = self::get_color_palette();
403 }
404 }
405 }
406
407 if ( $attributes ) {
408 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
409 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, array_merge( $settings, $attributes ) );
410 }
411 }
412
413 /**
414 * Sets some defaults in the chart for ChartJS charts. Only during first creation.
415 *
416 * @since 3.3.0
417 *
418 * @access private
419 */
420 private static function set_defaults_chartjs( $chart, $post_status ) {
421 if ( $post_status !== 'auto-draft' ) {
422 return;
423 }
424
425 $type = get_post_meta( $chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
426 $series = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SERIES, true );
427 $settings = get_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, true );
428
429 $attributes = array();
430 $name = 'series';
431 $count = count( $series );
432 $max = $count - 1;
433 switch ( $type ) {
434 case 'polarArea':
435 // fall through.
436 case 'pie':
437 $data = unserialize( $chart->post_content );
438 $name = 'slices';
439 $max = count( $data );
440 // fall through.
441 case 'column':
442 // fall through.
443 case 'bar':
444 for ( $i = 0; $i < $max; $i++ ) {
445 $colors = self::get_color_at( $i );
446 $attributes[] = array( 'backgroundColor' => $colors[0], 'hoverBackgroundColor' => $colors[1] );
447 }
448 break;
449 case 'radar':
450 // fall through.
451 case 'line':
452 // fall through.
453 case 'area':
454 for ( $i = 0; $i < $max; $i++ ) {
455 $colors = self::get_color_at( $i );
456 $attributes[] = array( 'borderColor' => $colors[0] );
457 }
458 break;
459 }
460
461 if ( $post_status === 'auto-draft' ) {
462 // the charts are huge in size so let's always get them down to 50%.
463 $settings['width'] = $settings['height'] = '50%';
464 }
465
466 if ( $attributes ) {
467 $settings[ $name ] = $attributes;
468 update_post_meta( $chart->ID, Visualizer_Plugin::CF_SETTINGS, $settings );
469 }
470 }
471 }
472