147 lines
8.5 KiB
Plaintext

#load "helpers.uce"
COMPONENT(Request& context)
{
DValue asset_props;
asset_props["css"]["0"] = "themes/common/css/gauges.css";
asset_props["js"]["0"] = "components/gauges/common.js";
print(component("../theme/assets", asset_props, context));
String gauge_id = first(context.props["id"].to_string(), "needlegauge-" + std::to_string((u64)time()));
String style = context.props["style"].to_string();
String title = context.props["title"].to_string();
String subtitle = context.props["subtitle"].to_string();
f64 size = float_val(first(context.props["size"].to_string(), "200"));
DValue scale = context.props["scale"];
f64 scale_angle_start = float_val(first(scale["angle_start"].to_string(), std::to_string(-gauge_pi())));
f64 scale_angle_end = float_val(first(scale["angle_end"].to_string(), "0"));
f64 img_height = float_val(first(context.props["img_height"].to_string(), std::to_string(size * std::max(cos(scale_angle_start), cos(scale_angle_end)))));
<>
<section class="gauge-set needlegauge-set" id="<?= gauge_attr_id(gauge_id) ?>" style="<?= style ?>">
<? if(title != "") { ?>
<div class="gauge-set-header">
<h3><?= title ?></h3>
<? if(subtitle != "") { ?><p><?= subtitle ?></p><? } ?>
</div>
<? } ?>
<div class="needlegauge-grid">
<? context.props["items"].each([&](DValue item, String item_id_raw) {
DValue merged = scale;
item.each([&](DValue value, String key) { merged[key] = value; });
String item_id = gauge_attr_id(item_id_raw);
String label = first(merged["label"].to_string(), item_id_raw);
String tooltip = merged["tooltip"].to_string();
String unit = merged["unit"].to_string();
f64 min_value = float_val(first(merged["min"].to_string(), "0"));
f64 max_value = float_val(first(merged["max"].to_string(), "100"));
f64 value = float_val(first(merged["value"].to_string(), "0"));
f64 vrange = max_value - min_value;
if(vrange == 0)
vrange = 1;
f64 angle_start = float_val(first(merged["angle_start"].to_string(), std::to_string(-gauge_pi())));
f64 angle_end = float_val(first(merged["angle_end"].to_string(), "0"));
f64 pct_value = gauge_clamp_value((value - min_value) / vrange, 0.0, 1.0);
f64 needle_angle = -gauge_pi() + angle_start + (pct_value * (angle_end - angle_start));
String needle_color = first(merged["color"].to_string(), "#888888");
if(merged["color"].get_type_name() == "array")
needle_color = first(gauge_range_color(merged["color"], value), "#888888");
String tick_color = first(merged["tick-color"].to_string(), "#888888");
f64 tick_interval = float_val(first(merged["ticks-every"].to_string(), std::to_string(vrange / 20.0)));
if(tick_interval == 0)
tick_interval = std::max(1.0, vrange / 20.0);
f64 label_interval = float_val(first(merged["value-labels-every"].to_string(), std::to_string(vrange / 4.0)));
if(label_interval == 0)
label_interval = std::max(1.0, vrange / 4.0);
String ticks_html = "";
for(f64 v = min_value; v <= max_value + 0.0001; v += tick_interval)
{
f64 angle = angle_start + ((v - min_value) / vrange) * (angle_end - angle_start);
f64 x1 = size / 2.0 + cos(angle) * size * 0.38;
f64 y1 = size / 2.0 + sin(angle) * size * 0.38;
f64 x2 = size / 2.0 + cos(angle) * size * 0.41;
f64 y2 = size / 2.0 + sin(angle) * size * 0.41;
ticks_html += "<line x1=\"" + gauge_string_attr(std::to_string(x1)) + "\" y1=\"" + gauge_string_attr(std::to_string(y1)) +
"\" x2=\"" + gauge_string_attr(std::to_string(x2)) + "\" y2=\"" + gauge_string_attr(std::to_string(y2)) +
"\" stroke=\"" + gauge_string_attr(tick_color) + "\" stroke-width=\"1\"/>";
}
for(f64 v = min_value; v <= max_value + 0.0001; v += label_interval)
{
f64 angle = angle_start + ((v - min_value) / vrange) * (angle_end - angle_start);
f64 x1 = size / 2.0 + cos(angle) * size * 0.35;
f64 y1 = size / 2.0 + sin(angle) * size * 0.35;
f64 x2 = size / 2.0 + cos(angle) * size * 0.41;
f64 y2 = size / 2.0 + sin(angle) * size * 0.41;
ticks_html += "<line x1=\"" + gauge_string_attr(std::to_string(x1)) + "\" y1=\"" + gauge_string_attr(std::to_string(y1)) +
"\" x2=\"" + gauge_string_attr(std::to_string(x2)) + "\" y2=\"" + gauge_string_attr(std::to_string(y2)) +
"\" stroke=\"" + gauge_string_attr(tick_color) + "\" stroke-width=\"3\"/>";
f64 lx = size / 2.0 + cos(angle) * size * 0.45;
f64 ly = size / 2.0 + sin(angle) * size * 0.45;
String label_value = std::to_string((s64)round(v));
ticks_html += "<text x=\"" + gauge_string_attr(std::to_string(lx)) + "\" y=\"" + gauge_string_attr(std::to_string(ly)) +
"\" text-anchor=\"middle\" dominant-baseline=\"central\" font-size=\"10\" fill=\"" + gauge_string_attr(tick_color) + "\">" +
html_escape(label_value) + "</text>";
}
String segments_html = "";
if(merged["color"].get_type_name() == "array")
{
merged["color"].each([&](DValue range, String key) {
f64 range_from = std::max(float_val(first(range["from"].to_string(), std::to_string(min_value))), min_value);
f64 range_to = std::min(float_val(first(range["to"].to_string(), std::to_string(max_value))), max_value);
f64 angle_from = angle_start + ((range_from - min_value) / vrange) * (angle_end - angle_start);
f64 angle_to = angle_start + ((range_to - min_value) / vrange) * (angle_end - angle_start);
segments_html += gauge_circle_segment_svg(size / 2.0, size / 2.0, size * 0.4, angle_from, angle_to, first(range["color"].to_string(), "rgba(120,120,120,0.5)"), 8.0, "rgba(0,0,0,0)", "opacity:0.25");
});
}
else
{
segments_html = gauge_circle_segment_svg(size / 2.0, size / 2.0, size * 0.4, angle_start, angle_end, tick_color, 8.0, "rgba(0,0,0,0)", "opacity:0.25");
}
?><section class="gauge-card needlegauge-card">
<div class="gauge-metric-label needlegauge-label-head"><?= label ?></div>
<div class="needlegauge-visual">
<svg id="<?= gauge_attr_id(gauge_id) ?>-<?= item_id ?>-svg" width="<?= std::to_string((s32)round(size)) ?>" height="<?= std::to_string((s32)round(img_height)) ?>" class="needlegauge-svg"
viewBox="0 0 <?= std::to_string((s32)round(size)) ?> <?= std::to_string((s32)round(img_height)) ?>">
<?: segments_html ?>
<g class="ticks"><?: ticks_html ?></g>
<line id="<?= gauge_attr_id(gauge_id) ?>-<?= item_id ?>-needle" class="needle"
x1="<?= std::to_string(size * 0.55) ?>" y1="<?= std::to_string(size * 0.5) ?>" x2="<?= std::to_string(size * 0.1) ?>" y2="<?= std::to_string(size * 0.5) ?>"
stroke="<?= needle_color ?>" stroke-width="3" stroke-linecap="round"
style="transform-origin: <?= std::to_string(size / 2.0) ?>px <?= std::to_string(size / 2.0) ?>px; transform: rotate(<?= std::to_string(needle_angle) ?>rad); transition: transform 0.3s ease;"/>
<circle cx="<?= std::to_string(size / 2.0) ?>" cy="<?= std::to_string(size / 2.0) ?>" r="6" fill="<?= needle_color ?>"/>
</svg>
</div>
<div class="needlegauge-info">
<div class="gauge-metric-value needlegauge-value" id="<?= gauge_attr_id(gauge_id) ?>-<?= item_id ?>-value" title="<?= tooltip ?>"><?= merged["value"].to_string() + unit ?></div>
<? if(tooltip != "") { ?><div class="gauge-metric-meta needlegauge-meta"><?= tooltip ?></div><? } ?>
</div>
</section><?
}); ?>
</div>
</section>
<? if(context.props["listen"].to_string() != "") { ?>
<script>
window.NeedlegaugeComponents = window.NeedlegaugeComponents || {
start_listen : function(prop) {
$.events.on('value-broadcast', function(data) {
if(!prop.items || !prop.items[data.name]) return;
let item = Object.assign({}, prop.scale || {}, prop.items[data.name]);
let valueEl = document.getElementById(prop.id + '-' + data.name + '-value');
let needle = document.getElementById(prop.id + '-' + data.name + '-needle');
if(valueEl) valueEl.textContent = data.value + (item.unit || '');
let min = Number(item.min || 0);
let max = Number(item.max || 100);
let pctValue = GaugeComponents.clampValue((Number(data.value) - min) / ((max - min) || 1), 0, 1);
let angleStart = Number(item.angle_start != null ? item.angle_start : -Math.PI);
let angleEnd = Number(item.angle_end != null ? item.angle_end : 0);
let angle = -Math.PI + angleStart + (pctValue * (angleEnd - angleStart));
if(needle) needle.style.transform = 'rotate(' + angle + 'rad)';
});
}
};
NeedlegaugeComponents.start_listen(<?: json_encode(context.props, '\'') ?>);
</script>
<? } ?>
</>
}