Source: components/compass.vue

<template>
    <div>
        <!-- <div style="position: relative;">
            <canvas id="compass" width="200" height="60" style="background-color: transparent; z-index: 1;"></canvas>
            <input v-model="value"
                style="text-align:center; position: absolute; z-index: 2; width: 30px; left: 85px; top: 40px;">
            <q-btn v-if="oldValue != value" dense flat @click="save" color="Positive" :label="$t('Save')"
                style="text-align:center; position: absolute; z-index: 2; width: 30px; left: 125px; top: 37px;" />
        </div> -->
        <div class="container">
            <canvas id="compass" width="200" height="60"></canvas>
            <input v-model="value" class="input-field" type="text" pattern="[0-9]*"/>
            <q-btn v-if="oldValue != value" dense flat @click="save" color="Positive" :label="$t('Save')"
                class="button" />
        </div>
    </div>
</template>
<script>
export default {
    name: "Compass",
    props: {
        modelValue: null,
        id: null
    },
    data() {
        return {
            value: 0,
            oldValue: 0,
            sides: {
                "0": "N",
                "22": "NNE",
                "45": "NE",
                "67": "ENE",
                "90": "E",
                "112": "ESE",
                "135": "SE",
                "157": "SSE",
                "180": "S",
                "202": "SSW",
                "225": "SW",
                "247": "WSW",
                "270": "W",
                "292": "WNW",
                "315": "NW",
                "337": "NNW"
            }
        }
    },
    watch: {
        value: function (val) {
            //this.$emit('update:modelValue', val);
            this.value = val % 360;
            this.drawCompass(val);
        }
    },
    mounted() {
        this.value = this.modelValue;
        this.oldValue = this.modelValue;
        this.drawCompass(this.modelValue);
    },
    methods: {
        save() {
            this.put("User/UpdateCompass/" + this.id + "/" + this.value);
            this.$emit('update:modelValue', this.value);
            this.oldValue = this.value;
        },
        drawCompass: function (angle) {
            var canvas = document.getElementById('compass');
            var ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.save();
            const delta = 30;
            for (let i = angle - delta; i <= angle + delta; i++) {
                let j = i % 360;
                if (j < 0) j += 360;
                let x = (i - (angle - delta)) * 200 / 60;
                ctx.beginPath();
                ctx.strokeStyle = "black";
                ctx.font = "10px Arial";
                ctx.lineWidth = 1;
                if (this.sides[j]) {
                    //calc width of text
                    let width = ctx.measureText(this.sides[j]).width;
                    ctx.fillText(this.sides[j], x - width / 2, 10);
                }
                if (j % 10 == 0) {
                    ctx.moveTo(x, 15);
                    ctx.lineTo(x, 25);
                    let width = ctx.measureText(j).width;
                    ctx.fillText(j, x - width / 2, 35);
                } else if (j % 5 == 0) {
                    ctx.moveTo(x, 15);
                    ctx.lineTo(x, 20);
                }
                // if (i == angle) {
                //     ctx.font = "15px Arial";
                //     ctx.lineWidth = 3;
                //     ctx.moveTo(x, 15);
                //     ctx.lineTo(x, 40);
                //     let width = ctx.measureText(j).width;
                //     ctx.fillText(j, x - width / 2, 55);
                // }
                ctx.stroke();
            }

            ctx.restore();
        }
    }
}
</script>
<style scoped>
.container {
    position: relative;
}

.input-field {
    text-align: center;
    position: absolute;
    width: 36px;
    left: 82px;
    top: 40px;
}

.input-field::-webkit-inner-spin-button,
.input-field::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.button {
    position: absolute;
    width: 30px;
    left: 125px;
    top: 37px;
}
</style>