mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-24 11:54:49 +00:00
Add graph and control panel
This commit is contained in:
1 parent
b68da7f759
commit
8e3c8bf61f
6 files changed
+334
-6
No files matched your search
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only or commercial
|
||||
|
||||
#include "mongoose.h"
|
||||
#include "math.h"
|
||||
|
||||
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
|
||||
#define HTTP_ADDR "http://0.0.0.0:8000"
|
||||
@@ -9,6 +10,40 @@
|
||||
#define HTTP_ADDR "http://0.0.0.0:80"
|
||||
#endif
|
||||
|
||||
#define NUM_POINTS 256 // How many data points to send
|
||||
// #define INTERVAL_MS 200 // How often, in milliseconds
|
||||
|
||||
struct point {
|
||||
uint16_t x; // X coordinate of the data point, usually this is time
|
||||
uint16_t y1; // We can plot multiple values per graph,
|
||||
uint16_t y2; // let's plot 2 values: y1 and y2.
|
||||
};
|
||||
|
||||
// This function prints a JSON array of plot points
|
||||
size_t print_points(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||
size_t i, n = 0;
|
||||
const char *comma = "";
|
||||
(void) ap;
|
||||
n += mg_xprintf(fn, arg, "[");
|
||||
for (i = 0; i < NUM_POINTS; i++) {
|
||||
struct point p;
|
||||
p.x = (uint16_t) i;
|
||||
// rand() - 0.5 + sin(x * 0.00002) * 40 + sin(x * 0.001) * 5 + sin(x * 0.1)
|
||||
// * 2;
|
||||
p.y1 = 100 + 50 * sin(i * 0.1) + rand() % 21;
|
||||
p.y2 = 70 + 30 * sin(i * 0.03) + rand() % 17;
|
||||
n += mg_xprintf(fn, arg, "%s[%hu,%hu,%hu]", comma, p.x, p.y1, p.y2);
|
||||
comma = ",";
|
||||
}
|
||||
n += mg_xprintf(fn, arg, "]");
|
||||
return n;
|
||||
}
|
||||
|
||||
static void get_points(union mg_val *val) {
|
||||
val->fn = (void (*)(void)) print_points;
|
||||
}
|
||||
|
||||
|
||||
static bool s_led;
|
||||
|
||||
static void get_led(union mg_val *val) {
|
||||
@@ -22,6 +57,7 @@ static void set_led(const union mg_val *val) {
|
||||
// Modify this. This represents device's state to the dashboard
|
||||
static struct mg_field fields[] = {
|
||||
{"led", MG_VAL_BOOL, get_led, set_led},
|
||||
{"points", MG_VAL_FN, get_points, NULL},
|
||||
{NULL, 0, 0, 0},
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="uPlot.min.css">
|
||||
<!-- Mongoose device dashboard -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/cesanta/mongoose@master/resources/dashboard.js"></script>
|
||||
<script>
|
||||
@@ -20,7 +21,8 @@
|
||||
});
|
||||
</script>
|
||||
<script src="main.js" defer></script>
|
||||
<title>My Dashboard Toolbar</title>
|
||||
<script src="uPlot.iife.min.js" defer></script>
|
||||
<title>My Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
@@ -119,6 +121,61 @@
|
||||
<div class="metric-status metric-status-good">Healthy headroom</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="detail-row">
|
||||
|
||||
<!-- Sensor History Chart -->
|
||||
<div class="panel chart-panel">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<div class="panel-title">Sensor History</div>
|
||||
</div>
|
||||
<div class="chart-legend">
|
||||
<div class="legend-item"><span class="legend-dot legend-dot-color1"></span>Temperature</div>
|
||||
<div class="legend-item"><span class="legend-dot legend-dot-color2"></span>Humidity</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chart"></div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Panel -->
|
||||
<div class="panel controls-panel">
|
||||
<div class="panel-title">Controls</div>
|
||||
<div class="controls-list">
|
||||
<div class="control-row">
|
||||
<div class="control-info">
|
||||
<div class="control-name">Cooling Fan</div>
|
||||
<div class="control-desc">PWM controlled output</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" checked>
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-info">
|
||||
<div class="control-name">Relay Channel 1</div>
|
||||
<div class="control-desc">Auxiliary load output</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" checked>
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-info">
|
||||
<div class="control-name">Relay Channel 2</div>
|
||||
<div class="control-desc">Safety-controlled line</div>
|
||||
</div>
|
||||
<label class="toggle">
|
||||
<input type="checkbox">
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<section id="settings" class="tab-panel" role="tabpanel" aria-labelledby="tab-settings" hidden></section>
|
||||
<section id="firmware" class="tab-panel" role="tabpanel" aria-labelledby="tab-firmware" hidden></section>
|
||||
|
||||
@@ -31,4 +31,43 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
const initialId = window.location.hash.slice(1) || "overview";
|
||||
activateTab(initialId);
|
||||
|
||||
|
||||
const el = document.getElementById("chart");
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
const options = {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
tzDate: (ts) => uPlot.tzDate(new Date(ts * 1e3), "Europe/London"),
|
||||
plugins: [],
|
||||
legend: { show: false },
|
||||
axes: [
|
||||
{ values: "{MMM} {DD}", size: 25 },
|
||||
{ size: 50, xvalues: (u, vals, space) => vals.map(v => (+v / 1000).toFixed(1) + 'k') },
|
||||
],
|
||||
series: [
|
||||
{},
|
||||
{stroke: 'rgba(5, 141, 199, 1)', fill: 'rgba(5, 141, 199, 0.1)'},
|
||||
{stroke: 'rgba(237, 126, 23, 1)', fill: 'rgba(237, 126, 23, 0.1)'},
|
||||
],
|
||||
};
|
||||
|
||||
// Create chart instance
|
||||
const chart = new uPlot(options, [], el);
|
||||
|
||||
function updateGraphPoints(args) {
|
||||
const data = [
|
||||
args.points.map(x => x[0]),
|
||||
args.points.map(x => x[1]),
|
||||
args.points.map(x => x[2]),
|
||||
];
|
||||
//console.log(123, args.points);
|
||||
chart.setData(data);
|
||||
};
|
||||
|
||||
// Intercept change notifications
|
||||
Dashboard.on('change', args => updateGraphPoints(args));
|
||||
Dashboard.call('get', 'points').then(resp => updateGraphPoints(resp));
|
||||
|
||||
});
|
||||
@@ -155,7 +155,7 @@ body {
|
||||
}
|
||||
|
||||
.metric-tile {
|
||||
padding: 1.25rem 1.5rem 1.2rem;
|
||||
padding: 0.75rem 1.5rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(203, 213, 225, 0.75);
|
||||
@@ -178,8 +178,8 @@ body {
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
color: #7d94ac;
|
||||
font-size: 0.82rem;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
@@ -189,7 +189,7 @@ body {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.2rem;
|
||||
color: #18314f;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.metric-number {
|
||||
@@ -202,7 +202,7 @@ body {
|
||||
.metric-unit {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #4a6580;
|
||||
color: #cbd5e1;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ body {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
margin-top: 0.1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.metric-status-good {
|
||||
@@ -228,6 +229,198 @@ body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Detail row (chart + controls) ─────────────────────────────── */
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(203, 213, 225, 0.75);
|
||||
border-radius: 1rem;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 1),
|
||||
0 2px 6px rgba(148, 163, 184, 0.1),
|
||||
0 8px 24px rgba(148, 163, 184, 0.08);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: #18314f;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.panel-subtitle {
|
||||
color: #7d94ac;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Chart */
|
||||
|
||||
.chart-panel {
|
||||
/* column flex already set by .panel */
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
#chart {
|
||||
width: 100%;
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.chart-x-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.6rem;
|
||||
color: #94a3b8;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.75rem;
|
||||
color: #5d7088;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.legend-dot-color2 { background: rgba(237, 126, 23, 1); }
|
||||
.legend-dot-color1 { background: rgba(5, 141, 199, 1) }
|
||||
|
||||
/* Controls */
|
||||
|
||||
.controls-panel {
|
||||
/* column flex already set by .panel */
|
||||
}
|
||||
|
||||
.controls-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid #e8eef5;
|
||||
}
|
||||
|
||||
.control-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.control-name {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
color: #18314f;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.control-desc {
|
||||
font-size: 0.75rem;
|
||||
color: #7d94ac;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
|
||||
.toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 3rem;
|
||||
height: 1.65rem;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-track {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 999px;
|
||||
background: #cbd5e1;
|
||||
transition: background 0.22s ease;
|
||||
}
|
||||
|
||||
.toggle-track::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
top: 50%;
|
||||
left: 0.2rem;
|
||||
transform: translateY(-50%);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);
|
||||
transition: left 0.22s ease;
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-track {
|
||||
background: linear-gradient(135deg, #10b981, #06b6d4);
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-track::before {
|
||||
left: calc(100% - 1.45rem);
|
||||
}
|
||||
|
||||
.toggle input:focus-visible + .toggle-track {
|
||||
outline: 2px solid #0ea5e9;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.detail-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.overview-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! https://github.com/leeoniya/uPlot (v1.6.32) */
|
||||
var uPlot=function(){"use strict";const l="u-off",e="u-label",t="width",n="height",i="top",o="bottom",s="left",r="right",u="#000",a=u+"0",f="mousemove",c="mousedown",h="mouseup",d="mouseenter",p="mouseleave",m="dblclick",g="change",x="dppxchange",w="--",_="undefined"!=typeof window,b=_?document:null,v=_?window:null,k=_?navigator:null;let y,M;function S(l,e){if(null!=e){let t=l.classList;!t.contains(e)&&t.add(e)}}function T(l,e){let t=l.classList;t.contains(e)&&t.remove(e)}function E(l,e,t){l.style[e]=t+"px"}function z(l,e,t,n){let i=b.createElement(l);return null!=e&&S(i,e),null!=t&&t.insertBefore(i,n),i}function D(l,e){return z("div",l,e)}const P=new WeakMap;function A(e,t,n,i,o){let s="translate("+t+"px,"+n+"px)";s!=P.get(e)&&(e.style.transform=s,P.set(e,s),0>t||0>n||t>i||n>o?S(e,l):T(e,l))}const W=new WeakMap;function Y(l,e,t){let n=e+t;n!=W.get(l)&&(W.set(l,n),l.style.background=e,l.style.borderColor=t)}const C=new WeakMap;function H(l,e,t,n){let i=e+""+t;i!=C.get(l)&&(C.set(l,i),l.style.height=t+"px",l.style.width=e+"px",l.style.marginLeft=n?-e/2+"px":0,l.style.marginTop=n?-t/2+"px":0)}const F={passive:!0},R={...F,capture:!0};function G(l,e,t,n){e.addEventListener(l,t,n?R:F)}function I(l,e,t){e.removeEventListener(l,t,F)}function L(l,e,t,n){let i;t=t||0;let o=2147483647>=(n=n||e.length-1);for(;n-t>1;)i=o?t+n>>1:sl((t+n)/2),l>e[i]?t=i:n=i;return l-e[t]>e[n]-l?n:t}function O(l){return(e,t,n)=>{let i=-1,o=-1;for(let o=t;n>=o;o++)if(l(e[o])){i=o;break}for(let i=n;i>=t;i--)if(l(e[i])){o=i;break}return[i,o]}}_&&function l(){let e=devicePixelRatio;y!=e&&(y=e,M&&I(g,M,l),M=matchMedia(`(min-resolution: ${y-.001}dppx) and (max-resolution: ${y+.001}dppx)`),G(g,M,l),v.dispatchEvent(new CustomEvent(x)))}();const N=l=>null!=l,j=l=>null!=l&&l>0,U=O(N),V=O(j);function B(l,e,t,n){let i=hl(l),o=hl(e);l==e&&(-1==i?(l*=t,e/=t):(l/=t,e*=t));let s=10==t?dl:pl,r=1==o?ul:sl,u=(1==i?sl:ul)(s(ol(l))),a=r(s(ol(e))),f=cl(t,u),c=cl(t,a);return 10==t&&(0>u&&(f=Al(f,-u)),0>a&&(c=Al(c,-a))),n||2==t?(l=f*i,e=c*o):(l=Pl(l,f),e=Dl(e,c)),[l,e]}function $(l,e,t,n){let i=B(l,e,t,n);return 0==l&&(i[0]=0),0==e&&(i[1]=0),i}const J=.1,q={mode:3,pad:J},K={pad:0,soft:null,mode:0},X={min:K,max:K};function Z(l,e,t,n){return Ol(t)?ll(l,e,t):(K.pad=t,K.soft=n?0:null,K.mode=n?3:0,ll(l,e,X))}function Q(l,e){return null==l?e:l}function ll(l,e,t){let n=t.min,i=t.max,o=Q(n.pad,0),s=Q(i.pad,0),r=Q(n.hard,-gl),u=Q(i.hard,gl),a=Q(n.soft,gl),f=Q(i.soft,-gl),c=Q(n.mode,0),h=Q(i.mode,0),d=e-l,p=dl(d),m=fl(ol(l),ol(e)),g=dl(m),x=ol(g-p);(1e-24>d||x>10)&&(d=0,0!=l&&0!=e||(d=1e-24,2==c&&a!=gl&&(o=0),2==h&&f!=-gl&&(s=0)));let w=d||m||1e3,_=dl(w),b=cl(10,sl(_)),v=Al(Pl(l-w*(0==d?0==l?.1:1:o),b/10),24),k=a>l||1!=c&&(3!=c||v>a)&&(2!=c||a>v)?gl:a,y=fl(r,k>v&&l>=k?k:al(k,v)),M=Al(Dl(e+w*(0==d?0==e?.1:1:s),b/10),24),S=e>f||1!=h&&(3!=h||f>M)&&(2!=h||M>f)?-gl:f,T=al(u,M>S&&S>=e?S:fl(S,M));return y==T&&0==y&&(T=100),[y,T]}const el=new Intl.NumberFormat(_?k.language:"en-US"),tl=l=>el.format(l),nl=Math,il=nl.PI,ol=nl.abs,sl=nl.floor,rl=nl.round,ul=nl.ceil,al=nl.min,fl=nl.max,cl=nl.pow,hl=nl.sign,dl=nl.log10,pl=nl.log2,ml=(l,e=1)=>nl.asinh(l/e),gl=1/0;function xl(l){return 1+(0|dl((l^l>>31)-(l>>31)))}function wl(l,e,t){return al(fl(l,e),t)}function _l(l){return"function"==typeof l}function bl(l){return _l(l)?l:()=>l}const vl=l=>l,kl=(l,e)=>e,yl=()=>null,Ml=()=>!0,Sl=(l,e)=>l==e,Tl=/\.\d*?(?=9{6,}|0{6,})/gm,El=l=>{if(Il(l)||Wl.has(l))return l;const e=""+l,t=e.match(Tl);if(null==t)return l;let n=t[0].length-1;if(-1!=e.indexOf("e-")){let[l,t]=e.split("e");return+`${El(l)}e${t}`}return Al(l,n)};function zl(l,e){return El(Al(El(l/e))*e)}function Dl(l,e){return El(ul(El(l/e))*e)}function Pl(l,e){return El(sl(El(l/e))*e)}function Al(l,e=0){if(Il(l))return l;let t=10**e;return rl(l*t*(1+Number.EPSILON))/t}const Wl=new Map;function Yl(l){return((""+l).split(".")[1]||"").length}function Cl(l,e,t,n){let i=[],o=n.map(Yl);for(let s=e;t>s;s++){let e=ol(s),t=Al(cl(l,s),e);for(let r=0;n.length>r;r++){let u=10==l?+`${n[r]}e${s}`:n[r]*t,a=(0>s?e:0)+(o[r]>s?o[r]:0),f=10==l?u:Al(u,a);i.push(f),Wl.set(f,a)}}return i}const Hl={},Fl=[],Rl=[null,null],Gl=Array.isArray,Il=Number.isInteger;function Ll(l){return"string"==typeof l}function Ol(l){let e=!1;if(null!=l){let t=l.constructor;e=null==t||t==Object}return e}function Nl(l){return null!=l&&"object"==typeof l}const jl=Object.getPrototypeOf(Uint8Array),Ul="__proto__";function Vl(l,e=Ol){let t;if(Gl(l)){let n=l.find((l=>null!=l));if(Gl(n)||e(n)){t=Array(l.length);for(let n=0;l.length>n;n++)t[n]=Vl(l[n],e)}else t=l.slice()}else if(l instanceof jl)t=l.slice();else if(e(l)){t={};for(let n in l)n!=Ul&&(t[n]=Vl(l[n],e))}else t=l;return t}function Bl(l){let e=arguments;for(let t=1;e.length>t;t++){let n=e[t];for(let e in n)e!=Ul&&(Ol(l[e])?Bl(l[e],Vl(n[e])):l[e]=Vl(n[e]))}return l}function $l(l,e,t){for(let n,i=0,o=-1;e.length>i;i++){let s=e[i];if(s>o){for(n=s-1;n>=0&&null==l[n];)l[n--]=null;for(n=s+1;t>n&&null==l[n];)l[o=n++]=null}}}const Jl="undefined"==Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
|
||||
Reference in new issue
Block a user