Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resize charts based on available space #5012

Merged
merged 11 commits into from
Oct 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ import {
AssetType
} from '@/types/Types';
import { CiemssPresetTypes, DrilldownTabs, ChartSetting, ChartSettingType } from '@/types/common';
import { getTimespan, drilldownChartSize, nodeMetadata } from '@/components/workflow/util';
import { getTimespan, nodeMetadata } from '@/components/workflow/util';
import { useToastService } from '@/services/toast';
import { autoCalibrationMapping } from '@/services/concept';
import {
Expand Down Expand Up @@ -496,6 +496,7 @@ import { displayNumber } from '@/utils/number';
import TeraPyciemssCancelButton from '@/components/pyciemss/tera-pyciemss-cancel-button.vue';
import TeraSaveSimulationModal from '@/components/project/tera-save-simulation-modal.vue';
import { useClientEvent } from '@/composables/useClientEvent';
import { useDrilldownChartSize } from '@/composables/useDrilldownChartSize';
import { flattenInterventionData, getInterventionPolicyById } from '@/services/intervention-policy';
import TeraInterventionSummaryCard from '@/components/workflow/ops/simulate-ciemss/tera-intervention-summary-card.vue';
import { getParameters } from '@/model-representation/service';
Expand Down Expand Up @@ -663,9 +664,9 @@ const disableRunButton = computed(

const selectedOutputId = ref<string>();
const lossChartContainer = ref(null);
const lossChartSize = computed(() => drilldownChartSize(lossChartContainer.value));
const lossChartSize = useDrilldownChartSize(lossChartContainer);
const outputPanel = ref(null);
const chartSize = computed(() => drilldownChartSize(outputPanel.value));
const chartSize = useDrilldownChartSize(outputPanel);

const chartSettings = computed(() => props.node.state.chartSettings ?? []);
const selectedParameterSettings = computed(() =>
Expand Down Expand Up @@ -875,7 +876,7 @@ const LOSS_CHART_DATA_SOURCE = 'lossData'; // Name of the streaming data source
const lossChartRef = ref<InstanceType<typeof VegaChart>>();
const lossChartSpec = ref();
const lossValues = ref<{ [key: string]: number }[]>([]);
const updateLossChartSpec = (data: string | Record<string, any>[]) => {
const updateLossChartSpec = (data: string | Record<string, any>[], size: { width: number; height: number }) => {
Tom-Szendrey marked this conversation as resolved.
Show resolved Hide resolved
lossChartSpec.value = createForecastChart(
null,
{
Expand All @@ -886,7 +887,7 @@ const updateLossChartSpec = (data: string | Record<string, any>[]) => {
null,
{
title: '',
width: lossChartSize.value.width,
width: size.width,
height: 100,
xAxisTitle: 'Solver iterations',
yAxisTitle: 'Loss'
Expand Down Expand Up @@ -1123,15 +1124,15 @@ watch(
);

watch(
() => props.node.state.inProgressCalibrationId,
(id) => {
[() => props.node.state.inProgressCalibrationId, lossChartSize],
([id, size]) => {
if (id === '') {
showSpinner.value = false;
updateLossChartSpec(lossValues.value);
updateLossChartSpec(lossValues.value, size);
unsubscribeToUpdateMessages([id], ClientEventType.SimulationPyciemss, messageHandler);
} else {
showSpinner.value = true;
updateLossChartSpec(LOSS_CHART_DATA_SOURCE);
updateLossChartSpec(LOSS_CHART_DATA_SOURCE, size);
subscribeToUpdateMessages([id], ClientEventType.SimulationPyciemss, messageHandler);
}
},
Expand All @@ -1154,7 +1155,7 @@ watch(
iter: i,
loss: d.data.loss
}));
updateLossChartSpec(lossValues.value);
updateLossChartSpec(lossValues.value, lossChartSize.value);
}

const state = props.node.state;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ import Dropdown from 'primevue/dropdown';
import TeraInputText from '@/components/widgets/tera-input-text.vue';
import SelectButton from 'primevue/selectbutton';
import Dialog from 'primevue/dialog';
import { useDrilldownChartSize } from '@/composables/useDrilldownChartSize';
import TeraSaveSimulationModal from '@/components/project/tera-save-simulation-modal.vue';
import TeraDatasetDatatable from '@/components/dataset/tera-dataset-datatable.vue';
import TeraDrilldown from '@/components/drilldown/tera-drilldown.vue';
Expand Down Expand Up @@ -424,7 +425,7 @@ import {
AssetType
} from '@/types/Types';
import { logger } from '@/utils/logger';
import { drilldownChartSize, nodeMetadata } from '@/components/workflow/util';
import { nodeMetadata } from '@/components/workflow/util';
import { WorkflowNode } from '@/types/workflow';
import TeraSliderPanel from '@/components/widgets/tera-slider-panel.vue';

Expand Down Expand Up @@ -515,7 +516,7 @@ const showSaveDataDialog = ref<boolean>(false);
const showSaveInterventionPolicy = ref<boolean>(false);

const outputPanel = ref(null);
const chartSize = computed(() => drilldownChartSize(outputPanel.value));
const chartSize = useDrilldownChartSize(outputPanel);
const cancelRunId = computed(() => props.node.state.inProgressPostForecastId || props.node.state.inProgressOptimizeId);

const activePolicyGroups = computed(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ import Button from 'primevue/button';
import Dropdown from 'primevue/dropdown';
import { Vue3Lottie } from 'vue3-lottie';
import EmptySeed from '@/assets/images/lottie-empty-seed.json';
import { useDrilldownChartSize } from '@/composables/useDrilldownChartSize';
import TeraInputNumber from '@/components/widgets/tera-input-number.vue';
import TeraSliderPanel from '@/components/widgets/tera-slider-panel.vue';
import type { CsvAsset, InterventionPolicy, SimulationRequest, TimeSpan } from '@/types/Types';
Expand All @@ -219,7 +220,7 @@ import {
CiemssMethodOptions
} from '@/services/models/simulation-service';
import { getModelByModelConfigurationId, getUnitsFromModelParts } from '@/services/model';
import { chartActionsProxy, drilldownChartSize, nodeMetadata } from '@/components/workflow/util';
import { chartActionsProxy, nodeMetadata } from '@/components/workflow/util';

import TeraDatasetDatatable from '@/components/dataset/tera-dataset-datatable.vue';
import SelectButton from 'primevue/selectbutton';
Expand Down Expand Up @@ -325,7 +326,7 @@ const selectedRunId = computed(() => props.node.outputs.find((o) => o.id === sel

const cancelRunId = computed(() => props.node.state.inProgressForecastId);
const outputPanel = ref(null);
const chartSize = computed(() => drilldownChartSize(outputPanel.value));
const chartSize = useDrilldownChartSize(outputPanel);

const showSaveDataset = ref(false);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Ref, ref } from 'vue';
import _ from 'lodash';
import { drilldownChartSize } from '@/components/workflow/util';
import { useResizeObserver } from './useResizeObserver';

/**
* A Vue composable that provides reactive chart size based on the container element's size.
*
* @param {Ref<HTMLElement | null>} containerElement - A reference to the container HTML element.
* @param {number} [debounceDelay=100] - The debounce delay in milliseconds for resize events.
* @returns {Ref<{ width: number, height: number }>} - A reactive reference to the chart size.
*/
export function useDrilldownChartSize(containerElement: Ref<HTMLElement | null>, debounceDelay = 100) {
const size = ref(drilldownChartSize(null));
useResizeObserver(
containerElement,
_.debounce(() => {
size.value = drilldownChartSize(containerElement.value);
}, debounceDelay)
);
return size;
}
41 changes: 41 additions & 0 deletions packages/client/hmi-client/src/composables/useResizeObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { onUnmounted, Ref, watch } from 'vue';

/**
* A composable function that sets up a ResizeObserver to observe changes in the size of a target element.
*
* @param targetElement - A Vue ref object containing the HTMLElement to be observed.
* @param callback - A callback function that will be called with the new DOMRectReadOnly object whenever the size of the target element changes.
*
* @example
* ```typescript
* import { ref } from 'vue';
* import { useResizeObserver } from './useResizeObserver';
*
* const elementRef = ref<HTMLElement>(null);
*
* useResizeObserver(elementRef, (rect) => {
* console.log('Element size changed:', rect);
* });
* ```
*/
export function useResizeObserver(targetElement: Ref<HTMLElement | null>, callback: (rect: DOMRectReadOnly) => void) {
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => callback(entry.contentRect));
});

watch(
targetElement,
(val, oldVal) => {
// In case the target element changes, stop observing the old element and start observing the new one.
if (oldVal) resizeObserver.unobserve(oldVal);
if (val) resizeObserver.observe(val);
},
{ immediate: true }
);

onUnmounted(() => {
// Stop observing the target element when the component is unmounted
if (targetElement.value) resizeObserver.unobserve(targetElement.value);
resizeObserver.disconnect();
});
}