Created
February 19, 2021 23:52
-
-
Save klyngen/fec8ca91599ac11939a071791c1b90fc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {query, css, html, customElement, LitElement, property, TemplateResult} from 'lit-element'; | |
@customElement("overtime-visualizer") | |
export class OvertimeVisualizer extends LitElement { | |
@query("#wrapper") | |
wrapper: HTMLDivElement; | |
@query("#test") | |
test: HTMLParagraphElement; | |
@property() | |
overtimeData: OvertimeData[] = []; | |
@property({type: Number}) | |
subtract = 0; | |
loaded = false; | |
private textWillOverflow(data: OvertimeData): boolean { | |
this.test.innerText = data.name; | |
const pixels: number = this.getRelativeWidth(data.value); | |
if (pixels < this.test.clientWidth) | |
return true; | |
return false; | |
} | |
get filteredData(): OvertimeData[] { | |
const sorted = this.overtimeData.sort((a, b) => a.priority - b.priority); | |
if (this.overtimeData) { | |
let remaining = this.subtract; | |
const clones: OvertimeData[] = []; | |
sorted.forEach(item => { | |
const clone = {...item}; | |
if (clone.value >= remaining) { | |
clone.value -= remaining; | |
remaining = 0; | |
} | |
if (clone.value < remaining) { | |
remaining -= item.value; | |
clone.value = 0; | |
} | |
if (this.textWillOverflow(clone)) { | |
clone.name = clone.name[0]; | |
} | |
if (clone.value > 0) | |
clones.push(clone); | |
}); | |
return clones; | |
} | |
return []; | |
} | |
static get styles() { | |
return css` | |
.overtime { | |
display: flex; | |
} | |
.overtime-component-color-bar { | |
height: 30px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
font-weight: 600; | |
} | |
.overtime-component p { | |
text-align: center; | |
width: 100%; | |
margin-bottom: 5px; | |
} | |
.overtime-component { | |
transition: width 0.5s; | |
} | |
.overtime-component--delete { | |
transform: translateX(0); | |
} | |
`; | |
} | |
private createOvertimeBars(): TemplateResult[] | TemplateResult { | |
if (this.overtimeData && this.wrapper) { | |
return this.filteredData | |
.map(item => | |
html`<div class="overtime-component" style="width: ${this.getRelativeWidth(item.value)}px"> | |
<p id="${item.name}">${item.name}</p> | |
<div style="background: ${item.color}" class="overtime-component-color-bar"> | |
${item.value} | |
</div> | |
</div>`); | |
} else { | |
// TODO apply some sort of loading animation | |
return html` | |
<span>There is no current overtime data</span> | |
`; | |
} | |
} | |
render(): TemplateResult { | |
return html` | |
<p style="position: absolute; visibility: hidden; height: auto; width: auto: white-space: nowrap" id="test"></p> | |
<div id="wrapper" class="overtime"> | |
${this.createOvertimeBars()} | |
</div> | |
`; | |
} | |
firstUpdated(_changedProperties: Map<string, string | number | unknown>) { | |
if (this.wrapper) { | |
this.requestUpdate(); | |
} | |
} | |
private getRelativeWidth(value: number): number { | |
if (this.wrapper) { | |
return this.wrapper.clientWidth * (value / this.valueSum); | |
} | |
return 0; | |
} | |
private get valueSum(): number { | |
if (this.overtimeData) { | |
return this.overtimeData | |
.map(item => item.value) | |
.reduce((previous: number, current: number, _) => previous+current); | |
} | |
return 0; | |
} | |
} | |
export interface OvertimeData { | |
name: string; | |
color: string; | |
value: number; | |
priority: number; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div ref="cl" class="color-list"> | |
<div | |
v-for="color in filteredColors" | |
:key="color.priority" | |
:style="createWidthString(color)" | |
class="color-bar" | |
> | |
<template v-if="color.value > 0"> | |
<div class="color-bar-content"> | |
<div class="color-bar-name">{{ color.name }}</div> | |
<div :style="createColorString(color)" class="color-bar-value"> | |
{{ color.value }} | |
</div> | |
</div> | |
</template> | |
</div> | |
</div> | |
</template> | |
<script lang="ts"> | |
import Vue from "vue"; | |
// eslint-disable-next-line | |
import { State } from "../store/index"; | |
import { mapState, Store } from "vuex"; | |
import { CategorizedFlexHours } from "../store/overtime"; | |
export default Vue.extend({ | |
props: { | |
subtract: { | |
default: "", | |
type: String, | |
}, | |
}, | |
data() { | |
return { | |
colors: [], | |
unsubscribe: () => {}, | |
}; | |
}, | |
computed: { | |
filteredColors(): CategorizedFlexHours[] { | |
return this.withDrawFromList( | |
this.colors as CategorizedFlexHours[], | |
Number.parseInt(this.subtract, 10) | |
); | |
}, | |
}, | |
async created() { | |
await this.$store.dispatch("FETCH_AVAILABLE_HOURS"); | |
this.colors = (this.$store as Store<State>).getters.getCategorizedFlexHours; | |
this.unsubscribe = (this.$store as Store<State>).subscribe( | |
(mutation, state) => { | |
if (mutation.type === "SET_AVAILABLEHOURS") { | |
this.colors = (this.$store as Store< | |
State | |
>).getters.getCategorizedFlexHours; | |
} | |
} | |
); | |
}, | |
beforeDestroy() { | |
this.unsubscribe(); | |
}, | |
methods: { | |
createColorString(colorConfig: CategorizedFlexHours): string { | |
return `background: ${colorConfig.colorValue};`; | |
}, | |
createWidthString(colorConfig: CategorizedFlexHours): string { | |
return this.createCalculatedWidthString(colorConfig.value); | |
}, | |
createFlexWidthString(value: number): string { | |
return `flex-grow: ${Math.floor( | |
(value / this.getValuePercentage()) * 100 | |
)};`; | |
}, | |
createCalculatedWidthString(value: number): string { | |
const width: number = this.getElementWidth(); | |
const pixelWidth: number = Math.floor( | |
(value / this.getValuePercentage()) * width | |
); | |
return `width: ${pixelWidth}px;`; | |
}, | |
getValuePercentage(): number { | |
let valueSum = 0; | |
(this.colors as CategorizedFlexHours[]).forEach(item => { | |
valueSum += item.value; | |
}); | |
return valueSum; | |
}, | |
getElementWidth(): number { | |
if (this.$refs.cl) return (this.$refs.cl as HTMLDivElement).clientWidth; | |
else return window.innerWidth; | |
}, | |
withDrawFromList( | |
colorConfigs: CategorizedFlexHours[], | |
value: number | |
): CategorizedFlexHours[] { | |
const newItems: CategorizedFlexHours[] = []; | |
var sortedConfig = colorConfigs.sort((a, b) => b.priority - a.priority); | |
for (var item of sortedConfig) { | |
const clone = { ...item }; | |
if (value > 0) { | |
if (clone.value < value) { | |
value = value - clone.value; | |
clone.value = 0; | |
} else { | |
clone.value = clone.value - value; | |
value = 0; | |
} | |
} | |
newItems.push(clone); | |
} | |
return newItems; | |
}, | |
}, | |
}); | |
</script> | |
<style scoped> | |
.color-list { | |
display: flex; | |
align-content: stretch; | |
} | |
.color-bar { | |
transition: width 0.5s; | |
} | |
.color-bar-value { | |
height: 30px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
font-weight: 600; | |
} | |
.color-bar-name { | |
text-align: center; | |
font-weight: 600; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment