'#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( '
', 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' ); } }