Path: blob/main/frontend/vue/components/Eigenvector/EigenvectorWidget.vue
4040 views
<template>
<div class="eigenvector-widget">
<EigenvectorControls
@initialStateChanged="onInitialStateChange"
@circuitChanged="onCircuitChange"
/>
<div v-if="currentState?.state" class="eigenvector-widget__representation">
<StatevectorBrackets class="eigenvector-widget__representation__brackets">
<AmplitudeDisk
class="eigenvector-widget__representation__disk"
:phase="currentState.state[0].phase"
:magnitude="currentState.state[0].magnitude"
/>
<AmplitudeDisk
class="eigenvector-widget__representation__disk"
:phase="currentState.state[1].phase"
:magnitude="currentState.state[1].magnitude"
/>
</StatevectorBrackets>
<div
class="eigenvector-widget__representation__state-change"
:class="[
`eigenvector-widget__representation__state-change_${currentCircuit.name.toLowerCase()}`,
{ 'eigenvector-widget__representation__state-change_pause': !isPlaying }
]"
>
<EigenvectorTransitionPath
class="eigenvector-widget__representation__state-change__path"
:active-path="currentCircuit.transitionType"
>
<div
class="
eigenvector-widget__representation__state-change__path__empty
"
>
{{ $translate("Select a circuit in the selector above") }}
</div>
</EigenvectorTransitionPath>
<AmplitudeDisk
class="
eigenvector-widget__representation__state-change__disk
eigenvector-widget__representation__state-change__disk_0
"
:phase="transitionState[0].phase"
:magnitude="transitionState[0].magnitude"
@animationcancel="animationCancel"
@animationiteration="animationStart"
@animationstart="animationStart"
@animationend="animationEnd"
/>
<AmplitudeDisk
class="
eigenvector-widget__representation__state-change__disk
eigenvector-widget__representation__state-change__disk_1
"
:phase="transitionState[1].phase"
:magnitude="transitionState[1].magnitude"
/>
</div>
<StatevectorBrackets class="eigenvector-widget__representation__brackets">
<AmplitudeDisk
v-if="currentCircuit?.name !== ''"
class="eigenvector-widget__representation__disk"
:phase="endState[0].phase"
:magnitude="endState[0].magnitude"
/>
<AmplitudeDisk
v-if="currentCircuit?.name !== ''"
class="eigenvector-widget__representation__disk"
:phase="endState[1].phase"
:magnitude="endState[1].magnitude"
/>
</StatevectorBrackets>
</div>
<div class="eigenvector-widget__play-control">
<label
class="eigenvector-widget__play-control__label"
:for="`play-${uid}`"
>
{{ isPlaying ? $translate("Stop animation") : $translate("Play animation")}}
</label>
<label :for="`play-${uid}`">
<PauseIcon v-if="isPlaying" />
<PlayIcon v-else />
</label>
<input
class="eigenvector-widget__play-control__checkbox"
:id="`play-${uid}`"
type="checkbox"
:checked="isPlaying"
@change="switchPlayState"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue-demi";
import AmplitudeDisk from "../AmplitudeDisk/AmplitudeDisk.vue";
import { Amplitude } from "../AmplitudeDisk/amplitude";
import StatevectorBrackets from "../Statevector/StatevectorBrackets.vue";
import EigenvectorControls, {
InitialState,
CircuitElement,
unselectedCircuit,
} from "./EigenvectorControls.vue";
import EigenvectorTransitionPath from "./EigenvectorTransitionPath.vue";
import PauseIcon from "@carbon/icons-vue/lib/pause--filled/20";
import PlayIcon from "@carbon/icons-vue/lib/play--filled--alt/20";
export default defineComponent({
name: "EigenvectorWidget",
components: {
StatevectorBrackets,
AmplitudeDisk,
EigenvectorControls,
EigenvectorTransitionPath,
PauseIcon,
PlayIcon,
},
data() {
return {
currentState: {
name: "",
state: [
{ phase: 0, magnitude: 0.5 },
{ phase: 0, magnitude: 0.5 },
],
} as InitialState,
currentCircuit: unselectedCircuit,
uid: Math.random().toString().replace(".", ""),
transitionState: [
{ phase: 0, magnitude: 0.5 },
{ phase: 0, magnitude: 0.5 },
],
timeout: [] as NodeJS.Timeout[],
animationFrame: [] as number[],
isPlaying: true,
};
},
computed: {
endState(): Amplitude[] {
const initialState = this.currentState.state;
const stateDelta = this.currentCircuit.stateDelta;
const newState = [
{
phase: initialState[0].phase + stateDelta[0].phase,
magnitude: initialState[0].magnitude + stateDelta[0].magnitude,
},
{
phase: initialState[1].phase + stateDelta[1].phase,
magnitude: initialState[1].magnitude + stateDelta[1].magnitude,
},
];
if (this.currentCircuit.transitionType === "cross") {
return [newState[1], newState[0]];
} else {
return newState;
}
},
},
methods: {
onCircuitChange(circuit: CircuitElement) {
this.currentCircuit = circuit;
},
onInitialStateChange(state: InitialState) {
this.currentState = state;
this.resetAnimation();
},
clearTimers() {
clearTimeout(this.timeout[0]);
clearTimeout(this.timeout[1]);
cancelAnimationFrame(this.animationFrame[0]);
cancelAnimationFrame(this.animationFrame[1]);
this.timeout = [];
this.animationFrame = [];
},
setProgress(progress: number, idx: number) {
const initialState = this.currentState.state;
const stateDelta = this.currentCircuit.stateDelta;
this.transitionState[idx] = {
phase: initialState[idx].phase + stateDelta[idx].phase * progress,
magnitude:
initialState[idx].magnitude + stateDelta[idx].magnitude * progress,
};
},
animateState(idx: number) {
const ANIMATION_TIME = 1000;
const initialTime = new Date().getTime();
const frame = () => {
const timeDiff = new Date().getTime() - initialTime;
const progress = Math.min(timeDiff / ANIMATION_TIME, 1);
this.setProgress(progress, idx);
this.animationFrame[idx] =
progress < 1 ? requestAnimationFrame(frame) : -1;
};
frame();
},
resetAnimation() {
this.clearTimers();
this.setProgress(0, 0);
this.setProgress(0, 1);
},
animationStart() {
this.resetAnimation();
const timeoutTimes =
this.currentCircuit.transitionType === "cross"
? [1500, 3500]
: [500, 500];
this.timeout[0] = setTimeout(() => {
this.animateState(0);
}, timeoutTimes[0]);
this.timeout[1] = setTimeout(() => {
this.animateState(1);
}, timeoutTimes[1]);
},
animationEnd() {
this.resetAnimation();
},
animationCancel() {
this.resetAnimation();
},
switchPlayState(ev: Event) {
this.isPlaying = (ev.target as HTMLInputElement).checked;
},
},
});
</script>
<style lang="scss" scoped>
@import "carbon-components/scss/globals/scss/typography";
@import "~/../scss/variables/colors.scss";
@mixin cross-path-top($name) {
@keyframes #{$name} {
0% {
top: $spacing-02;
left: -3.5rem;
}
10% {
top: $spacing-02;
left: 0;
}
25% {
top: $spacing-02;
left: calc(50% - 4rem);
}
27% {
top: $spacing-02;
}
42% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
}
50% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(50% + 1rem);
}
75% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(50% + 1rem);
}
90% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(100% + 0.5rem);
}
100% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(100% + 0.5rem);
}
}
}
@mixin cross-path-bottom($name) {
@keyframes #{$name} {
0% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: -3.5rem;
}
10% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: 0;
}
25% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(50% - 4rem);
}
50% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
left: calc(50% - 4rem);
}
52% {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
}
67% {
top: $spacing-02;
}
75% {
top: $spacing-02;
left: calc(50% + 1rem);
}
90% {
top: $spacing-02;
left: calc(100% + 0.5rem);
}
100% {
top: $spacing-02;
left: calc(100% + 0.5rem);
}
}
}
@include cross-path-top(x-path-top);
@include cross-path-top(y-path-top);
@include cross-path-bottom(x-path-bottom);
@include cross-path-bottom(y-path-bottom);
@keyframes straight-path {
0% {
left: -3.5rem;
}
100% {
left: calc(100% + 0.5rem);
}
}
.eigenvector-widget {
&__representation {
display: flex;
flex-direction: row;
padding: $spacing-05 $spacing-03;
background-color: $background-color-white;
&__state-change {
position: relative;
flex: 1 0;
padding: 0 $spacing-05;
&__path {
height: 8rem;
&__empty {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
&__test {
position: absolute;
top: 0;
height: 8rem;
width: 2rem;
left: 10rem;
background-color: rgba(256, 256, 256, 0.8);
}
&__disk {
display: none;
position: absolute;
left: calc(50% - 4rem);
animation-iteration-count: infinite;
&_0 {
top: $spacing-02;
}
&_1 {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
}
}
&_z #{&}__disk {
display: block;
animation-name: straight-path;
animation-duration: 3s;
}
&_x #{&}__disk,
&_y #{&}__disk {
display: block;
animation-duration: 7s;
&_0 {
top: $spacing-02;
}
&_1 {
top: calc(#{$spacing-02} + 3rem + #{$spacing-06});
}
}
&_x #{&}__disk {
&_0 {
animation-name: x-path-top;
}
&_1 {
animation-name: x-path-bottom;
}
}
&_y #{&}__disk {
&_0 {
animation-name: y-path-top;
}
&_1 {
animation-name: y-path-bottom;
}
}
&_pause #{&}__disk {
display: none;
}
}
&__brackets {
display: flex;
flex-direction: column;
gap: $spacing-06;
flex: 0 0 4rem;
padding: $spacing-02 $spacing-03;
}
&__state-change__disk,
&__disk {
height: 3rem;
width: 3rem;
margin: 0;
}
}
&__play-control {
padding: $spacing-04;
display: flex;
flex-direction: row;
color: $active-color;
&__label {
@include type-style("body-long-01");
flex: 0 0 7rem;
}
&__checkbox {
display: none !important;
}
}
}
</style>