import { Component, ComponentNature, POSITION, Properties, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene' import { themesColorRange } from '@fmsim/api' import { MCSStatusMixin } from './features/mcs-status-mixin' import { LEGEND_CAPACITY, Legend } from './features/mcs-status-default' import { safeRound } from './utils/safe-round' import { getValueOnRanges } from './utils/get-value-on-ranges' import { ParentObjectMixin, ParentObjectMixinProperties } from './features/parent-object-mixin' const NATURE: ComponentNature = { mutable: false, resizable: true, rotatable: true, properties: [ ...ParentObjectMixinProperties, { type: 'select', label: 'legend-name', name: 'legendName', property: { options: themesColorRange } }, { type: 'number', name: 'currentUsage', label: 'current-usage' }, { type: 'number', name: 'highWatermark', label: 'high-watermark' }, { type: 'number', name: 'lowWatermark', label: 'low-watermark' }, { type: 'number', name: 'maxCapacity', label: 'max-capacity' }, { type: 'boolean', name: 'fullup', label: 'fullup' }, { type: 'number', label: 'round', name: 'round', property: { min: 0 } }, { type: 'hidden', name: 'usage', label: 'usage' } ], 'value-property': 'usage' } var controlHandler = { ondragmove: function (point: POSITION, index: number, component: Component) { var { left, top, width, height } = component.model var transcoorded = component.transcoordP2S(point.x, point.y) var round = ((transcoorded.x - left) / (width / 2)) * 100 round = safeRound(round, width, height) component.set({ round }) } } @sceneComponent('StockerCapacityBar') @sceneComponent('MCSGaugeCapacityBar') export default class StockerCapacityBar extends ParentObjectMixin(MCSStatusMixin(RectPath(Shape))) { static get nature() { return NATURE } get controls() { var { left, top, width, round, height } = this.state round = round == undefined ? 0 : safeRound(round, width, height) return [ { x: left + (width / 2) * (round / 100), y: top, handler: controlHandler } ] } isIdentifiable() { return false } getTheme() { const { legendName } = this.state if (legendName) { return (this.root as any)?.style?.[legendName] } } get legend(): Legend { return this.getTheme() || this.getLegendFallback() } getLegendFallback() { return (this.root as any).stockerCapacityLegendTheme || LEGEND_CAPACITY } get statusColor() { const { fullup } = this.state return fullup ? 'red' : getValueOnRanges(this.usage, this.legend) } get text() { return this.usage + '%' } render(context: CanvasRenderingContext2D) { const { left, top, width, height } = this.bounds if (width >= height) { this.renderLandscape(context) } else { this.renderPortrait(context) } } renderLandscape(context: CanvasRenderingContext2D) { const { left, top, width, height } = this.bounds const { highWatermark, lowWatermark, lineWidth, strokeStyle, round, fillStyle = 'white' } = this.state context.save() context.translate(left, top) // gauge background context.beginPath() context.fillStyle = fillStyle context.roundRect(0, 0, width, height, round) context.fill() context.clip() // gauge foreground context.beginPath() context.fillStyle = this.statusColor! context.rect(0, 0, (width / 100) * this.usage, height) context.fill() // high-watermark level if (highWatermark && highWatermark > 0 && highWatermark < 100) { context.beginPath() context.strokeStyle = 'black' context.lineWidth = 1 context.setLineDash([3]) const x = (width / 100) * highWatermark context.moveTo(x, 0) context.lineTo(x, height) context.stroke() } // low-watermark level if (lowWatermark && lowWatermark > 0 && lowWatermark < 100) { context.beginPath() context.strokeStyle = 'black' context.lineWidth = 1 context.setLineDash([3]) const x = (width / 100) * lowWatermark context.moveTo(x, 0) context.lineTo(x, height) context.stroke() } // gauge outline context.beginPath() context.setLineDash([]) context.strokeStyle = strokeStyle context.lineWidth = lineWidth context.roundRect(0, 0, width, height, round) context.stroke() context.beginPath() context.translate(-left, -top) context.restore() } renderPortrait(context: CanvasRenderingContext2D) { const { left, top, width, height } = this.bounds const { highWatermark, lowWatermark, lineWidth, strokeStyle, round, fillStyle = 'white' } = this.state context.save() context.translate(left, top) // gauge background context.beginPath() context.fillStyle = fillStyle context.roundRect(0, 0, width, height, round) context.fill() context.clip() // gauge foreground context.beginPath() context.fillStyle = this.statusColor! context.rect(0, height - (height / 100) * this.usage, width, (height / 100) * this.usage) context.fill() // high-watermark level if (highWatermark && highWatermark > 0 && highWatermark < 100) { context.beginPath() context.strokeStyle = 'black' context.lineWidth = 1 context.setLineDash([3]) const y = height - (height / 100) * highWatermark context.moveTo(0, y) context.lineTo(width, y) context.stroke() } // low-watermark level if (lowWatermark && lowWatermark > 0 && lowWatermark < 100) { context.beginPath() context.strokeStyle = 'black' context.lineWidth = 1 context.setLineDash([3]) const y = height - (height / 100) * lowWatermark context.moveTo(0, y) context.lineTo(width, y) context.stroke() } // gauge outline context.beginPath() context.setLineDash([]) context.strokeStyle = strokeStyle context.lineWidth = lineWidth context.roundRect(0, 0, width, height, round) context.stroke() context.beginPath() context.translate(-left, -top) context.restore() } get usage() { const { currentUsage = 0, maxCapacity = 100 } = this.state return Math.round((currentUsage / maxCapacity) * 100) } set usage(usage: number) { // intentionally have done nothing } onchangeData(after: Record, before: Record): void { const { CURRENTCAPACITY = 0, MAXCAPACITY = 100, HIGHWATERMARK = 100, LOWWATERMARK = 0, FULLUP = 'F' } = this.data && typeof this.data == 'object' ? this.data : {} const currentUsage = Number(CURRENTCAPACITY) || 0 const maxCapacity = Number(MAXCAPACITY) || 100 const highWatermark = Number(HIGHWATERMARK) || 100 const lowWatermark = Number(LOWWATERMARK) || 0 this.setState({ currentUsage, maxCapacity, highWatermark, lowWatermark, fullup: FULLUP == 'T', usage: Math.round((currentUsage / maxCapacity) * 100) }) } }