Skip to content

Commit

Permalink
feat: highlight the metric close to mouse pointer
Browse files Browse the repository at this point in the history
  • Loading branch information
bmario committed Oct 4, 2024
1 parent d72d4c3 commit 4fd7c6b
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 24 deletions.
34 changes: 24 additions & 10 deletions src/graticule.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,15 +432,16 @@ export class Graticule {
}

getTimeValueAtPoint (positionArr) {
const relationalPos = [positionArr[0] - this.dimensions.x, positionArr[1] - this.dimensions.y]
const x = positionArr[0] - this.dimensions.x
const y = positionArr[1] - this.dimensions.y
if (undefined !== this.curTimeRange &&
undefined !== this.curValueRange &&
relationalPos[0] >= 0 &&
relationalPos[0] <= this.dimensions.width &&
relationalPos[1] >= 0 &&
relationalPos[1] <= this.dimensions.height) {
return [Math.round((relationalPos[0] * this.curTimePerPixel) + this.curTimeRange[0]),
((this.dimensions.height - relationalPos[1]) * this.curValuesPerPixel) + this.curValueRange[0]
x >= 0 &&
x <= this.dimensions.width &&
y >= 0 &&
y <= this.dimensions.height) {
return [Math.round((x * this.curTimePerPixel) + this.curTimeRange[0]),
((this.dimensions.height - y) * this.curValuesPerPixel) + this.curValueRange[0]
]
} else {
return undefined
Expand Down Expand Up @@ -757,19 +758,20 @@ export class Graticule {
}

drawBands (ctx, timePerPixel, valuesPerPixel, graticuleDimensions) {
const peakedMetric = store.getters['metrics/getPeakedMetric']()
for (const metric of this.data.metrics) {
const drawState = store.getters['metrics/getMetricDrawState'](metric.name)
if (!drawState.draw) continue

if (drawState.drawMin && drawState.drawMax) {
this.drawBand(ctx, timePerPixel, valuesPerPixel, graticuleDimensions, metric)
this.drawBand(ctx, timePerPixel, valuesPerPixel, graticuleDimensions, metric, peakedMetric)
}
}

this.resetCtx(ctx)
}

drawBand (ctx, timePerPixel, valuesPerPixel, graticuleDimensions, metric) {
drawBand (ctx, timePerPixel, valuesPerPixel, graticuleDimensions, metric, peakedMetric) {
const band = metric.band
if (band) {
const styleOptions = this.parseStyleOptions(band.styleOptions, ctx)
Expand Down Expand Up @@ -817,11 +819,17 @@ export class Graticule {
previousY = y
}
ctx.closePath()
if (metric.name === peakedMetric) {
ctx.lineWidth = 2
ctx.globalAlpha = 0.5
ctx.stroke()
}
ctx.fill()
}
}

drawSeries (timeRange, valueRange, timePerPixel, valuesPerPixel, ctx, graticuleDimensions) {
const peakedMetric = store.getters['metrics/getPeakedMetric']()
for (let i = 0; i < this.data.metrics.length; ++i) {
if (!store.getters['metrics/getMetricDrawState'](this.data.metrics[i].name).draw) continue

Expand All @@ -846,12 +854,18 @@ export class Graticule {

for (const curAggregate in this.data.metrics[i].series) {
const curSeries = this.data.metrics[i].series[curAggregate]

if (curSeries) {
const styleOptions = this.parseStyleOptions(curSeries.styleOptions, ctx)
const styleOptions = { ...this.parseStyleOptions(curSeries.styleOptions, ctx) }
if (styleOptions.skip || curSeries.points.length === 0) {
this.resetCtx(ctx)
continue
}

if (peakedMetric === this.data.metrics[i].name) {
styleOptions.pointWidth = 10
}

const offsiteCanvas = this.generateOffsiteDot(styleOptions)

for (let j = 0, x, y, previousX, previousY; j < curSeries.points.length; ++j) {
Expand Down
64 changes: 52 additions & 12 deletions src/interact.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function uiInteractZoomArea (evtObj) {
function uiInteractZoomIn (evtObj) {
evtObj.preventDefault()
const relativeStart = mouseDown.relativeStartPos
const relativeEnd = calculateActualMousePos(evtObj)
const relativeEnd = calculateActualMousePos(evtObj, window.MetricQWebView.graticule.ele)

relativeEnd[0] = Math.max(window.MetricQWebView.graticule.dimensions.x,
Math.min(Math.abs(relativeEnd[0]), window.MetricQWebView.graticule.dimensions.width))
Expand Down Expand Up @@ -127,7 +127,7 @@ function uiInteractZoomWheel (evtObj) {
scrollDirection = 0.2
}
scrollDirection *= store.state.configuration.zoomSpeed / 10
const curPos = calculateActualMousePos(evtObj)
const curPos = calculateActualMousePos(evtObj, window.MetricQWebView.graticule.ele)
const curTimeValue = window.MetricQWebView.graticule.getTimeValueAtPoint(curPos)
if (curTimeValue) {
if (!window.MetricQWebView.handler.zoomTimeAtPoint(curTimeValue, scrollDirection)) {
Expand All @@ -140,26 +140,34 @@ function uiInteractZoomWheel (evtObj) {
}

function uiInteractLegend (evtObj) {
const curPosOnCanvas = calculateActualMousePos(evtObj)
const curPosOnCanvas = calculateActualMousePos(evtObj, window.MetricQWebView.graticule.ele)
const curPoint = window.MetricQWebView.graticule.getTimeValueAtPoint(curPosOnCanvas)

if (!curPoint) {
return
}

const timeAt = curPoint[0]
const valueAt = curPoint[1]

window.MetricQWebView.graticule.draw(false)

const myCtx = window.MetricQWebView.graticule.ctx
myCtx.fillStyle = 'rgba(0,0,0,0.8)'
myCtx.fillRect(curPosOnCanvas[0] - 1, window.MetricQWebView.graticule.dimensions.y, 2, window.MetricQWebView.graticule.dimensions.height)
// myCtx.fillRect(window.MetricQWebView.graticule.dimensions.x, curPosOnCanvas[1] - 1, window.MetricQWebView.graticule.dimensions.width, 2)
myCtx.font = '14px ' + window.MetricQWebView.graticule.DEFAULT_FONT // actually it's sans-serif

const legendEntries = []
let maxLabelWidth = 0
let maxTextWidth = 0

let closestMetric
let closestMetricValue

const range = window.MetricQWebView.graticule.curValueRange
const maxDistance = 0.05 * (range[1] - range[0])

for (const metric of Object.values(window.MetricQWebView.graticule.data.metrics)) {
const metricDrawState = store.getters['metrics/getMetricDrawState'](metric.name)

Expand All @@ -170,6 +178,16 @@ function uiInteractLegend (evtObj) {
const value = metric.series.raw.getValueAtTimeAndIndex(timeAt)
if (value === undefined) continue

if (
Math.abs(valueAt - value[1]) < maxDistance &&
(
closestMetric === undefined ||
Math.abs(value[1] - valueAt) < Math.abs(closestMetricValue - valueAt)
)
) {
closestMetric = metric.name
closestMetricValue = value[1]
}
curText = (Number(value[1])).toFixed(3)
} else if (metric.series.min !== undefined &&
metric.series.max !== undefined &&
Expand All @@ -179,6 +197,19 @@ function uiInteractLegend (evtObj) {
const avg = metric.series.avg.getValueAtTimeAndIndex(timeAt)
if (min === undefined || max === undefined || avg === undefined) continue

if (
(valueAt < max[1] && valueAt > min[1] && metricDrawState.drawMin && metricDrawState.drawMax) ||
maxDistance > Math.abs(valueAt - avg[1])
) {
if (
closestMetric === undefined ||
Math.abs(avg[1] - valueAt) < Math.abs(closestMetricValue - valueAt)
) {
closestMetric = metric.name
closestMetricValue = avg[1]
}
}

curText = ''
if (metricDrawState.drawMin) {
curText += '▼ ' + (Number(min[1])).toFixed(3) + ' | '
Expand Down Expand Up @@ -217,6 +248,8 @@ function uiInteractLegend (evtObj) {
legendEntries.push(newEntry)
}

store.dispatch('metrics/updatePeakedMetric', { metric: closestMetric })

let timeString = new Date(curPoint[0]).toLocaleString()

if (window.MetricQWebView.graticule.curTimeRange !== undefined) {
Expand Down Expand Up @@ -251,7 +284,7 @@ function uiInteractLegend (evtObj) {
}

drawHoverDate(myCtx, timeString, curPosOnCanvas[0], maxLabelWidth, offsetTop, offsetMid, verticalDiff, distanceToRightEdge)
drawHoverText(myCtx, legendEntries, curPosOnCanvas[0], maxTextWidth, maxLabelWidth, offsetTop, offsetMid, verticalDiff, borderPadding, distanceToRightEdge)
drawHoverText(myCtx, legendEntries, curPosOnCanvas[0], maxTextWidth, maxLabelWidth, offsetTop, offsetMid, verticalDiff, borderPadding, distanceToRightEdge, closestMetric)
}

function drawHoverDate (myCtx, timeString, curXPosOnCanvas, maxNameWidth, offsetTop, offsetMid, verticalDiff, distanceToRightEdge) {
Expand All @@ -265,7 +298,7 @@ function drawHoverDate (myCtx, timeString, curXPosOnCanvas, maxNameWidth, offset
myCtx.fillText(timeString, curXPosOnCanvas + offsetMid, offsetTop - 0.5 * verticalDiff)
}

function drawHoverText (myCtx, metricsArray, curXPosOnCanvas, maxValueWidth, maxLabelWidth, offsetTop, offsetMid, verticalDiff, borderPadding, distanceToRightEdge) {
function drawHoverText (myCtx, metricsArray, curXPosOnCanvas, maxValueWidth, maxLabelWidth, offsetTop, offsetMid, verticalDiff, borderPadding, distanceToRightEdge, peakedMetric) {
myCtx.textBaseline = 'middle'
myCtx.textAlign = 'left'
let offsetRight = 0
Expand All @@ -279,9 +312,18 @@ function drawHoverText (myCtx, metricsArray, curXPosOnCanvas, maxValueWidth, max
for (let i = 0; i < metricsArray.length; ++i) {
const y = offsetTop + i * verticalDiff
myCtx.fillStyle = metricsArray[i].metric.color
myCtx.globalAlpha = 0.4
if (metricsArray[i].metric.name === peakedMetric) {
myCtx.globalAlpha = 0.8
} else {
myCtx.globalAlpha = 0.4
}
myCtx.fillRect(curXPosOnCanvas + offsetMid - offsetRight - borderPadding, y, maxValueWidth + maxLabelWidth + (offsetMid + borderPadding) * 2, 20)
myCtx.fillStyle = '#000000'
if (metricsArray[i].metric.name === peakedMetric) {
myCtx.lineWidth = 2
myCtx.color = '#000000'
myCtx.strokeRect(curXPosOnCanvas + offsetMid - offsetRight - borderPadding, y, maxValueWidth + maxLabelWidth + (offsetMid + borderPadding) * 2, 20)
}
myCtx.globalAlpha = 1
myCtx.fillText(metricsArray[i].curText, curXPosOnCanvas + offsetMid - offsetRight, y + 0.5 * verticalDiff)
myCtx.fillText(metricsArray[i].label, curXPosOnCanvas + (offsetMid * 3 + maxValueWidth) - offsetRight, y + 0.5 * verticalDiff)
Expand Down Expand Up @@ -345,12 +387,10 @@ export function registerCallbacks (anchoringObject) {
})
}

function calculateActualMousePos (evtObj) {
const curPos = [evtObj.x - 3 * evtObj.target.offsetLeft,
evtObj.y - evtObj.target.offsetTop]
const scrollOffset = calculateScrollOffset(evtObj.target)
curPos[0] += scrollOffset[0]
curPos[1] += scrollOffset[1]
function calculateActualMousePos (evtObj, target) {
if (target === undefined) target = evtObj.target
const box = target.getBoundingClientRect()
const curPos = [evtObj.x - box.left, evtObj.y - box.top]
return curPos
}

Expand Down
16 changes: 15 additions & 1 deletion src/store/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import * as Error from '@/errors'
export default {
namespaced: true,
state: {
metrics: {}
metrics: {},
peakedMetric: undefined
},
getters: {
getMetricDrawState: (state) => (metricName) => {
Expand Down Expand Up @@ -45,6 +46,9 @@ export default {
},
getUnit: (state) => (metric) => {
return state.metrics[metric].unit
},
getPeakedMetric: (state) => () => {
return state.peakedMetric
}
},
mutations: {
Expand Down Expand Up @@ -121,7 +125,14 @@ export default {
} else {
throw new Error('Metric not found!')
}
},

setPeakedMetric (state, { metric }) {
if (state.metrics[metric] || metric === undefined) {
Vue.set(state, 'peakedMetric', metric)
}
}

},
actions: {
checkGlobalDrawState ({ commit, getters }) {
Expand Down Expand Up @@ -248,6 +259,9 @@ export default {
},
updateDataPoints ({ state, commit }, { metricKey, pointsAgg, pointsRaw }) {
commit('privateSet', { metricKey, metric: { pointsAgg: pointsAgg, pointsRaw: pointsRaw } })
},
updatePeakedMetric ({ commit }, { metric }) {
commit('setPeakedMetric', { metric })
}
},
modules: {}
Expand Down
11 changes: 10 additions & 1 deletion src/ui/metric-legend.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<td>
<span
ref="metricName"
class="metricText"
:class="['metricText', peakedClass]"
>
<span
v-if="metric.factor !== 1"
Expand Down Expand Up @@ -103,6 +103,11 @@ export default {
},
color () {
return this.$props.metric.draw ? this.$props.metric.color : 'grey'
},
peakedClass () {
if (this.$store.getters['metrics/getPeakedMetric']() === this.metric.key) return 'peaked'
return undefined
}
},
updated () {
Expand Down Expand Up @@ -172,4 +177,8 @@ table {
position: relative;
top: 3px;
}
.peaked {
font-weight: bold;
}
</style>

0 comments on commit 4fd7c6b

Please sign in to comment.