Wont be long (they are in labs): Sparklines — Vuetify
The space between them is the gap what is defined by dashboard.
Widget takes 100% available space.
If you don't want to mess with provided styles for all gauges in all situations but just in this card or dedicated widgets only - add a class to them to override appearance of widget
.my-level .led-level{
padding-inline:5px;
}
where my-level
is that classname you wrote where you wont it to be applied.
Otherwise to add left and right padding for all of them
.led-level{
padding-inline:5px;
}
Whole widget can be made responsive by all means but only for dedicated use cases. It takes to break the layout at those exact sizes you as the creator of the page will define.
The responsiveness of DB2 is not magical resolver of every possible use cases. It still requires to make some compromises like placing and sizing your widgets so that they look OK on all your screens. That means you can't have that many widgets in one row if they don't fit with the smallest screen you are using.
But saying so - you have the source code of the level - you can make it to be multiple of them in one container which you can control and then make your own placement/sizing rules inside that container so it fits always.
8 posts were split to a new topic: Dash 2.0 Sparkline
That's really cool.
Does anyone has an idea how to implement this in Grafana, too ?
Hi @Giamma,
I had the same doubt as you last week, because I hadn't seen it before in my life. It is used for optional chaining.
Bart
Ok, thank you ....
Now that newer versions of node.js, it is safe to use that in runtime code.
However, you probably shouldn't use it for front-end browser code since it only started being supported early 2020. iOS support came with v13.4 in March 2020. So if there is a chance that your users might have older devices, best avoided.
I target my front-end code for early 2019 as a rule as this seems to cover all but the oldest devices. One way around this issue is to use a build-tool such as ESBUILD to produce production, minified versions of front-end code. It lets you write current generation code but can build to any previous JavaScript or browser version.
Thank you ...
I was kindly asked for a bit redesign for horizontal level (add icon, spread dots evenly, make it more responsive, get rid of filter usage ...) A bit dedicated design or so. But I'll share anyway
It uses container query's for responsiveness. Those are set to solid values so the break points may need adjustments if you plan to use it on your dashboard. (break points can't be dynamically adjustable)
Code
<template>
<div class="led-level">
<div :class="icon ? 'led-level-grid-2':'led-level-grid-1'">
<div v-if="icon" class="led-level-icon">
<v-icon aria-hidden="false">{{icon}}</v-icon>
</div>
<div class="led-level-content">
<div class="led-level-text">
<span class="led-level-label">{{label}}</span>
<span class="led-level-value">{{formattedValue}}<span class="led-level-unit">{{unit}}</span></span>
</div>
<div class="led-level-stripe">
<div v-for="(color, index) in colors" :key="index" class="led-level-led" :ref="'dot-' + index"></div>
</div>
<div class="led-level-limits">
<span class="led-level-limit">{{min}}</span>
<span v-for="(tick, index) in ticks" :key="index" class="led-level-limit" :ref="'tick-' + index">{{tick}}</span>
<span class="led-level-limit">{{max}}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
//Define me here
label:"Level long name", // The label
icon:"mdi-account", //mdi-account (optional) the icon
min:0, // Smallest expected value
max:100, // Highest expected value
unit:"cm³",// The unit of the measurement
dim:0.2, //How dim is led when not glowing
animate:true, // Animating led's is not most performant thing in the world.
colors:[
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#00e300",
"#ffa916",
"#ffa916",
"#ffa916",
"#ff4c16",
"#ff4c16"],
ticks:[50,75], //optional tick values
//no need to change those
value:0,
previousValue:0,
inited:false
}
},
methods: {
getElement: function(name,base){
if(base){
return this.$refs[name]
}
return this.$refs[name][0]
},
validate(data){
let ret
if(typeof data !== "number"){
ret = parseFloat(data)
if(isNaN(ret)){
console.log("BAD DATA! id:",this.id,"data:",data)
return null
}
}
else{
ret = data
}
return ret
},
full: function(){
return Math.floor(this.colors.length*this.percentage/100)
},
half: function (){
let p = this.colors.length*this.percentage/100;
p -= this.full()
p *= .5
p += this.dim;
return p;
},
tickPosition: function(tv){
return Math.floor(((tv - this.min) / (this.max - this.min)) * 100)+'%';
},
lit: function(){
if(this.inited == false){
return
}
const down = this.previousValue > this.value
let time = down ? 1 : 0.2
let step = down ? 0.12 : 0.06
this.colors.forEach((color,i) => {
let dot = this.getElement("dot-"+i);
if(!dot){
console.log("lit() no dots found")
return
}
if(i<this.full()){
dot.style.opacity = 1;
time += step
}
else if(i==this.full()){
dot.style.opacity = this.half()
}
else{
dot.style.opacity = this.dim
}
if(down){
time -= step
}
else{
time -= step
}
dot.style.transition = this.animate && time > 0 ? "opacity "+time+"s" : "unset";
})
this.previousValue = this.value
}
},
watch: {
msg: function(){
if(this.msg.payload !== undefined){
const v = this.validate(this.msg.payload)
if(v === null){
return
}
this.value = v
this.lit()
}
}
},
computed: {
formattedValue: function () {
return this.value.toFixed(2)
},
percentage: function(){
return Math.floor(((this.value - this.min) / (this.max - this.min)) * 100);
}
},
mounted(){
this.colors.forEach((c,i) => {
let dot = this.getElement("dot-"+i);
if(!dot){
return
}
dot.style.backgroundColor = c
}
);
this.ticks.forEach((t,i) => {
let tick = this.getElement("tick-"+i);
if(!tick){
return
}
tick.style.left = this.tickPosition(t)
}
);
this.inited = true;
},
unmounted () {
}
}
</script>
CSS
.led-level{
container-type: inline-size;
container-name: led-level;
--break-point:160;
}
.led-level-grid-2 {
display:grid;
grid-template-columns: 2rem auto;
gap:1rem;
height: 100%;
}
.led-level-grid-1 {
display:grid;
grid-template-columns: 1fr;
gap:1rem;
height: 100%;
}
@container led-level (max-width: 160px) {
.led-level-grid-2{
grid-template-columns: 1rem auto;
}
.led-level-icon .v-icon{
font-size: calc(var(--v-icon-size-multiplier)* 2em) !important;
}
}
.led-level-icon{
display:flex;
justify-content: center;
align-items: center;
}
.led-level-icon .v-icon{
font-size: calc(var(--v-icon-size-multiplier)* 3em);
}
.led-level-content {
container-type: inline-size;
container-name: level-content;
display: grid;
grid-template-rows: 1.3em minmax(3px, 1fr) .7em;
gap: 2px;
height: 100%;
}
.led-level-text {
font-size: 1.25em;
line-height: 1em;
align-self: end;
display: flex;
justify-content: space-between;
user-select: none;
overflow: auto;
}
.led-level-label{
font-size: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.led-level-value {
font-weight: bold;
}
.led-level-unit {
font-size: .75em;
font-weight: normal;
padding-inline-start: 0.15em;
}
.led-level-stripe {
display: flex;
}
.led-level-led {
background: #ffffff;
width: 100%;
height: 100%;
border-left: 1px solid rgb(var(--v-theme-group-background));
border-right: 1px solid rgb(var(--v-theme-group-background));
border-radius: 0px;
}
@container level-content (max-width: 150px) {
.led-level-led:nth-child(even){
display:none;
}
.led-level-limit:not(:first-child):not(:last-child){
display:none;
}
}
@container level-content (max-width: 130px) {
.led-level-label{
display:none;
}
.led-level-text{
justify-content:flex-end;
}
}
.led-level-limits {
display: block;
position: relative;
font-size: .75em;
line-height: 1;
user-select: none;
}
.led-level-limit{
position:absolute;
transform:translate(-50%,0);
}
.led-level-limit:first-child{
left:0;
transform: translate(0, 0);
}
.led-level-limit:last-child{
right:0;
transform: translate(0, 0);
}