Skip to content

Commit

Permalink
Task: funman output should be slightly more inline with designs (#2316)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Chang <mwdchang@gmail.com>
  • Loading branch information
Tom-Szendrey and mwdchang authored Nov 28, 2023
1 parent fd7f753 commit 6069b41
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
<template>
<div class="section-row">
<label>Select Parameter One</label>
<Dropdown v-model="selectedParamOne" :options="parameterOptions"> </Dropdown>
<label>Trajectory State</label>
<Dropdown v-model="selectedTrajState" :options="modelStates"> </Dropdown>
</div>
<div class="section-row">
<label>Select Parameter Two</label>
<Dropdown v-model="selectedParamTwo" :options="parameterOptions"> </Dropdown>
</div>
<div class="section-row">
<label>Timestep</label>
<Dropdown v-model="timestep" :options="timestepOptions"> </Dropdown>
</div>
<div class="container">
<div ref="boxRef"></div>
<div ref="trajRef"></div>
<div ref="trajRef"></div>

<h4>Configuration parameters <i class="pi pi-info-circle" /></h4>
<p class="secondary-text">
Adjust parameter ranges to only include values in the green region or less.
</p>

<!-- TODO: add boxes modal per row https://github.com/DARPA-ASKEM/terarium/issues/1924 -->
<div class="variables-table">
<div class="variables-header">
<header
v-for="(title, index) in ['Parameter', 'Lower bound', 'Upper bound', '', '']"
:key="index"
>
{{ title }}
</header>
</div>
<div v-for="(column, index) in lastTrueBox?.bounds" :key="index">
<div class="variables-row" v-if="parameterOptions.includes(index.toString())">
<div>{{ index.toString() }}</div>
<div>{{ column?.lb }}</div>
<div>{{ column?.ub }}</div>
</div>
</div>
</div>
</template>

Expand All @@ -22,8 +35,7 @@ import { ref, onMounted, watch } from 'vue';
import {
getQueries,
processFunman,
renderFumanTrajectories,
renderFunmanBoundaryChart
renderFumanTrajectories
} from '@/services/models/funman-service';
import Dropdown from 'primevue/dropdown';
Expand All @@ -32,46 +44,49 @@ const props = defineProps<{
}>();
const parameterOptions = ref<string[]>([]);
const selectedParamOne = ref();
const selectedParamTwo = ref();
const selectedParam = ref();
const selectedTrajState = ref();
const modelStates = ref<string[]>();
const timestepOptions = ref();
const timestep = ref();
const boxRef = ref();
const trajRef = ref();
const boxId = 'box2';
const lastTrueBox = ref();
const initalizeParameters = async () => {
const funModel = await getQueries(props.funModelId);
parameterOptions.value = [];
funModel.model.petrinet.semantics.ode.parameters.map((ele) =>
parameterOptions.value.push(ele.id)
);
selectedParamOne.value = parameterOptions.value[0];
selectedParamTwo.value = parameterOptions.value[0];
selectedParam.value = parameterOptions.value[0];
timestepOptions.value = funModel.request.structure_parameters[0].schedules[0].timepoints;
timestep.value = timestepOptions.value[1];
const tempList: string[] = [];
funModel.model.petrinet.model.states.forEach((element) => {
tempList.push(element.id);
});
modelStates.value = tempList;
selectedTrajState.value = modelStates.value[0];
lastTrueBox.value = funModel.parameter_space.true_boxes.at(-1);
};
const renderGraph = async () => {
const funModel = await getQueries(props.funModelId);
const width = 800;
const height = 250;
const funModel = await getQueries(props.funModelId);
const processedData = processFunman(funModel);
renderFunmanBoundaryChart(
boxRef.value,
renderFumanTrajectories(
trajRef.value as HTMLElement,
processedData,
selectedParamOne.value,
selectedParamTwo.value,
timestep.value,
boxId,
selectedTrajState.value,
{
width,
height
}
);
renderFumanTrajectories(trajRef.value as HTMLElement, processedData, boxId, {
width,
height
});
};
onMounted(() => {
Expand All @@ -88,26 +103,54 @@ watch(
watch(
// Whenever user changes options rerender.
() => [selectedParamOne.value, selectedParamTwo.value, timestep.value],
() => [selectedParam.value, timestep.value, selectedTrajState.value],
async () => {
renderGraph();
}
);
</script>

<style scoped>
.container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.section-row {
display: flex;
/* flex-direction: column; */
padding: 0.5rem 0rem;
align-items: center;
gap: 0.8125rem;
align-self: stretch;
}
.secondary-text {
color: var(--Text-Secondary, #667085);
/* Body Small/Regular */
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.3125rem; /* 150% */
letter-spacing: 0.01563rem;
}
.variables-table {
display: grid;
grid-template-columns: 1fr;
}
.variables-table div {
padding: 0.25rem;
}
.variables-row {
display: grid;
grid-template-columns: repeat(6, 1fr) 0.5fr;
grid-template-rows: 1fr 1fr;
border-top: 1px solid var(--surface-border);
}
.variables-header {
display: grid;
grid-template-columns: repeat(6, 1fr) 0.5fr;
}
header {
padding-right: 1rem;
}
</style>
70 changes: 61 additions & 9 deletions packages/client/hmi-client/src/services/models/funman-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import API from '@/api/api';
import { FunmanPostQueriesRequest } from '@/types/Types';
import * as d3 from 'd3';

interface FunmanProcessedData {
export interface FunmanProcessedData {
boxes: any[];
points: any[];
states: string[];
Expand Down Expand Up @@ -164,37 +164,89 @@ export const renderFumanTrajectories = (
element: HTMLElement,
processedData: FunmanProcessedData,
boxId: string,
state: string,
options: RenderOptions
) => {
const width = options.width;
const height = options.height;
const topMargin = 10;
const rightMargin = 30;
const leftMargin = 30;
const bottomMargin = 30;
const { trajs, states } = processedData;

const elemSelection = d3.select(element);
d3.select(element).selectAll('*').remove();
const svg = elemSelection.append('svg').attr('width', width).attr('height', height);
const group = svg.append('g');

const points = trajs.filter((d: any) => d.boxId === boxId);

// Find max/min across timesteps
const xDomain = d3.extent(points.map((d) => d.timestep)) as [number, number];

// Find max/min across all state values
const yDomain = d3.extent(points.map((d) => states.map((s) => d[s])).flat()) as [number, number];
const yDomain = d3.extent(
points.map((d) => states.filter((s) => s === state).map((s) => d[s])).flat()
) as [number, number];

const xScale = d3
.scaleLinear()
.domain(xDomain)
.range([leftMargin, width - rightMargin]);

const yScale = d3
.scaleLinear()
.domain(yDomain)
.range([height - bottomMargin, topMargin]);

// Add the x-axis.
svg
.append('g')
.attr('transform', `translate(0,${height - bottomMargin})`)
.call(
d3
.axisBottom(xScale)
.ticks(width / 60)
.tickSizeOuter(0)
);

// Add the y-axis
svg
.append('g')
.attr('transform', `translate(${leftMargin},0)`)
.call(
d3
.axisLeft(yScale)
.ticks(height / 60)
.tickSizeOuter(0)
);

// Add label for x-axis
svg
.append('text')
.attr('class', 'x label')
.attr('text-anchor', 'end')
.attr('x', width / 2)
.attr('y', height - 2)
.text('Timestep');

const xScale = d3.scaleLinear().domain(xDomain).range([0, width]);
const yScale = d3.scaleLinear().domain(yDomain).range([height, 0]);
const pathFn = d3
.line<{ x: number; y: number }>()
.x((d) => xScale(d.x))
.y((d) => yScale(d.y))
.curve(d3.curveBasis);

states.forEach((s: string) => {
const path = points.map((p: any) => ({ x: p.timestep, y: p[s] }));
group.append('path').attr('d', pathFn(path)).style('stroke', '#888').style('fill', 'none');
});
states
.filter((s) => s === state)
.forEach((s: string) => {
const path = points.map((p: any) => ({ x: p.timestep, y: p[s] }));
svg
.append('g')
.append('path')
.attr('d', pathFn(path))
.style('stroke', '#888')
.style('fill', 'none');
});
};

const getBoxesDomain = (boxes: FunmanBox[]) => {
Expand Down
37 changes: 12 additions & 25 deletions packages/client/hmi-client/src/workflow/ops/funman/tera-funman.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ import Slider from 'primevue/slider';
import { FunmanPostQueriesRequest, Model, ModelConfiguration } from '@/types/Types';
import { getQueries, makeQueries } from '@/services/models/funman-service';
import { WorkflowNode } from '@/types/workflow';
import { workflowEventBus } from '@/services/workflow';
import teraConstraintGroupForm from '@/components/funman/tera-constraint-group-form.vue';
import teraFunmanOutput from '@/components/funman/tera-funman-output.vue';
import { getModelConfigurationById } from '@/services/model-configurations';
Expand All @@ -146,6 +145,8 @@ const props = defineProps<{
node: WorkflowNode<FunmanOperationState>;
}>();
const emit = defineEmits(['append-output-port', 'update-state']);
enum FunmanTabs {
wizard,
notebook
Expand Down Expand Up @@ -250,14 +251,11 @@ const getStatus = async (runId) => {
const updateOutputPorts = async (runId) => {
const portLabel = props.node.inputs[0].label;
workflowEventBus.emit('append-output-port', {
node: props.node,
port: {
id: uuidv4(),
label: `${portLabel} Result`,
type: FunmanOperation.outputs[0].type,
value: runId
}
emit('append-output-port', {
id: uuidv4(),
label: `${portLabel} Result`,
type: FunmanOperation.outputs[0].type,
value: runId
});
};
Expand All @@ -274,11 +272,7 @@ const addConstraintForm = () => {
}
state.constraintGroups.push(newGroup);
workflowEventBus.emitNodeStateChange({
workflowId: props.node.workflowId,
nodeId: props.node.id,
state
});
emit('update-state', state);
};
const deleteConstraintGroupForm = (data) => {
Expand All @@ -288,11 +282,7 @@ const deleteConstraintGroupForm = (data) => {
}
state.constraintGroups.splice(data.index, 1);
workflowEventBus.emitNodeStateChange({
workflowId: props.node.workflowId,
nodeId: props.node.id,
state
});
emit('update-state', state);
};
const updateConstraintGroupForm = (data) => {
Expand All @@ -302,11 +292,7 @@ const updateConstraintGroupForm = (data) => {
}
state.constraintGroups[data.index] = data.updatedConfig;
workflowEventBus.emitNodeStateChange({
workflowId: props.node.workflowId,
nodeId: props.node.id,
state
});
emit('update-state', state);
};
// Used to set requestStepList.
Expand Down Expand Up @@ -383,6 +369,7 @@ watch(
<style scoped>
.content {
display: flex;
height: 80%;
overflow: auto;
padding: 1rem 1.5rem 1rem 0rem;
align-items: flex-start;
Expand All @@ -402,7 +389,7 @@ watch(
flex-direction: column;
align-items: flex-start;
flex: 1 0 0;
align-self: stretch;
align-self: auto;
}
.container h1 {
color: var(--text-color-primary);
Expand Down

0 comments on commit 6069b41

Please sign in to comment.