trying to port web app starter from PHP
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<script>
|
||||
|
||||
window.ArcgaugeComponents = window.ArcgaugeComponents || {
|
||||
start_listen : function(prop) {
|
||||
$.events.on('value-broadcast', function(data) {
|
||||
if(!prop.items || !prop.items[data.name]) return;
|
||||
const item = Object.assign({}, prop.scale || {}, prop.items[data.name]);
|
||||
GaugeComponents.updateArcGauge({
|
||||
arcId: prop.id + '-' + data.name + '-arc',
|
||||
textId: prop.id + '-' + data.name + '-text',
|
||||
metaId: prop.id + '-' + data.name + '-meta',
|
||||
value: Number(data.value),
|
||||
max: Number(item.max || 100),
|
||||
suffix: item.unit || '',
|
||||
precision: item.precision,
|
||||
watermarkPrefix: item.watermark_prefix || data.name,
|
||||
color: item.color,
|
||||
meta: data.meta != null ? data.meta : null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script><?php
|
||||
|
||||
include_js('components/gauges/common.js');
|
||||
include_css('themes/common/css/gauges.css');
|
||||
|
||||
return [
|
||||
|
||||
'render' => function($prop) {
|
||||
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'arcgauge-'.uniqid();
|
||||
$prop['scale'] = $prop['scale'] ?? array();
|
||||
$prop['items'] = $prop['items'] ?? array();
|
||||
?>
|
||||
<div class="arcgauge-set" id="<?= asafe($prop['id']) ?>" style="<?= asafe((string)($prop['style'] ?? '')) ?>">
|
||||
<?php if(!empty($prop['title'])) { ?>
|
||||
<div class="arcgauge-set-header">
|
||||
<h3><?= safe((string)$prop['title']) ?></h3>
|
||||
<?php if(!empty($prop['subtitle'])) { ?><p><?= safe((string)$prop['subtitle']) ?></p><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="arcgauge-grid">
|
||||
<?php foreach($prop['items'] as $item_id => $item) {
|
||||
$item = array_merge($prop['scale'], $item);
|
||||
$value = (float)first($item['value'], 0);
|
||||
$max = (float)first($item['max'], 100);
|
||||
$precision = isset($item['precision']) ? (int)$item['precision'] : 1;
|
||||
$pct = $max > 0 ? clamp(($value / $max) * 100, 0, 100) : 0;
|
||||
$arc_length = round(($pct / 100) * 157.08, 1);
|
||||
$display_value = number_format($value, $precision).safe((string)($item['unit'] ?? ''));
|
||||
$resolved_color = '#10b981';
|
||||
if(isset($item['color']))
|
||||
{
|
||||
if(is_array($item['color']))
|
||||
{
|
||||
$color_entry = pick_entry_from_range($item['color'], $value);
|
||||
$resolved_color = first($color_entry['color'] ?? false, $resolved_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
$resolved_color = (string)$item['color'];
|
||||
}
|
||||
}
|
||||
else if($pct >= 85)
|
||||
{
|
||||
$resolved_color = 'var(--error, #ef4444)';
|
||||
}
|
||||
else if($pct >= 60)
|
||||
{
|
||||
$resolved_color = 'var(--warning, #f59e0b)';
|
||||
}
|
||||
else
|
||||
{
|
||||
$resolved_color = 'var(--success, #10b981)';
|
||||
}
|
||||
?>
|
||||
<section class="arcgauge-card">
|
||||
<div class="arcgauge-label"><?= safe((string)first($item['label'], ucfirst((string)$item_id))) ?></div>
|
||||
<svg class="arcgauge-svg" viewBox="0 0 120 68" aria-hidden="true">
|
||||
<path class="arcgauge-track" d="M 10 60 A 50 50 0 0 1 110 60" fill="none" stroke-width="5" stroke-linecap="round"/>
|
||||
<path id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-arc" class="arcgauge-arc-dyn" d="M 10 60 A 50 50 0 0 1 110 60" fill="none" stroke="<?= asafe($resolved_color) ?>" stroke-width="5" stroke-linecap="round" stroke-dasharray="<?= asafe((string)$arc_length) ?> 157.08"/>
|
||||
<?php if(!empty($item['watermark_prefix'])) { ?>
|
||||
<line id="<?= asafe($item['watermark_prefix']) ?>WmLo" class="arcgauge-watermark arcgauge-watermark-lo" x1="0" y1="0" x2="0" y2="0" opacity="0"/>
|
||||
<line id="<?= asafe($item['watermark_prefix']) ?>WmHi" class="arcgauge-watermark arcgauge-watermark-hi" x1="0" y1="0" x2="0" y2="0" opacity="0"/>
|
||||
<?php } ?>
|
||||
<text id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-text" class="arcgauge-value" x="60" y="47" text-anchor="middle"><?= safe($display_value) ?></text>
|
||||
<text class="arcgauge-caption" x="60" y="62" text-anchor="middle"><?= safe((string)first($item['caption'], '')) ?></text>
|
||||
</svg>
|
||||
<div class="arcgauge-meta" id="<?= asafe($prop['id']) ?>-<?= asafe($item_id) ?>-meta"><?= safe((string)first($item['meta'], '--')) ?></div>
|
||||
</section>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
if(!empty($prop['listen']))
|
||||
{
|
||||
?><script>
|
||||
ArcgaugeComponents.start_listen(<?= jsafe($prop) ?>);
|
||||
</script><?php
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
@@ -0,0 +1,95 @@
|
||||
.horizontal .progressbar-item {
|
||||
display: flex;
|
||||
min-width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.progressbar-container.vertical {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vertical .progressbar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progressbar-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-label, .horizontal .progressbar-value {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-label {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.vertical .progressbar-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-value {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.progressbar-background {
|
||||
flex: 1;
|
||||
background: var(--bg-color);
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vertical .progressbar-background {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.progressbar-bar {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.progressbar-marker {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
opacity: 0.5;
|
||||
background: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.progressbar-marker:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.horizontal .progressbar-marker {
|
||||
border-width: 0 2px 0 2px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
min-width: 4px;
|
||||
}
|
||||
|
||||
.vertical .progressbar-marker {
|
||||
border-width: 2px 0 2px 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
min-height: 4px;
|
||||
}
|
||||
|
||||
.progressbar-background {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.needlegauge-svg .needle {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.needlegauge-item {
|
||||
display: inline-block;
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// common code for gauges components
|
||||
|
||||
window.GaugeComponents = window.GaugeComponents || {};
|
||||
|
||||
Object.assign(window.GaugeComponents, { // as a namespace
|
||||
|
||||
// utility functions
|
||||
clampValue: function(value, min, max) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
},
|
||||
|
||||
pickEntryFromRange: function(ranges, value) {
|
||||
if (!Array.isArray(ranges)) return null;
|
||||
for (const entry of ranges) {
|
||||
if (value >= entry.from && value <= entry.to) return entry;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an SVG element with namespace
|
||||
*/
|
||||
createSVGElement: function(tagName, attributes = {}) {
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
|
||||
Object.entries(attributes).forEach(([key, value]) => {
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets CSS custom property value from computed styles
|
||||
*/
|
||||
getCSSVar: function(varName) {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
|
||||
},
|
||||
|
||||
resolveColor: function(colorSpec, value, pct) {
|
||||
if (Array.isArray(colorSpec)) {
|
||||
const match = this.pickEntryFromRange(colorSpec, value);
|
||||
if (match && match.color) return match.color;
|
||||
}
|
||||
if (typeof colorSpec === 'string' && colorSpec !== '') return colorSpec;
|
||||
if (pct < 60) return this.getCSSVar('--success') || '#10b981';
|
||||
if (pct < 85) return this.getCSSVar('--warning') || '#f59e0b';
|
||||
return this.getCSSVar('--error') || '#ef4444';
|
||||
},
|
||||
|
||||
formatValue: function(value, precision, suffix) {
|
||||
const numericValue = Number(value);
|
||||
const normalizedPrecision = Number.isFinite(precision) ? precision : 1;
|
||||
if (!Number.isFinite(numericValue)) return '--';
|
||||
return numericValue.toFixed(normalizedPrecision) + (suffix || '');
|
||||
},
|
||||
|
||||
gaugeArcPoint: function(pct, radius = 50) {
|
||||
const angle = Math.PI - (pct / 100) * Math.PI;
|
||||
return {
|
||||
x: 60 + radius * Math.cos(angle),
|
||||
y: 60 - radius * Math.sin(angle),
|
||||
};
|
||||
},
|
||||
|
||||
updateWatermark: function(prefix, pct) {
|
||||
const now = Date.now();
|
||||
this._watermarks = this._watermarks || {};
|
||||
let watermark = this._watermarks[prefix];
|
||||
const resetWindow = 10 * 60 * 1000;
|
||||
if (!watermark || (now - watermark.resetTs) > resetWindow) {
|
||||
watermark = { lo: pct, hi: pct, resetTs: now };
|
||||
this._watermarks[prefix] = watermark;
|
||||
} else {
|
||||
if (pct < watermark.lo) watermark.lo = pct;
|
||||
if (pct > watermark.hi) watermark.hi = pct;
|
||||
}
|
||||
return watermark;
|
||||
},
|
||||
|
||||
renderWatermarkTick: function(lineId, pct) {
|
||||
const line = document.getElementById(lineId);
|
||||
if (!line) return;
|
||||
if (pct == null) {
|
||||
line.setAttribute('opacity', '0');
|
||||
return;
|
||||
}
|
||||
const outer = this.gaugeArcPoint(pct, 53);
|
||||
const inner = this.gaugeArcPoint(pct, 43);
|
||||
line.setAttribute('x1', outer.x.toFixed(1));
|
||||
line.setAttribute('y1', outer.y.toFixed(1));
|
||||
line.setAttribute('x2', inner.x.toFixed(1));
|
||||
line.setAttribute('y2', inner.y.toFixed(1));
|
||||
line.setAttribute('opacity', '0.7');
|
||||
},
|
||||
|
||||
updateArcGauge: function(options) {
|
||||
const value = Number(options.value);
|
||||
const max = Number(options.max || 100);
|
||||
const normalizedValue = Number.isFinite(value) ? value : 0;
|
||||
const pct = this.clampValue(max === 0 ? 0 : (normalizedValue / max) * 100, 0, 100);
|
||||
const arcLength = (pct / 100) * 157.08;
|
||||
const arc = document.getElementById(options.arcId);
|
||||
const text = document.getElementById(options.textId);
|
||||
const meta = options.metaId ? document.getElementById(options.metaId) : null;
|
||||
if (arc) {
|
||||
arc.setAttribute('stroke-dasharray', arcLength.toFixed(1) + ' 157.08');
|
||||
arc.setAttribute('stroke', this.resolveColor(options.color, normalizedValue, pct));
|
||||
}
|
||||
if (text) {
|
||||
text.textContent = this.formatValue(normalizedValue, options.precision, options.suffix);
|
||||
}
|
||||
if (meta && options.meta != null) {
|
||||
meta.textContent = options.meta;
|
||||
}
|
||||
if (options.watermarkPrefix) {
|
||||
const watermark = this.updateWatermark(options.watermarkPrefix, pct);
|
||||
this.renderWatermarkTick(options.watermarkPrefix + 'WmLo', watermark.lo);
|
||||
this.renderWatermarkTick(options.watermarkPrefix + 'WmHi', watermark.hi);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
<script>
|
||||
|
||||
window.NeedlegaugeComponents = window.NeedlegaugeComponents || {
|
||||
|
||||
start_listen : function(prop) {
|
||||
$.events.on('value-broadcast', function(data) {
|
||||
if(prop.items[data.name]) {
|
||||
// update value text
|
||||
let item = Object.assign({}, prop.scale, prop.items[data.name]);
|
||||
$('#' + prop.id + '-' + data.name + '-value').text(data.value + (item.unit || ''));
|
||||
let vrange = (item.max || 100) - (item.min || 0);
|
||||
let pct_value = GaugeComponents.clampValue((data.value - (item.min || 0)) / vrange, 0, 1);
|
||||
let angle = -Math.PI + item.angle_start + (pct_value * (item.angle_end - item.angle_start));
|
||||
$('#' + prop.id + '-' + data.name + '-needle').css('transform', 'rotate(' + angle + 'rad)');
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script><?php
|
||||
|
||||
include_js('components/gauges/common.js');
|
||||
include_css('themes/common/css/gauges.css');
|
||||
|
||||
return [
|
||||
|
||||
'render' => function($prop) {
|
||||
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'needlegauge-' . uniqid();
|
||||
$prop['style'] = (string)($prop['style'] ?? '');
|
||||
$prop['items'] = (array)($prop['items'] ?? array());
|
||||
$prop['scale'] = (array)($prop['scale'] ?? array());
|
||||
$prop['size'] = first($prop['size'] ?? false, 200);
|
||||
$prop['subtitle'] = (string)($prop['subtitle'] ?? '');
|
||||
$prop['scale']['angle_start'] = first($prop['scale']['angle_start'], -pi());
|
||||
$prop['scale']['angle_end'] = first($prop['scale']['angle_end'], 0);
|
||||
$prop['img_height'] = first($prop['img_height'] ?? false,
|
||||
$prop['size'] * max(cos(first($prop['scale']['angle_start'], -pi())), cos(first($prop['scale']['angle_end'], 0))));
|
||||
?>
|
||||
<section class="gauge-set needlegauge-set" id="<?= asafe($prop['id']) ?>" style="<?= asafe($prop['style']) ?>">
|
||||
<?php if(isset($prop['title'])) { ?>
|
||||
<div class="gauge-set-header">
|
||||
<h3><?= safe($prop['title']) ?></h3>
|
||||
<?php if($prop['subtitle'] !== '') { ?><p><?= safe($prop['subtitle']) ?></p><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="needlegauge-grid">
|
||||
<?php
|
||||
foreach($prop['items'] as $item_id => $item)
|
||||
{
|
||||
$item = array_merge($prop['scale'], $item);
|
||||
$item['min'] = first($item['min'] ?? false, 0);
|
||||
$item['max'] = first($item['max'] ?? false, 100);
|
||||
$item['tooltip'] = (string)($item['tooltip'] ?? '');
|
||||
$item['unit'] = (string)($item['unit'] ?? '');
|
||||
$item['color'] = $item['color'] ?? false;
|
||||
$item['label'] = (string)first($item['label'] ?? false, ucfirst((string)$item_id));
|
||||
$vrange = ($item['max'] - $item['min']);
|
||||
$pct_value = clamp(($item['value'] - $item['min']) / $vrange, 0, 1);
|
||||
$needle_angle = -pi() + $item['angle_start'] + ($pct_value * ($item['angle_end'] - $item['angle_start']));
|
||||
$needle_color = first($item['color'] ?? false, '#888888');
|
||||
if(is_array($item['color'])) {
|
||||
$color_entry = pick_entry_from_range($item['color'], $item['value']);
|
||||
$needle_color = first($color_entry['color'] ?? false, '#888888');
|
||||
}
|
||||
$tick_interval = first($item['ticks-every'], $vrange / 20);
|
||||
$label_interval = first($item['value-labels-every'], $vrange / 4);
|
||||
$tick_number = 0;
|
||||
$ticks_html = '';
|
||||
$min_angle = first($item['angle_start']);
|
||||
$max_angle = first($item['angle_end']);
|
||||
$tick_color = first($item['tick-color'], '#888888');
|
||||
|
||||
for($v = $item['min']; $v <= $item['max']; $v += $tick_interval)
|
||||
{
|
||||
$tick_number++;
|
||||
$angle = $min_angle + (($v-$item['min'])/$vrange) * ($max_angle - $min_angle);
|
||||
|
||||
$x1 = $prop['size']/2 + cos($angle) * $prop['size']*0.38;
|
||||
$y1 = $prop['size']/2 + sin($angle) * $prop['size']*0.38;
|
||||
$x2 = $prop['size']/2 + cos($angle) * $prop['size']*0.41;
|
||||
$y2 = $prop['size']/2 + sin($angle) * $prop['size']*0.41;
|
||||
|
||||
$ticks_html .= '<line x1="'.($x1).'" y1="'.($y1).'" x2="'.($x2).'" y2="'.($y2).'"
|
||||
stroke="'. $tick_color .'" stroke-width="1"/>';
|
||||
|
||||
}
|
||||
|
||||
for($v = $item['min']; $v <= $item['max']; $v += $label_interval)
|
||||
{
|
||||
$tick_number++;
|
||||
$angle = $min_angle + (($v-$item['min'])/$vrange) * ($max_angle - $min_angle);
|
||||
|
||||
$x1 = $prop['size']/2 + cos($angle) * $prop['size']*0.35;
|
||||
$y1 = $prop['size']/2 + sin($angle) * $prop['size']*0.35;
|
||||
$x2 = $prop['size']/2 + cos($angle) * $prop['size']*0.41;
|
||||
$y2 = $prop['size']/2 + sin($angle) * $prop['size']*0.41;
|
||||
|
||||
$ticks_html .= '<line x1="'.($x1).'" y1="'.($y1).'" x2="'.($x2).'" y2="'.($y2).'"
|
||||
stroke="'. $tick_color .'" stroke-width="3"/>';
|
||||
|
||||
$lx = $prop['size']/2 + cos($angle) * $prop['size']*0.45;
|
||||
$ly = $prop['size']/2 + sin($angle) * $prop['size']*0.45;
|
||||
$ticks_html .= '<text x="'.($lx).'" y="'.($ly).'" text-anchor="middle" dominant-baseline="central"
|
||||
font-size="10" fill="'. $tick_color .'">'.($v == 0 ? '0' : $v).'</text>';
|
||||
}
|
||||
|
||||
?>
|
||||
<section class="gauge-card needlegauge-card">
|
||||
<div class="gauge-metric-label needlegauge-label-head"><?= safe($item['label']) ?></div>
|
||||
|
||||
<div class="needlegauge-visual">
|
||||
<svg id="<?= $prop['id'] ?>-<?= $item_id ?>-svg" width="<?= $prop['size'] ?>" height="<?= $prop['img_height'] ?>" class="needlegauge-svg"
|
||||
viewBox="0 0 <?= $prop['size'] ?> <?= $prop['img_height'] ?>">
|
||||
|
||||
<?php
|
||||
if(is_array($item['color']))
|
||||
{
|
||||
foreach($item['color'] as $range)
|
||||
{
|
||||
$angle_from = $min_angle + ((max($range['from'], $item['min']) - $item['min']) / $vrange) * ($max_angle - $min_angle);
|
||||
$angle_to = $min_angle + ((min($range['to'], $item['max']) - $item['min']) / $vrange) * ($max_angle - $min_angle);
|
||||
$color = $range['color'] ?? 'rgba(120,120,120,0.5)';
|
||||
SVG::circle_segment($prop['size']/2, $prop['size']/2, $prop['size']*0.4,
|
||||
$angle_from, $angle_to, $color, 8, 'rgba(0,0,0,0)', 'opacity:0.25');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SVG::circle_segment($prop['size']/2, $prop['size']/2, $prop['size']*0.4,
|
||||
$min_angle, $max_angle, $tick_color, 8, 'rgba(0,0,0,0)', 'opacity:0.25');
|
||||
}
|
||||
?>
|
||||
|
||||
<g class="ticks">
|
||||
<?= $ticks_html ?>
|
||||
</g>
|
||||
|
||||
<line id="<?= $prop['id'] ?>-<?= $item_id ?>-needle" class="needle"
|
||||
x1="<?= $prop['size']*0.55 ?>" y1="<?= $prop['size']*0.5 ?>" x2="<?= $prop['size']*0.1 ?>" y2="<?= $prop['size']*0.5 ?>"
|
||||
stroke="<?= ($needle_color) ?>" stroke-width="3" stroke-linecap="round"
|
||||
style="transform-origin: <?= $prop['size']/2 ?>px <?= $prop['size']/2 ?>px;
|
||||
transform: rotate(<?= ($needle_angle) ?>rad); transition: transform 0.3s ease;"/>
|
||||
|
||||
<circle cx="<?= $prop['size']/2 ?>" cy="<?= $prop['size']/2 ?>" r="6" fill="<?= $needle_color ?>"/>
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="needlegauge-info">
|
||||
<div class="gauge-metric-value needlegauge-value" id="<?= $prop['id'] ?>-<?= $item_id ?>-value"
|
||||
title="<?= asafe($item['tooltip']) ?>">
|
||||
<?= safe($item['value']) ?><?= safe($item['unit']) ?>
|
||||
</div>
|
||||
<?php if($item['tooltip'] !== '') { ?><div class="gauge-metric-meta needlegauge-meta"><?= safe($item['tooltip']) ?></div><?php } ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
if(!empty($prop['listen']))
|
||||
{
|
||||
?><script>
|
||||
NeedlegaugeComponents.start_listen(<?= jsafe($prop) ?>);
|
||||
</script><?php
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
@@ -0,0 +1,203 @@
|
||||
<script>
|
||||
|
||||
window.ProgressbarComponents = window.ProgressbarComponents || {
|
||||
|
||||
start_listen : function(prop) {
|
||||
$.events.on('value-broadcast', function(data) {
|
||||
if(prop.items[data.name]) {
|
||||
// update value text
|
||||
let bar = Object.assign({}, prop.scale, prop.items[data.name]);
|
||||
$('#' + prop.id + '-' + data.name + '-value').text(data.value + (bar.unit || ''));
|
||||
// update bar width/height
|
||||
let vrange = (bar.max || 100) - (bar.min || 0);
|
||||
let pct_value = GaugeComponents.clampValue((data.value - (bar.min || 0)) / vrange * 100, 0, 100);
|
||||
if(Array.isArray(bar.color)) {
|
||||
let colorMatch = GaugeComponents.pickEntryFromRange(bar.color, Number(data.value));
|
||||
if(colorMatch && colorMatch.color)
|
||||
$('#' + prop.id + '-' + data.name + '-bar').css('background', colorMatch.color);
|
||||
}
|
||||
if(prop.layout === 'horizontal') {
|
||||
$('#' + prop.id + '-' + data.name + '-bar').css('width', pct_value + '%');
|
||||
} else {
|
||||
$('#' + prop.id + '-' + data.name + '-bar').css('height', pct_value + '%');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script><?php
|
||||
|
||||
include_js('components/gauges/common.js');
|
||||
include_css('themes/common/css/gauges.css');
|
||||
|
||||
return [
|
||||
|
||||
'render' => function($prop) {
|
||||
$prop['id'] = !empty($prop['id']) ? $prop['id'] : 'progressbar-'.uniqid();
|
||||
$prop['style'] = (string)($prop['style'] ?? '');
|
||||
$prop['item-style'] = (string)($prop['item-style'] ?? '');
|
||||
$prop['label-style'] = (string)($prop['label-style'] ?? '');
|
||||
$prop['value-style'] = (string)($prop['value-style'] ?? '');
|
||||
$prop['bar-style'] = (string)($prop['bar-style'] ?? '');
|
||||
$prop['items'] = (array)($prop['items'] ?? array());
|
||||
$default_palette = [
|
||||
'var(--success, #10b981)',
|
||||
'var(--primary, #60a5fa)',
|
||||
'var(--accent, #22d3ee)',
|
||||
'var(--warning, #f59e0b)',
|
||||
];
|
||||
$auto_color_counter = 0;
|
||||
if(empty($prop['scale'])) $prop['scale'] = [];
|
||||
$layout = first($prop['layout'] ?? false, 'horizontal');
|
||||
?>
|
||||
<section class="gauge-set progressbar-set progressbar-set-<?= asafe($layout) ?>" id="<?= $prop['id'] ?>" style="<?= $prop['style'] ?>">
|
||||
<?php if(isset($prop['title'])) { ?>
|
||||
<div class="gauge-set-header">
|
||||
<h3><?= safe($prop['title']) ?></h3>
|
||||
<?php if(!empty($prop['subtitle'])) { ?><p><?= safe((string)$prop['subtitle']) ?></p><?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if($layout == 'horizontal') { ?>
|
||||
<div class="progressbar-grid progressbar-grid-horizontal">
|
||||
<?php foreach($prop['items'] as $bar_id => $bar)
|
||||
{
|
||||
$bar = array_merge($prop['scale'], $bar);
|
||||
$bar['style'] = (string)($bar['style'] ?? '');
|
||||
$bar['tooltip'] = (string)($bar['tooltip'] ?? '');
|
||||
$bar['before'] = $bar['before'] ?? '';
|
||||
$bar['after'] = $bar['after'] ?? '';
|
||||
$bar['unit'] = (string)($bar['unit'] ?? '');
|
||||
$bar['color'] = $bar['color'] ?? false;
|
||||
$vrange = (first($bar['max'], 100) - first($bar['min'], 0));
|
||||
$bar['pct-value'] = clamp(($bar['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
|
||||
if(is_array($bar['color']))
|
||||
{
|
||||
$color_entry = pick_entry_from_range($bar['color'], $bar['value']);
|
||||
$bar['color'] = $color_entry['color'] ?? false;
|
||||
}
|
||||
if(!$bar['color'])
|
||||
$bar['color'] = first($prop['bar-color'] ?? false, $default_palette[$auto_color_counter++ % sizeof($default_palette)]);
|
||||
?>
|
||||
<section class="gauge-card progressbar-card progressbar-card-horizontal" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>"
|
||||
title="<?= asafe($bar['tooltip']) ?>"
|
||||
style="<?= $prop['item-style'] ?>;<?= $bar['style'] ?>">
|
||||
<?= $bar['before'] ?>
|
||||
<div class="progressbar-card-head">
|
||||
<div class="gauge-metric-label progressbar-label" style="<?= $prop['label-style'] ?>">
|
||||
<?= safe($bar['label']) ?>
|
||||
</div>
|
||||
<div class="gauge-metric-value progressbar-value" style="<?= $prop['value-style'] ?>"
|
||||
id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-value">
|
||||
<?= safe($bar['value']) ?><?= safe($bar['unit']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progressbar-background progressbar-background-horizontal">
|
||||
<div class="progressbar-bar" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-bar"
|
||||
style="background-color: <?= safe($bar['color']) ?>;
|
||||
<?= $prop['bar-style'] ?>
|
||||
opacity: <?= isset($bar['opacity']) ? safe($bar['opacity']) : '0.75' ?>;
|
||||
width: <?= safe($bar['pct-value']) ?>%;">
|
||||
</div>
|
||||
<?php
|
||||
if(isset($prop['markers'])) {
|
||||
foreach($prop['markers'] as $marker_id => $marker) {
|
||||
$marker_pct = clamp(($marker['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
|
||||
?>
|
||||
<div class="progressbar-marker"
|
||||
title="<?= asafe($marker['label']) ?>"
|
||||
style="left: <?= ($marker_pct) ?>%;
|
||||
background: <?= first($marker['color'] ?? false, 'var(--primary-light)') ?>;">
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php if($bar['tooltip'] !== '') { ?><div class="gauge-metric-meta progressbar-meta"><?= safe($bar['tooltip']) ?></div><?php } ?>
|
||||
<?= $bar['after'] ?>
|
||||
</section>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } else { ?>
|
||||
<div class="progressbar-grid progressbar-grid-vertical" style="--progressbar-height: <?= safe((string)first($prop['height'] ?? false, 240)) ?>px;">
|
||||
<?php
|
||||
if(sizeof($prop['items']) > 0)
|
||||
{
|
||||
foreach($prop['items'] as $bar_id => $bar)
|
||||
{
|
||||
$bar = array_merge($prop['scale'], $bar);
|
||||
$bar['style'] = (string)($bar['style'] ?? '');
|
||||
$bar['tooltip'] = (string)($bar['tooltip'] ?? '');
|
||||
$bar['before'] = $bar['before'] ?? '';
|
||||
$bar['after'] = $bar['after'] ?? '';
|
||||
$bar['unit'] = (string)($bar['unit'] ?? '');
|
||||
$bar['color'] = $bar['color'] ?? false;
|
||||
$vrange = (first($bar['max'], 100) - first($bar['min'], 0));
|
||||
$bar['pct-value'] = clamp(($bar['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
|
||||
if(is_array($bar['color']))
|
||||
{
|
||||
$color_entry = pick_entry_from_range($bar['color'], $bar['value']);
|
||||
$bar['color'] = $color_entry['color'] ?? false;
|
||||
}
|
||||
if(!$bar['color'])
|
||||
$bar['color'] = first($prop['bar-color'] ?? false, $default_palette[$auto_color_counter++ % sizeof($default_palette)]);
|
||||
?>
|
||||
<section class="gauge-card progressbar-card progressbar-card-vertical" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>"
|
||||
title="<?= asafe($bar['tooltip']) ?>"
|
||||
style="<?= $prop['item-style'] ?>;<?= $bar['style'] ?>">
|
||||
<?= $bar['before'] ?>
|
||||
<div class="gauge-metric-label progressbar-label" style="<?= $prop['label-style'] ?>">
|
||||
<?= safe($bar['label']) ?>
|
||||
</div>
|
||||
|
||||
<div class="progressbar-background progressbar-background-vertical">
|
||||
<div class="progressbar-bar" id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-bar"
|
||||
style="background-color: <?= safe($bar['color']) ?>;
|
||||
<?= $prop['bar-style'] ?>
|
||||
opacity: <?= isset($bar['opacity']) ? safe($bar['opacity']) : '0.75' ?>;
|
||||
height: <?= safe($bar['pct-value']) ?>%;">
|
||||
</div>
|
||||
<?php
|
||||
// Render markers for vertical layout
|
||||
if(isset($prop['markers'])) {
|
||||
foreach($prop['markers'] as $marker_id => $marker) {
|
||||
$marker_pct = clamp(($marker['value'] - first($bar['min'], 0)) / $vrange * 100, 0, 100);
|
||||
?>
|
||||
<div class="progressbar-marker"
|
||||
title="<?= asafe($marker['label']) ?>"
|
||||
style="bottom: <?= ($marker_pct) ?>%;
|
||||
background: <?= first($marker['color'] ?? false, 'var(--primary-light)') ?>;">
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="gauge-metric-value progressbar-value" style="<?= $prop['value-style'] ?>"
|
||||
id="<?= $prop['id'] ?>-<?= safe($bar_id) ?>-value">
|
||||
<?= safe($bar['value']) ?><?= safe($bar['unit']) ?>
|
||||
</div>
|
||||
<?php if($bar['tooltip'] !== '') { ?><div class="gauge-metric-meta progressbar-meta"><?= safe($bar['tooltip']) ?></div><?php } ?>
|
||||
|
||||
<?= $bar['after'] ?>
|
||||
</section>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</section>
|
||||
<?php
|
||||
if(!empty($prop['listen']))
|
||||
{
|
||||
?><script>
|
||||
ProgressbarComponents.start_listen(<?= jsafe($prop) ?>);
|
||||
</script><?php
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
Reference in New Issue
Block a user