'slice-state-ok']) * * @return $this */ public function addSlice($data, $attributes = array()) { $this->slices[] = array($data, $attributes); $this->count += $data; return $this; } /** * Set the thickness for this Donut * * @param integer $thickness * * @return $this */ public function setThickness($thickness) { $this->thickness = $thickness; return $this; } /** * Get the thickness for this Donut * * @return integer */ public function getThickness() { return $this->thickness; } /** * Set the center color for this Donut * * @param string $centerColor * * @return $this */ public function setCenterColor($centerColor) { $this->centerColor = $centerColor; return $this; } /** * Get the center color for this Donut * * @return string */ public function getCenterColor() { return $this->centerColor; } /** * Set the text of the big label * * @param string $labelBig * * @return $this */ public function setLabelBig($labelBig) { $this->labelBig = $labelBig; return $this; } /** * Get the text of the big label * * @return string */ public function getLabelBig() { return $this->labelBig; } /** * Set the url behind the big label * * @param Url $labelBigUrl * * @return $this */ public function setLabelBigUrl($labelBigUrl) { $this->labelBigUrl = $labelBigUrl; return $this; } /** * Get the url behind the big label * * @return Url */ public function getLabelBigUrl() { return $this->labelBigUrl; } /** * Get whether the big label shall be eye-catching * * @return bool */ public function getLabelBigEyeCatching() { return $this->labelBigState !== null; } /** * Set whether the big label shall be eye-catching * * @param bool $labelBigEyeCatching * * @return $this */ public function setLabelBigEyeCatching($labelBigEyeCatching = true) { $this->labelBigState = $labelBigEyeCatching ? 'critical' : null; return $this; } /** * Get the state the big label shall indicate * * @return string|null */ public function getLabelBigState() { return $this->labelBigState; } /** * Set the state the big label shall indicate * * @param string|null $labelBigState * * @return $this */ public function setLabelBigState($labelBigState) { $this->labelBigState = $labelBigState; return $this; } /** * Set the text of the small label * * @param string $labelSmall * * @return $this */ public function setLabelSmall($labelSmall) { $this->labelSmall = $labelSmall; return $this; } /** * Get the text of the small label * * @return string */ public function getLabelSmall() { return $this->labelSmall; } /** * Put together all slices of this Donut * * @return array $svg */ protected function assemble() { // svg tag containing the ring $svg = array( 'tag' => 'svg', 'attributes' => array( 'xmlns' => 'http://www.w3.org/2000/svg', 'viewbox' => '0 0 40 40', 'class' => 'donut-graph' ), 'content' => array() ); // Donut hole $svg['content'][] = array( 'tag' => 'circle', 'attributes' => array( 'cx' => 20, 'cy' => 20, 'r' => sprintf('%F', $this->radius), 'fill' => $this->getCenterColor() ) ); // When there is no data show gray circle $svg['content'][] = array( 'tag' => 'circle', 'attributes' => array( 'aria-hidden' => true, 'cx' => 20, 'cy' => 20, 'r' => sprintf('%F', $this->radius), 'fill' => $this->getCenterColor(), 'stroke-width' => $this->getThickness(), 'class' => 'slice-state-not-checked' ) ); $slices = $this->slices; if ($this->count !== 0) { array_walk($slices, function (&$slice) { $slice[0] = round(100 / $this->count * $slice[0], 2); }); } // on 0 the donut would start at "3 o'clock" and the offset shifts counterclockwise $offset = 25; foreach ($slices as $slice) { $svg['content'][] = array( 'tag' => 'circle', 'attributes' => $slice[1] + array( 'cx' => 20, 'cy' => 20, 'r' => sprintf('%F', $this->radius), 'fill' => 'transparent', 'stroke-width' => $this->getThickness(), 'stroke-dasharray' => sprintf('%F', $slice[0]) . ' ' . sprintf('%F', (99.9 - $slice[0])), // 99.9 prevents gaps (slight overlap) 'stroke-dashoffset' => sprintf('%F', $offset) ) ); // negative values shift in the clockwise direction $offset -= $slice[0]; } $result = array( 'tag' => 'div', 'content' => array($svg) ); $labelBig = (string) $this->getLabelBig(); $labelSmall = (string) $this->getLabelSmall(); if ($labelBig !== '' || $labelSmall !== '') { $labels = array( 'tag' => 'div', 'attributes' => array( 'class' => 'donut-label' ), 'content' => array() ); if ($labelBig !== '') { $labels['content'][] = array( 'tag' => 'a', 'attributes' => array( 'aria-label' => $labelBig . ' ' . $labelSmall, 'href' => $this->getLabelBigUrl() ? $this->getLabelBigUrl()->getAbsoluteUrl() : null, 'class' => $this->labelBigState === null ? 'donut-label-big' : 'donut-label-big state-' . $this->labelBigState ), 'content' => $this->shortenLabel($labelBig) ); } if ($labelSmall !== '') { $labels['content'][] = array( 'tag' => 'p', 'attributes' => array( 'class' => 'donut-label-small', 'x' => '50%', 'y' => '50%' ), 'content' => $labelSmall ); } $result['content'][] = $labels; } return $result; } /** * Shorten the label to 3 digits if it is numeric * * 10 => 10 ... 1111 => ~1k ... 1888 => ~2k * * @param int|string $label * * @return string */ protected function shortenLabel($label) { if (is_numeric($label) && strlen($label) > 3) { return round($label, -3)/1000 . 'k'; } return $label; } protected function encode($content) { return htmlspecialchars($content, ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8', true); } protected function renderAttributes(array $attributes) { $html = array(); foreach ($attributes as $name => $value) { if ($value === null) { continue; } if (is_bool($value) && $value) { $html[] = $name; continue; } if (is_array($value)) { $value = implode(' ', $value); } $html[] = "$name=\"" . $this->encode($value) . '"'; } return implode(' ', $html); } protected function renderContent(array $element) { $tag = $element['tag']; $attributes = isset($element['attributes']) ? $element['attributes'] : array(); $content = isset($element['content']) ? $element['content'] : null; $html = array( // rtrim because attributes may be empty rtrim("<$tag " . $this->renderAttributes($attributes)) . ">" ); if ($content !== null) { if (is_array($content)) { foreach ($content as $child) { $html[] = is_array($child) ? $this->renderContent($child) : $this->encode($child); } } else { $html[] = $this->encode($content); } } $html[] = ""; return implode("\n", $html); } public function render() { $svg = $this->assemble(); return $this->renderContent($svg); } }