Initial commit
This commit is contained in:
276
includes/class-rr-charts.php
Normal file
276
includes/class-rr-charts.php
Normal file
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class RR_Charts {
|
||||
|
||||
private $data;
|
||||
private static $counter = 0;
|
||||
|
||||
// RR Farbpalette
|
||||
private $colors = [
|
||||
'primary' => '#2F4858',
|
||||
'accent' => '#C5422A',
|
||||
'hover' => '#B33822',
|
||||
'bg' => '#F6F9FC',
|
||||
'geoeffnet' => '#2F7A4D',
|
||||
'geschlossen'=> '#C5422A',
|
||||
'neuer_betreiber' => '#D4922A',
|
||||
];
|
||||
|
||||
// Abstufungen der Primärfarbe
|
||||
private $primary_shades = [
|
||||
'#2F4858', '#3D5A6E', '#4B6D84', '#5A7F9A',
|
||||
'#6892B0', '#77A4C6', '#86B7DC', '#9ECAEB',
|
||||
'#B5D7F0', '#CCE4F5', '#E3F1FA', '#F0F7FC',
|
||||
'#2A4050', '#3E5C72', '#527894', '#6694B6',
|
||||
'#7AB0D8',
|
||||
];
|
||||
|
||||
public function __construct( RR_Data $data ) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
private function get_id() {
|
||||
return 'rr-chart-' . ( ++self::$counter ) . '-' . wp_rand( 1000, 9999 );
|
||||
}
|
||||
|
||||
private function enqueue() {
|
||||
wp_enqueue_script( 'chartjs' );
|
||||
wp_enqueue_script( 'chartjs-datalabels' );
|
||||
wp_enqueue_style( 'rr-charts' );
|
||||
}
|
||||
|
||||
private function wrap( $canvas_id, $js, $height = '400px', $mobile_height = null ) {
|
||||
if ( null === $mobile_height ) {
|
||||
$mobile_height = $height;
|
||||
}
|
||||
$style = sprintf(
|
||||
'width:100%%;max-width:100%%;height:%s;position:relative;',
|
||||
esc_attr( $height )
|
||||
);
|
||||
|
||||
$fn = str_replace( '-', '_', $canvas_id );
|
||||
|
||||
return sprintf(
|
||||
'<div class="rr-chart-wrap" id="wrap_%s" style="%s"><canvas id="%s"></canvas></div>
|
||||
<script>
|
||||
(function(){
|
||||
var attempts=0;
|
||||
var mob=window.innerWidth<768;
|
||||
if(mob){var w=document.getElementById("wrap_%s");if(w)w.style.height="%s";}
|
||||
function %s(){
|
||||
var ctx=document.getElementById("%s");
|
||||
if(!ctx){if(attempts++<50){setTimeout(%s,300);}return;}
|
||||
if(typeof Chart==="undefined"||typeof ChartDataLabels==="undefined"){if(attempts++<50){setTimeout(%s,300);}return;}
|
||||
if(ctx.getAttribute("data-rr-init")){return;}
|
||||
ctx.setAttribute("data-rr-init","1");
|
||||
Chart.register(ChartDataLabels);
|
||||
var _m=window.innerWidth<768;
|
||||
%s
|
||||
}
|
||||
if(document.readyState==="complete"){%s();}
|
||||
else{window.addEventListener("load",%s);}
|
||||
setTimeout(%s,1000);
|
||||
})();
|
||||
</script>',
|
||||
esc_attr( $canvas_id ),
|
||||
$style,
|
||||
esc_attr( $canvas_id ),
|
||||
esc_attr( $canvas_id ),
|
||||
esc_attr( $mobile_height ),
|
||||
$fn,
|
||||
esc_attr( $canvas_id ),
|
||||
$fn,
|
||||
$fn,
|
||||
$js,
|
||||
$fn,
|
||||
$fn,
|
||||
$fn
|
||||
);
|
||||
}
|
||||
|
||||
private function bar_datalabels() {
|
||||
return "datalabels:{color:'#fff',font:{weight:'bold',size:_m?10:12},display:function(c){return c.dataset.data[c.dataIndex]>0;},anchor:'center',align:'center'}";
|
||||
}
|
||||
|
||||
private function hbar_datalabels() {
|
||||
return "datalabels:{color:function(c){var v=c.dataset.data[c.dataIndex];var max=Math.max.apply(null,c.dataset.data);return v<(max*0.15)?'#333':'#fff';},font:{weight:'bold',size:_m?9:11},display:function(c){return c.dataset.data[c.dataIndex]>0;},anchor:function(c){var v=c.dataset.data[c.dataIndex];var max=Math.max.apply(null,c.dataset.data);return v<(max*0.15)?'end':'center';},align:function(c){var v=c.dataset.data[c.dataIndex];var max=Math.max.apply(null,c.dataset.data);return v<(max*0.15)?'right':'center';}}";
|
||||
}
|
||||
|
||||
private function line_datalabels() {
|
||||
return "datalabels:{color:'#333',font:{weight:'bold',size:_m?9:11},anchor:'end',align:'top',offset:_m?2:4,display:function(c){return c.dataset.data[c.dataIndex]>0;}}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Geöffnet vs. Geschlossen - Donut (400px hoch)
|
||||
*/
|
||||
public function render_status_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$counts = $this->data->get_status_counts();
|
||||
$id = $this->get_id();
|
||||
|
||||
$labels = wp_json_encode( array_map( 'ucfirst', array_keys( $counts ) ) );
|
||||
$values = wp_json_encode( array_values( $counts ) );
|
||||
$bg = wp_json_encode( [
|
||||
$this->colors['geoeffnet'],
|
||||
$this->colors['geschlossen'],
|
||||
$this->colors['neuer_betreiber'],
|
||||
] );
|
||||
|
||||
$js = "new Chart(ctx,{type:'doughnut',data:{labels:{$labels},datasets:[{data:{$values},backgroundColor:{$bg},borderWidth:2,borderColor:'#fff'}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:_m?11:14},padding:_m?10:16}},datalabels:{color:'#fff',font:{weight:'bold',size:_m?11:14},formatter:function(v,c){var t=c.dataset.data.reduce(function(a,b){return a+b},0);return v+' ('+Math.round(v/t*100)+'%)';},display:function(c){return c.dataset.data[c.dataIndex]>0;}},tooltip:{callbacks:{label:function(c){var t=c.dataset.data.reduce(function(a,b){return a+b},0);var p=Math.round(c.raw/t*100);return c.label+': '+c.raw+' ('+p+'%)';}}}}}}); ";
|
||||
|
||||
return $this->wrap( $id, $js, '400px', '350px' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. Öffnungsrate pro Staffel - Stacked Bar (500px hoch)
|
||||
*/
|
||||
public function render_staffel_status_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$staffel_data = $this->data->get_staffel_status();
|
||||
$id = $this->get_id();
|
||||
|
||||
$labels = wp_json_encode( array_map( function( $s ) {
|
||||
return 'Staffel ' . $s;
|
||||
}, array_keys( $staffel_data ) ) );
|
||||
|
||||
$ds_open = wp_json_encode( array_column( array_values( $staffel_data ), 'geöffnet' ) );
|
||||
$ds_closed = wp_json_encode( array_column( array_values( $staffel_data ), 'geschlossen' ) );
|
||||
$ds_new = wp_json_encode( array_column( array_values( $staffel_data ), 'neuer Betreiber' ) );
|
||||
|
||||
$c_open = $this->colors['geoeffnet'];
|
||||
$c_closed = $this->colors['geschlossen'];
|
||||
$c_new = $this->colors['neuer_betreiber'];
|
||||
$dl = $this->bar_datalabels();
|
||||
|
||||
$js = "new Chart(ctx,{type:'bar',data:{labels:{$labels},datasets:[{label:'Geöffnet',data:{$ds_open},backgroundColor:'{$c_open}'},{label:'Geschlossen',data:{$ds_closed},backgroundColor:'{$c_closed}'},{label:'Neuer Betreiber',data:{$ds_new},backgroundColor:'{$c_new}'}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:_m?10:14},padding:_m?8:16}},{$dl}},scales:{x:{stacked:true,ticks:{font:{size:_m?9:12},maxRotation:_m?45:0}},y:{stacked:true,beginAtZero:true,ticks:{stepSize:1,font:{size:_m?10:12}}}}}});";
|
||||
|
||||
return $this->wrap( $id, $js, '500px', '450px' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. Verbesserung beim Testessen - Grouped Bar (500px hoch)
|
||||
*/
|
||||
public function render_testessen_verbesserung_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$items = $this->data->get_testessen_verbesserung();
|
||||
$id = $this->get_id();
|
||||
|
||||
$staffeln = [];
|
||||
foreach ( $items as $item ) {
|
||||
$s = $item['staffel'];
|
||||
if ( ! isset( $staffeln[ $s ] ) ) {
|
||||
$staffeln[ $s ] = [ 'sum1' => 0, 'sum2' => 0, 'c1' => 0, 'c2' => 0 ];
|
||||
}
|
||||
if ( $item['test1'] > 0 ) {
|
||||
$staffeln[ $s ]['sum1'] += $item['test1'];
|
||||
$staffeln[ $s ]['c1']++;
|
||||
}
|
||||
if ( $item['test2'] > 0 ) {
|
||||
$staffeln[ $s ]['sum2'] += $item['test2'];
|
||||
$staffeln[ $s ]['c2']++;
|
||||
}
|
||||
}
|
||||
ksort( $staffeln, SORT_NUMERIC );
|
||||
|
||||
$labels = [];
|
||||
$d1 = [];
|
||||
$d2 = [];
|
||||
foreach ( $staffeln as $s => $v ) {
|
||||
$labels[] = 'Staffel ' . $s;
|
||||
$d1[] = $v['c1'] > 0 ? round( $v['sum1'] / $v['c1'], 2 ) : 0;
|
||||
$d2[] = $v['c2'] > 0 ? round( $v['sum2'] / $v['c2'], 2 ) : 0;
|
||||
}
|
||||
|
||||
$labels_json = wp_json_encode( $labels );
|
||||
$d1_json = wp_json_encode( $d1 );
|
||||
$d2_json = wp_json_encode( $d2 );
|
||||
|
||||
$c1 = $this->colors['accent'];
|
||||
$c2 = $this->colors['primary'];
|
||||
|
||||
$js = "new Chart(ctx,{type:'bar',data:{labels:{$labels_json},datasets:[{label:'1. Testessen',data:{$d1_json},backgroundColor:'{$c1}'},{label:'2. Testessen',data:{$d2_json},backgroundColor:'{$c2}'}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:_m?10:14},padding:_m?8:16}},datalabels:{color:'#fff',font:{weight:'bold',size:_m?9:11},anchor:'center',align:'center',display:function(c){return c.dataset.data[c.dataIndex]>0;}}},scales:{x:{ticks:{font:{size:_m?9:12},maxRotation:_m?45:0}},y:{beginAtZero:true,max:5,ticks:{font:{size:_m?10:12}},title:{display:true,text:'Bewertung (1-5)',font:{size:_m?10:12}}}}}});";
|
||||
|
||||
return $this->wrap( $id, $js, '500px', '400px' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 4. Restaurants pro Bundesland - Horizontal Bar (dynamisch: 45px pro Region, min 400px)
|
||||
*/
|
||||
public function render_region_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$regions = $this->data->get_region_counts();
|
||||
$count = count( $regions );
|
||||
$h = max( 400, $count * 45 );
|
||||
$id = $this->get_id();
|
||||
|
||||
$labels = wp_json_encode( array_keys( $regions ) );
|
||||
$values = wp_json_encode( array_values( $regions ) );
|
||||
$bg = wp_json_encode( array_slice( $this->primary_shades, 0, max( $count, 1 ) ) );
|
||||
|
||||
// Spezielle Datalabels für multi-color Balken: Helligkeit der BG-Farbe prüfen
|
||||
$dl = "datalabels:{color:function(c){var bg=c.dataset.backgroundColor[c.dataIndex]||'#333';var r=parseInt(bg.substr(1,2),16),g=parseInt(bg.substr(3,2),16),b=parseInt(bg.substr(5,2),16);var lum=(0.299*r+0.587*g+0.114*b)/255;return lum>0.55?'#333':'#fff';},font:{weight:'bold',size:_m?9:11},display:function(c){return c.dataset.data[c.dataIndex]>0;},anchor:'center',align:'center'}";
|
||||
|
||||
$js = "new Chart(ctx,{type:'bar',data:{labels:{$labels},datasets:[{label:'Restaurants',data:{$values},backgroundColor:{$bg}}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},{$dl}},scales:{x:{beginAtZero:true,ticks:{stepSize:1,font:{size:_m?10:12}}},y:{ticks:{font:{size:_m?10:12}}}}}});";
|
||||
|
||||
$h_mobile = max( 400, $count * 38 );
|
||||
return $this->wrap( $id, $js, $h . 'px', $h_mobile . 'px' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. Überleben pro Bundesland - Stacked Horizontal Bar (dynamisch: 45px pro Region, min 400px)
|
||||
*/
|
||||
public function render_region_status_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$region_status = $this->data->get_region_status();
|
||||
|
||||
uasort( $region_status, function( $a, $b ) {
|
||||
return array_sum( $b ) - array_sum( $a );
|
||||
} );
|
||||
|
||||
$count = count( $region_status );
|
||||
$h = max( 400, $count * 45 );
|
||||
$id = $this->get_id();
|
||||
|
||||
$labels = wp_json_encode( array_keys( $region_status ) );
|
||||
$ds_open = wp_json_encode( array_column( array_values( $region_status ), 'geöffnet' ) );
|
||||
$ds_closed = wp_json_encode( array_column( array_values( $region_status ), 'geschlossen' ) );
|
||||
$ds_new = wp_json_encode( array_column( array_values( $region_status ), 'neuer Betreiber' ) );
|
||||
|
||||
$c_open = $this->colors['geoeffnet'];
|
||||
$c_closed = $this->colors['geschlossen'];
|
||||
$c_new = $this->colors['neuer_betreiber'];
|
||||
$dl = $this->hbar_datalabels();
|
||||
|
||||
$js = "new Chart(ctx,{type:'bar',data:{labels:{$labels},datasets:[{label:'Geöffnet',data:{$ds_open},backgroundColor:'{$c_open}'},{label:'Geschlossen',data:{$ds_closed},backgroundColor:'{$c_closed}'},{label:'Neuer Betreiber',data:{$ds_new},backgroundColor:'{$c_new}'}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:_m?10:14},padding:_m?8:16}},{$dl}},scales:{x:{stacked:true,beginAtZero:true,ticks:{stepSize:1,font:{size:_m?10:12}}},y:{stacked:true,ticks:{font:{size:_m?10:12}}}}}});";
|
||||
|
||||
$h_mobile = max( 400, $count * 38 );
|
||||
return $this->wrap( $id, $js, $h . 'px', $h_mobile . 'px' );
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. Durchschnittliche Ergebnisse pro Staffel - Line Chart (450px hoch)
|
||||
*/
|
||||
public function render_staffel_ergebnis_chart( $atts ) {
|
||||
$this->enqueue();
|
||||
$avg_data = $this->data->get_staffel_ergebnis_avg();
|
||||
$id = $this->get_id();
|
||||
|
||||
$labels = wp_json_encode( array_map( function( $s ) {
|
||||
return 'Staffel ' . $s;
|
||||
}, array_keys( $avg_data ) ) );
|
||||
|
||||
$d1 = wp_json_encode( array_column( array_values( $avg_data ), 'avg1' ) );
|
||||
$d2 = wp_json_encode( array_column( array_values( $avg_data ), 'avg2' ) );
|
||||
|
||||
$c1 = $this->colors['accent'];
|
||||
$c2 = $this->colors['primary'];
|
||||
$dl = $this->line_datalabels();
|
||||
|
||||
$js = "new Chart(ctx,{type:'line',data:{labels:{$labels},datasets:[{label:'Ø 1. Testessen',data:{$d1},borderColor:'{$c1}',backgroundColor:'{$c1}22',tension:0.3,fill:true,pointRadius:_m?3:5},{label:'Ø 2. Testessen',data:{$d2},borderColor:'{$c2}',backgroundColor:'{$c2}22',tension:0.3,fill:true,pointRadius:_m?3:5}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:_m?10:14},padding:_m?8:16}},{$dl}},scales:{x:{ticks:{font:{size:_m?9:12},maxRotation:_m?45:0}},y:{beginAtZero:true,max:5,ticks:{font:{size:_m?10:12}},title:{display:true,text:'Ø Bewertung (1-5)',font:{size:_m?10:12}}}}}});";
|
||||
|
||||
return $this->wrap( $id, $js, '450px', '380px' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user