diff --git a/front/src/applications/operationalStudies/consts.ts b/front/src/applications/operationalStudies/consts.ts index fadc74a8dd2..3fce5376281 100644 --- a/front/src/applications/operationalStudies/consts.ts +++ b/front/src/applications/operationalStudies/consts.ts @@ -14,6 +14,7 @@ import { PowerRestrictionRangeItem, } from 'common/api/osrdEditoastApi'; import { LinearMetadataItem } from 'common/IntervalsDataViz/types'; +import { HeightPosition } from 'reducers/osrdsimulation/types'; export const BLOCKTYPES = [ { @@ -217,7 +218,12 @@ interface Profile { isStriped: boolean; } -interface ElectricalConditionSegment { +/** Those keys are used to index objects of type HeightPosition but also to access properties ending by + * "_start" / "_middle" / "_end" in objects of type PowerRestrictionSegment in order to draw the linear graph. */ +export const DRAWING_KEYS: (keyof HeightPosition)[] = ['position', 'height']; +export type DrawingKeys = typeof DRAWING_KEYS; + +export interface ElectricalConditionSegment { position_start: number; position_end: number; position_middle: number; @@ -376,7 +382,7 @@ export const createProfileSegment = ( return segment; }; -interface PowerRestrictionSegment { +export interface PowerRestrictionSegment { position_start: number; position_end: number; position_middle: number; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.jsx b/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.jsx deleted file mode 100644 index 65ccc394035..00000000000 --- a/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as d3 from 'd3'; - -const drawCurve = ( - chart, - classes, - dataSimulation, - groupID, - interpolation, - keyValues, - name, - rotate, - isSelected = true -) => { - const drawZone = chart.drawZone.select(`#${groupID}`); - drawZone - .append('path') - .attr('class', `line zoomable ${classes}`) - .datum(dataSimulation) - .attr('fill', 'none') - .attr('stroke-width', 1) - .attr( - 'd', - d3 - .line() - .x((d) => chart.x(rotate ? d[keyValues[1]] : d[keyValues[0]])) - .y((d) => chart.y(rotate ? d[keyValues[0]] : d[keyValues[1]])) - .curve(d3[interpolation]) - ); - - if (isSelected) { - drawZone - .append('circle') - .attr('class', `pointer ${classes}`) - .attr('id', `pointer-${name}`) - .attr('transform', 'translate(-0.5,0)') - .attr('r', 3) - .style('opacity', 0); - } -}; - -export default drawCurve; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.ts b/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.ts new file mode 100644 index 00000000000..48f2f969e83 --- /dev/null +++ b/front/src/modules/simulationResult/components/ChartHelpers/drawCurve.ts @@ -0,0 +1,47 @@ +import * as d3 from 'd3'; +import { Chart, ConsolidatedPosition } from 'reducers/osrdsimulation/types'; +import { ArrayElement, ArrayElementKeys } from 'utils/types'; +import { GevPreparedata } from '../SpeedSpaceChart/prepareData'; + +const drawCurve = < + T extends ArrayElement | ConsolidatedPosition +>( + chart: Chart, + classes: string, + dataSimulation: T[], + groupID: string, + interpolation: string, + keyValues: ArrayElementKeys[], + name: string, + rotate: boolean, + isSelected = true +) => { + const drawZone = chart.drawZone.select(`#${groupID}`); + + drawZone + .append('path') + .attr('class', `line zoomable ${classes}`) + .datum(dataSimulation) + .attr('fill', 'none') + .attr('stroke-width', 1) + .attr( + 'd', + d3 + .line() + .x((d) => chart.x((rotate ? d[keyValues[1]] : d[keyValues[0]]) as number)) + .y((d) => chart.y((rotate ? d[keyValues[0]] : d[keyValues[1]]) as number)) + .curve(d3[interpolation as keyof d3.CurveFactory]) + ); + + if (isSelected) { + drawZone + .append('circle') + .attr('class', `pointer ${classes}`) + .attr('id', `pointer-${name}`) + .attr('transform', 'translate(-0.5,0)') + .attr('r', 3) + .style('opacity', 0); + } +}; + +export default drawCurve; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.jsx b/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.jsx deleted file mode 100644 index 05bcf90d851..00000000000 --- a/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.jsx +++ /dev/null @@ -1,336 +0,0 @@ -import { pointer } from 'd3-selection'; -import i18n from 'i18n'; -import { getElementWidth } from './ChartHelpers'; - -const drawElectricalProfile = ( - chart, - classes, - dataSimulation, - groupID, - interpolation, - keyValues, - name, - rotate, - isStripe, - isIncompatible, - // TODO : when migrated id should be reverted with null by default - id -) => { - // TODO : remove line when parameter fixed - if (id === undefined) id = null; - - const width = rotate - ? chart.x(dataSimulation[`${keyValues[1]}_end`]) - - chart.x(dataSimulation[`${keyValues[1]}_start`]) - : chart.x(dataSimulation[`${keyValues[0]}_end`]) - - chart.x(dataSimulation[`${keyValues[0]}_start`]); - - const height = rotate - ? chart.y(dataSimulation[`${keyValues[0]}_end`]) - - chart.y(dataSimulation[`${keyValues[0]}_start`]) - : chart.y(dataSimulation[`${keyValues[1]}_end`]) - - chart.y(dataSimulation[`${keyValues[1]}_start`]); - - // prepare stripe pattern - if (isStripe) { - const stripe = chart.drawZone.select(`#${groupID}`); - stripe - - .append('pattern') - .attr('id', `${id}`) - .attr('patternUnits', 'userSpaceOnUse') - .attr('width', 8) - .attr('height', 8) - .append('path') - .attr('d', 'M-2,2 l4,-4 M0,8 l8,-8 M6,10 l4,-4') - .attr('stroke', dataSimulation.color) - .attr('stroke-width', 2.5); - } - - const drawZone = chart.drawZone.select(`#${groupID}`); - drawZone - - // add main rect for profile displayed - .append('rect') - .attr('id', id) - .attr('class', `rect main ${classes}`) - .datum(dataSimulation) - .attr('fill', isStripe ? `url(#${id})` : dataSimulation.color) - .attr('stroke-width', 1) - .attr('stroke', isStripe ? `url(#${id})` : dataSimulation.color) - .attr( - 'x', - chart.x( - rotate ? dataSimulation[`${keyValues[1]}_start`] : dataSimulation[`${keyValues[0]}_start`] - ) - ) - .attr( - 'y', - chart.y( - rotate ? dataSimulation[`${keyValues[0]}_start`] : dataSimulation[`${keyValues[1]}_start`] - ) - - height * -1 - ) - .attr('width', width) - .attr('height', height * -1); - - // add text to main rect - const addTextZone = () => { - if ((rotate && height < -24) || (!rotate && width > 60)) { - const textZone = chart.drawZone.select(`#${groupID}`); - - // get the width of the text element and adding a few pixels for readability - const labelFontSize = 8; // in px - const svgWidth = getElementWidth(dataSimulation.text, labelFontSize, `#${groupID}`) + 10; - - // add rect for text zone - textZone - .append('rect') - .attr('class', `rect electricalProfileTextBlock ${classes}`) - .attr('fill', '#FFF') - .attr('transform', rotate ? 'translate(-20, -10)' : `translate(-${svgWidth / 2}, -5.5)`) - .attr('rx', 2) - .attr('ry', 2) - .attr( - 'x', - chart.x( - rotate - ? dataSimulation[`${keyValues[1]}_middle`] - : dataSimulation[`${keyValues[0]}_middle`] - ) - ) - .attr( - 'y', - chart.y( - rotate - ? dataSimulation[`${keyValues[0]}_start`] - - (dataSimulation[`${keyValues[0]}_end`] - dataSimulation[`${keyValues[0]}_middle`]) - : dataSimulation[`${keyValues[1]}_start`] - - (dataSimulation[`${keyValues[1]}_end`] - dataSimulation[`${keyValues[1]}_middle`]) - ) - - height * -1 - ) - .attr('width', rotate ? 40 : svgWidth) - .attr('height', rotate ? 20 : '0.6em'); - - // add text to text zone - textZone - .append('text') - .attr('class', `electricalProfileText ${classes}`) - .attr('dominant-baseline', 'middle') - .attr('text-anchor', 'middle') - .text(dataSimulation.text) - .attr('fill', dataSimulation.textColor) - .attr('font-size', 8) - .attr('font-weight', 'bold') - .attr( - 'x', - chart.x( - rotate - ? dataSimulation[`${keyValues[1]}_start`] + - (dataSimulation[`${keyValues[1]}_end`] - dataSimulation[`${keyValues[1]}_middle`]) - : dataSimulation[`${keyValues[0]}_middle`] - ) - ) - .attr( - 'y', - chart.y( - rotate - ? dataSimulation[`${keyValues[0]}_start`] - - (dataSimulation[`${keyValues[0]}_end`] - dataSimulation[`${keyValues[0]}_middle`]) - : dataSimulation[`${keyValues[1]}_start`] - - (dataSimulation[`${keyValues[1]}_end`] - dataSimulation[`${keyValues[1]}_middle`]) - ) - - height * -1 - ); - } - }; - - if ( - dataSimulation.electrification.mode_handled || - dataSimulation.electrification.isLowerPantograph - ) - drawZone.call(addTextZone); - - // create pop-up when hovering rect-profile - drawZone - .selectAll(`.${classes}`) - .on('mouseover', (event) => { - const lastPosition = chart.x(dataSimulation.lastPosition); - const pointerPositionX = pointer(event, event.currentTarget)[0]; - const pointerPositionY = pointer(event, event.currentTarget)[1]; - let popUpWidth; - - if (!dataSimulation.electrification.profile) { - popUpWidth = 175; - } else if (dataSimulation.electrification.mode === '1500') { - popUpWidth = 125; - } else { - popUpWidth = 165; - } - - let popUpPosition = 0; - if (pointerPositionX + popUpWidth > lastPosition) { - popUpPosition = pointerPositionX + popUpWidth - lastPosition; - } - - if (rotate) { - drawZone - .select(`.${classes} `) - .attr('transform', 'translate(-4, 0)') - .attr('width', width + 8); - } else { - drawZone - .select(`.${classes} `) - .attr('transform', 'translate(0, -4)') - .attr('height', (height - 8) * -1); - } - - drawZone - .append('rect') - .attr('class', `rect data`) - .attr('fill', '#FFF') - .attr('rx', 4) - .attr('ry', 4) - .attr( - 'transform', - rotate - ? 'translate(80, 0)' - : `translate(${0 - popUpPosition}, ${ - isIncompatible || !dataSimulation.electrification.profile ? '-65' : '-45' - } )` - ) - .attr('x', pointerPositionX) - .attr( - 'y', - rotate ? pointerPositionY : chart.y(dataSimulation[`${keyValues[1]}_start`]) - height * -1 - ) - .attr('width', popUpWidth) - .attr('height', isIncompatible || !dataSimulation.electrification.profile ? 55 : 35); - - // add profile pop-up rect - drawZone - .append('rect') - .attr('class', `rect data`) - .attr('fill', isStripe ? `url(#${id})` : dataSimulation.textColor) - .attr('stroke-width', 1) - .attr('stroke', isStripe ? `url(#${id})` : dataSimulation.textColor) - .attr( - 'transform', - rotate - ? `translate(88, ${ - isIncompatible || !dataSimulation.electrification.profile ? '16' : '8' - })` - : `translate(${8 - popUpPosition}, ${ - isIncompatible || !dataSimulation.electrification.profile ? '-47' : '-37' - } )` - ) - .attr('x', pointerPositionX) - .attr( - 'y', - rotate ? pointerPositionY : chart.y(dataSimulation[`${keyValues[1]}_start`]) - height * -1 - ) - .attr('width', dataSimulation.electrification.mode === 1500 ? 20 : 28) - .attr('height', 20); - - // add profile pop-up text - if (dataSimulation.electrification.object_type !== 'Electrified') { - drawZone - .append('text') - .attr('class', `data`) - .attr('dominant-baseline', 'middle') - .text( - `${i18n.t('electricalProfiles.notElectrified', { - ns: 'simulation', - })}` - ) - .attr( - 'transform', - rotate - ? `translate(123,${ - isIncompatible || !dataSimulation.electrification.profile ? 18 : 20 - })` - : `translate(${48 - popUpPosition}, ${ - isIncompatible || !dataSimulation.electrification.profile ? '-37' : '-25' - })` - ) - .attr('x', pointerPositionX) - .attr( - 'y', - rotate - ? pointerPositionY - : chart.y(dataSimulation[`${keyValues[1]}_start`]) - height * -1 - ); - } else { - drawZone - .append('text') - .attr('class', `data`) - .attr('dominant-baseline', 'middle') - .text( - isIncompatible || !dataSimulation.electrification.profile - ? `${dataSimulation.electrification.mode}V ${i18n.t('electricalProfiles.used', { - ns: 'simulation', - })}` - : `${dataSimulation.electrification.mode}V ${dataSimulation.electrification.profile}` - ) - .attr( - 'transform', - rotate - ? `translate(123,${ - isIncompatible || !dataSimulation.electrification.profile ? 18 : 20 - })` - : `translate(${48 - popUpPosition}, ${ - isIncompatible || !dataSimulation.electrification.profile ? '-45' : '-25' - })` - ) - .attr('x', pointerPositionX) - .attr( - 'y', - rotate - ? pointerPositionY - : chart.y(dataSimulation[`${keyValues[1]}_start`]) - height * -1 - ); - - if (isIncompatible || !dataSimulation.electrification.profile) { - drawZone - .append('text') - .attr('class', `data`) - .attr('dominant-baseline', 'middle') - .text( - dataSimulation.electrification.profile - ? `${dataSimulation.electrification.profile} ${i18n.t( - 'electricalProfiles.incompatible', - { - ns: 'simulation', - } - )}` - : `${i18n.t('electricalProfiles.missingProfile', { - ns: 'simulation', - })}` - ) - .attr( - 'transform', - rotate ? 'translate(123, 40)' : `translate(${48 - popUpPosition}, -25)` - ) - .attr('x', pointerPositionX) - .attr( - 'y', - rotate - ? pointerPositionY - : chart.y(dataSimulation[`${keyValues[1]}_start`]) - height * -1 - ); - } - } - }) - .on('mouseout', () => { - // add mouse-out interraction to remove pop-up - drawZone - .select(`.${classes} `) - .attr('transform', 'translate(0, 0)') - .attr('width', width) - .attr('height', height * -1); - drawZone.selectAll('.data').remove(); - }); -}; - -export default drawElectricalProfile; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.ts b/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.ts new file mode 100644 index 00000000000..e8b878a2329 --- /dev/null +++ b/front/src/modules/simulationResult/components/ChartHelpers/drawElectricalProfile.ts @@ -0,0 +1,272 @@ +import { pointer } from 'd3-selection'; +import i18n from 'i18n'; +import { Chart } from 'reducers/osrdsimulation/types'; +import { ElectricalConditionSegment, DrawingKeys } from 'applications/operationalStudies/consts'; +import getAxisValues from 'modules/simulationResult/components/ChartHelpers/drawHelpers'; +import { getElementWidth } from './ChartHelpers'; + +const ELECTRIFIED = 'Electrified'; +const NEUTRAL = 'Neutral'; + +const drawElectricalProfile = ( + chart: Chart, + classes: string, + electricalConditionSegment: ElectricalConditionSegment, + groupID: string, + keyValues: DrawingKeys, + rotate: boolean, + isStripe: boolean, + isIncompatible: boolean, + id: string +) => { + const electrificationType = electricalConditionSegment.electrification.object_type; + const electrificationProfile = + electrificationType === ELECTRIFIED + ? electricalConditionSegment.electrification.profile + : undefined; + const electrificationMode = + electrificationType === ELECTRIFIED + ? electricalConditionSegment.electrification.mode + : undefined; + + // Get the different axis values based on rotation + // x + const xValues = getAxisValues(electricalConditionSegment, keyValues, rotate ? 1 : 0); + // y + const yValues = getAxisValues(electricalConditionSegment, keyValues, rotate ? 0 : 1); + + const width = chart.x(xValues.end) - chart.x(xValues.start); + const height = chart.y(yValues.end) - chart.y(yValues.start); + + // prepare stripe pattern + if (isStripe) { + const stripe = chart.drawZone.select(`#${groupID}`); + stripe + + .append('pattern') + .attr('id', `${id}`) + .attr('patternUnits', 'userSpaceOnUse') + .attr('width', 8) + .attr('height', 8) + .append('path') + .attr('d', 'M-2,2 l4,-4 M0,8 l8,-8 M6,10 l4,-4') + .attr('stroke', electricalConditionSegment.color) + .attr('stroke-width', 2.5); + } + + const drawZone = chart.drawZone.select(`#${groupID}`); + + // add main rect for profile displayed + drawZone + .append('rect') + .attr('id', id) + .attr('class', `rect main ${classes}`) + .datum(electricalConditionSegment) + .attr('fill', isStripe ? `url(#${id})` : electricalConditionSegment.color) + .attr('stroke-width', 1) + .attr('stroke', isStripe ? `url(#${id})` : electricalConditionSegment.color) + .attr('x', chart.x(xValues.start)) + .attr('y', chart.y(yValues.start) + height) + .attr('width', width) + .attr('height', height * -1); + + /** Adds text to the main rect */ + const addTextZone = () => { + if ((rotate && height < -24) || (!rotate && width > 60)) { + const textZone = chart.drawZone.select(`#${groupID}`); + + // get the width of the text element and adding a few pixels for readability + const labelFontSize = 8; // in px + const svgWidth = + getElementWidth(electricalConditionSegment.text, labelFontSize, `#${groupID}`) + 10; + + // add rect for text zone + textZone + .append('rect') + .attr('class', `rect electricalProfileTextBlock ${classes}`) + .attr('fill', '#FFF') + .attr('transform', rotate ? 'translate(-20, -10)' : `translate(-${svgWidth / 2}, -5.5)`) + .attr('rx', 2) + .attr('ry', 2) + .attr('x', chart.x(xValues.middle)) + .attr('y', chart.y(yValues.start - (yValues.end - yValues.middle)) + height) + .attr('width', rotate ? 40 : svgWidth) + .attr('height', rotate ? 20 : '0.6em'); + + // add text to text zone + textZone + .append('text') + .attr('class', `electricalProfileText ${classes}`) + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .text(electricalConditionSegment.text) + .attr('fill', electricalConditionSegment.textColor) + .attr('font-size', 8) + .attr('font-weight', 'bold') + .attr( + 'x', + chart.x(rotate ? xValues.start + (xValues.end - xValues.middle) : xValues.middle) + ) + .attr('y', chart.y(yValues.start - (yValues.end - yValues.middle)) + height); + } + }; + + if ( + (electrificationType === ELECTRIFIED && + electricalConditionSegment.electrification.mode_handled) || + (electrificationType === NEUTRAL && electricalConditionSegment.electrification.lower_pantograph) + ) + drawZone.call(addTextZone); + + // create pop-up when hovering rect-profile + drawZone + .selectAll(`.${classes}`) + .on('mouseover', (event) => { + const lastPosition = chart.x(electricalConditionSegment.lastPosition); + const pointerPositionX = pointer(event, event.currentTarget)[0]; + const pointerPositionY = pointer(event, event.currentTarget)[1]; + let popUpWidth; + + if (!electrificationProfile) { + popUpWidth = 175; + } else if (electrificationMode === '1500') { + popUpWidth = 125; + } else { + popUpWidth = 165; + } + + let popUpPosition = 0; + if (pointerPositionX + popUpWidth > lastPosition) { + popUpPosition = pointerPositionX + popUpWidth - lastPosition; + } + + if (rotate) { + drawZone + .select(`.${classes} `) + .attr('transform', 'translate(-4, 0)') + .attr('width', width + 8); + } else { + drawZone + .select(`.${classes} `) + .attr('transform', 'translate(0, -4)') + .attr('height', (height - 8) * -1); + } + + drawZone + .append('rect') + .attr('class', `rect data`) + .attr('fill', '#FFF') + .attr('rx', 4) + .attr('ry', 4) + .attr( + 'transform', + rotate + ? 'translate(80, 0)' + : `translate(${0 - popUpPosition}, ${ + isIncompatible || !electrificationProfile ? '-65' : '-45' + } )` + ) + .attr('x', pointerPositionX) + .attr('y', rotate ? pointerPositionY : chart.y(yValues.start) + height) + .attr('width', popUpWidth) + .attr('height', isIncompatible || !electrificationProfile ? 55 : 35); + + // add profile pop-up rect + drawZone + .append('rect') + .attr('class', `rect data`) + .attr('fill', isStripe ? `url(#${id})` : electricalConditionSegment.textColor) + .attr('stroke-width', 1) + .attr('stroke', isStripe ? `url(#${id})` : electricalConditionSegment.textColor) + .attr( + 'transform', + rotate + ? `translate(88, ${isIncompatible || !electrificationProfile ? '16' : '8'})` + : `translate(${8 - popUpPosition}, ${ + isIncompatible || !electrificationProfile ? '-47' : '-37' + } )` + ) + .attr('x', pointerPositionX) + .attr('y', rotate ? pointerPositionY : chart.y(yValues.start) + height) + .attr('width', electrificationMode === '1500' ? 20 : 28) + .attr('height', 20); + + // add profile pop-up text + if (electrificationType !== ELECTRIFIED) { + drawZone + .append('text') + .attr('class', `data`) + .attr('dominant-baseline', 'middle') + .text( + `${i18n.t('electricalProfiles.notElectrified', { + ns: 'simulation', + })}` + ) + .attr( + 'transform', + rotate + ? `translate(123,${isIncompatible || !electrificationProfile ? 18 : 20})` + : `translate(${48 - popUpPosition}, ${ + isIncompatible || !electrificationProfile ? '-37' : '-25' + })` + ) + .attr('x', pointerPositionX) + .attr('y', rotate ? pointerPositionY : chart.y(yValues.start) + height); + } else { + drawZone + .append('text') + .attr('class', `data`) + .attr('dominant-baseline', 'middle') + .text( + isIncompatible || !electrificationProfile + ? `${electrificationMode}V ${i18n.t('electricalProfiles.used', { + ns: 'simulation', + })}` + : `${electrificationMode}V ${electrificationProfile}` + ) + .attr( + 'transform', + rotate + ? `translate(123,${isIncompatible || !electrificationProfile ? 18 : 20})` + : `translate(${48 - popUpPosition}, ${ + isIncompatible || !electrificationProfile ? '-45' : '-25' + })` + ) + .attr('x', pointerPositionX) + .attr('y', rotate ? pointerPositionY : chart.y(yValues.start) + height); + + if (isIncompatible || !electrificationProfile) { + drawZone + .append('text') + .attr('class', `data`) + .attr('dominant-baseline', 'middle') + .text( + electrificationProfile + ? `${electrificationProfile} ${i18n.t('electricalProfiles.incompatible', { + ns: 'simulation', + })}` + : `${i18n.t('electricalProfiles.missingProfile', { + ns: 'simulation', + })}` + ) + .attr( + 'transform', + rotate ? 'translate(123, 40)' : `translate(${48 - popUpPosition}, -25)` + ) + .attr('x', pointerPositionX) + .attr('y', rotate ? pointerPositionY : chart.y(yValues.start) + height); + } + } + }) + .on('mouseout', () => { + // add mouse-out interraction to remove pop-up + drawZone + .select(`.${classes} `) + .attr('transform', 'translate(0, 0)') + .attr('width', width) + .attr('height', height * -1); + drawZone.selectAll('.data').remove(); + }); +}; + +export default drawElectricalProfile; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawHelpers.ts b/front/src/modules/simulationResult/components/ChartHelpers/drawHelpers.ts new file mode 100644 index 00000000000..3d9849cd032 --- /dev/null +++ b/front/src/modules/simulationResult/components/ChartHelpers/drawHelpers.ts @@ -0,0 +1,18 @@ +import { + ElectricalConditionSegment, + PowerRestrictionSegment, + DrawingKeys, +} from 'applications/operationalStudies/consts'; + +/** Returns the start, middle and end values of the heights or the positions + * (depending on the axis and rotation) of a given segment. */ +const getAxisValues = ( + segment: ElectricalConditionSegment | PowerRestrictionSegment, + keyValues: DrawingKeys, + keyIndex: number +) => ({ + start: segment[`${keyValues[keyIndex]}_start`], + middle: segment[`${keyValues[keyIndex]}_middle`], + end: segment[`${keyValues[keyIndex]}_end`], +}); +export default getAxisValues; diff --git a/front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.jsx b/front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.ts similarity index 52% rename from front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.jsx rename to front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.ts index 0d087f64f9f..54d695d71b1 100644 --- a/front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.jsx +++ b/front/src/modules/simulationResult/components/ChartHelpers/drawPowerRestriction.ts @@ -1,45 +1,37 @@ +import { PowerRestrictionSegment, DrawingKeys } from 'applications/operationalStudies/consts'; import { pointer } from 'd3-selection'; import i18n from 'i18n'; +import { Chart } from 'reducers/osrdsimulation/types'; +import getAxisValues from 'modules/simulationResult/components/ChartHelpers/drawHelpers'; const drawPowerRestriction = ( - chart, - classes, - dataSimulation, - groupID, - interpolation, - keyValues, - name, - rotate, - isStripe, - isIncompatible, - isRestriction, - // TODO : when migrated id should be reverted with null by default and moved in last parameter like it was before - id, + chart: Chart, + classes: string, + powerRestrictionSegment: PowerRestrictionSegment, + groupID: string, + keyValues: DrawingKeys, + rotate: boolean, + isStripe: boolean, + isIncompatible: boolean, + isRestriction: boolean, + id: string, isElectricalSettings = false ) => { - // TODO : remove line when parameter fixed - if (id === undefined) id = null; + // Get the different axis values based on rotation + // x + const xValues = getAxisValues(powerRestrictionSegment, keyValues, rotate ? 1 : 0); + // y + const yValues = getAxisValues(powerRestrictionSegment, keyValues, rotate ? 0 : 1); - const width = rotate - ? chart.x(dataSimulation[`${keyValues[1]}_end`]) - - chart.x(dataSimulation[`${keyValues[1]}_start`]) - : chart.x(dataSimulation[`${keyValues[0]}_end`]) - - chart.x(dataSimulation[`${keyValues[0]}_start`]); + const width = chart.x(xValues.end) - chart.x(xValues.start); + const height = chart.y(yValues.end) - chart.y(yValues.start); - const height = rotate - ? chart.y(dataSimulation[`${keyValues[0]}_end`]) - - chart.y(dataSimulation[`${keyValues[0]}_start`]) - : chart.y(dataSimulation[`${keyValues[1]}_end`]) - - chart.y(dataSimulation[`${keyValues[1]}_start`]); - - let margin = 0; - if (isElectricalSettings) margin = 26; + const margin = isElectricalSettings ? 26 : 0; // prepare stripe pattern if (isStripe) { const stripe = chart.drawZone.select(`#${groupID}`); stripe - .append('pattern') .attr('id', `${id}`) .attr('patternUnits', 'userSpaceOnUse') @@ -53,32 +45,16 @@ const drawPowerRestriction = ( const drawZone = chart.drawZone.select(`#${groupID}`); + // add main rect for profile displayed drawZone - - // add main rect for profile displayed .append('rect') .attr('id', id) .attr('class', `rect main ${classes}`) - .datum(dataSimulation) + .datum(powerRestrictionSegment) .attr('fill', isStripe ? `url(#${id})` : '#333') .attr('stroke-width', 1) - .attr( - 'x', - chart.x( - rotate - ? dataSimulation[`${keyValues[1]}_start`] + margin - : dataSimulation[`${keyValues[0]}_start`] + margin - ) - ) - .attr( - 'y', - chart.y( - rotate - ? dataSimulation[`${keyValues[0]}_start`] + margin - : dataSimulation[`${keyValues[1]}_start`] + margin - ) - - height * -1 - ) + .attr('x', chart.x(xValues.start + margin)) + .attr('y', chart.y(yValues.start + margin) + height) .attr('width', width) .attr('height', height * -1); @@ -95,31 +71,8 @@ const drawPowerRestriction = ( .attr('transform', rotate ? 'translate(-20, -10)' : 'translate(-25, -5.5)') .attr('rx', 2) .attr('ry', 2) - .attr( - 'x', - chart.x( - rotate - ? dataSimulation[`${keyValues[1]}_middle`] + margin - : dataSimulation[`${keyValues[0]}_middle`] + margin - ) - ) - .attr( - 'y', - chart.y( - rotate - ? dataSimulation[`${keyValues[0]}_start`] + - margin - - (dataSimulation[`${keyValues[0]}_end`] + - margin - - dataSimulation[`${keyValues[0]}_middle`] - - margin) - : dataSimulation[`${keyValues[1]}_start`] - - (dataSimulation[`${keyValues[1]}_end`] - - dataSimulation[`${keyValues[1]}_middle`] - - margin) - ) - - height * -1 - ) + .attr('x', chart.x(xValues.middle + margin)) + .attr('y', chart.y(yValues.start - (yValues.end - yValues.middle - margin)) + height) .attr('width', rotate ? 40 : '3.2em') .attr('height', rotate ? 20 : '0.6em'); @@ -129,7 +82,7 @@ const drawPowerRestriction = ( .attr('class', `electricalProfileText ${classes}`) .attr('dominant-baseline', 'middle') .attr('text-anchor', 'middle') - .text(dataSimulation.seenRestriction) + .text(powerRestrictionSegment.seenRestriction) .attr('fill', '#333') .attr('font-size', 8) .attr('font-weight', 'bold') @@ -137,28 +90,11 @@ const drawPowerRestriction = ( 'x', chart.x( rotate - ? dataSimulation[`${keyValues[1]}_start`] + - (dataSimulation[`${keyValues[1]}_end`] - - dataSimulation[`${keyValues[1]}_middle`] + - margin) - : dataSimulation[`${keyValues[0]}_middle`] + margin + ? xValues.start + (xValues.end - xValues.middle + margin) + : xValues.middle + margin ) ) - .attr( - 'y', - chart.y( - rotate - ? dataSimulation[`${keyValues[0]}_start`] - - (dataSimulation[`${keyValues[0]}_end`] - - dataSimulation[`${keyValues[0]}_middle`] - - margin) - : dataSimulation[`${keyValues[1]}_start`] - - (dataSimulation[`${keyValues[1]}_end`] - - dataSimulation[`${keyValues[1]}_middle`] - - margin) - ) - - height * -1 - ); + .attr('y', chart.y(yValues.start - (yValues.end - yValues.middle - margin)) + height); } }; @@ -168,7 +104,7 @@ const drawPowerRestriction = ( drawZone .selectAll(`.${classes}`) .on('mouseover', (event) => { - const lastPosition = chart.x(dataSimulation.lastPosition); + const lastPosition = chart.x(powerRestrictionSegment.lastPosition); const pointerPositionX = pointer(event, event.currentTarget)[0]; const pointerPositionY = pointer(event, event.currentTarget)[1]; let popUpWidth; @@ -204,12 +140,7 @@ const drawPowerRestriction = ( .attr('ry', 4) .attr('transform', rotate ? 'translate(80, 0)' : `translate(${0 - popUpPosition}, -45)`) .attr('x', pointerPositionX) - .attr( - 'y', - rotate - ? pointerPositionY + margin - : chart.y(dataSimulation[`${keyValues[1]}_start`] + margin) - height * -1 - ) + .attr('y', rotate ? pointerPositionY + margin : chart.y(yValues.start + margin) + height) .attr('width', popUpWidth) .attr('height', 35); @@ -217,7 +148,7 @@ const drawPowerRestriction = ( let text; if (isRestriction) { - text = `${dataSimulation.seenRestriction} ${i18n.t('powerRestriction.used', { + text = `${powerRestrictionSegment.seenRestriction} ${i18n.t('powerRestriction.used', { ns: 'simulation', })}`; } else { @@ -233,15 +164,10 @@ const drawPowerRestriction = ( .text(text) .attr('transform', rotate ? `translate(88, 19)` : `translate(${8 - popUpPosition}, -27)`) .attr('x', pointerPositionX) - .attr( - 'y', - rotate - ? pointerPositionY + margin - : chart.y(dataSimulation[`${keyValues[1]}_start`] + margin) - height * -1 - ); + .attr('y', rotate ? pointerPositionY + margin : chart.y(yValues.start + margin) + height); }) + // add mouse-out interraction to remove pop-up .on('mouseout', () => { - // add mouse-out interraction to remove pop-up drawZone .select(`.${classes} `) .attr('transform', 'translate(0, 0)') diff --git a/front/src/modules/simulationResult/components/ChartHelpers/svgDefs.jsx b/front/src/modules/simulationResult/components/ChartHelpers/svgDefs.ts similarity index 95% rename from front/src/modules/simulationResult/components/ChartHelpers/svgDefs.jsx rename to front/src/modules/simulationResult/components/ChartHelpers/svgDefs.ts index dbd7688e181..c4eb9076782 100644 --- a/front/src/modules/simulationResult/components/ChartHelpers/svgDefs.jsx +++ b/front/src/modules/simulationResult/components/ChartHelpers/svgDefs.ts @@ -1,4 +1,4 @@ -const svgDefs = (defs) => { +const svgDefs = (defs: d3.Selection) => { const hatchSize = 4; const rotation = 45; diff --git a/front/src/modules/simulationResult/components/SpaceCurvesSlopes.tsx b/front/src/modules/simulationResult/components/SpaceCurvesSlopes.tsx index e5955a89c2d..748e82be42b 100644 --- a/front/src/modules/simulationResult/components/SpaceCurvesSlopes.tsx +++ b/front/src/modules/simulationResult/components/SpaceCurvesSlopes.tsx @@ -48,12 +48,14 @@ const drawAxisTitle = (chart: Chart) => { .text('m'); }; -const drawSpaceCurvesSlopesChartCurve = ( +const drawSpaceCurvesSlopesChartCurve = < + T extends GradientPosition | RadiusPosition | HeightPosition +>( chartLocal: Chart, classes: string, - data: GradientPosition[] | RadiusPosition[] | HeightPosition[], + data: T[], interpolation: 'curveLinear' | 'curveMonotoneX', - yAxisValue: string, + yAxisValue: keyof T, curveName: string ) => { drawCurve( diff --git a/front/src/modules/simulationResult/components/SpeedSpaceChart/d3Helpers.ts b/front/src/modules/simulationResult/components/SpeedSpaceChart/d3Helpers.ts index e58e0d66012..dd600590525 100644 --- a/front/src/modules/simulationResult/components/SpeedSpaceChart/d3Helpers.ts +++ b/front/src/modules/simulationResult/components/SpeedSpaceChart/d3Helpers.ts @@ -6,6 +6,7 @@ import * as d3 from 'd3'; import { createProfileSegment, createPowerRestrictionSegment, + DRAWING_KEYS, } from 'applications/operationalStudies/consts'; import { isEmpty } from 'lodash'; import { Chart, SpeedSpaceChart, SpeedSpaceSettingsType } from 'reducers/osrdsimulation/types'; @@ -163,7 +164,7 @@ function drawTrain( dataSimulation.slopesCurve, 'speedSpaceChart', 'curveLinear', - ['position', 'height'], + DRAWING_KEYS, 'slopes', rotate ); @@ -211,9 +212,7 @@ function drawTrain( `electricalProfiles_${index}`, segment, 'speedSpaceChart', - 'curveLinear', ['position', 'height'], - 'electrical_profiles', rotate, segment.isStriped, segment.isIncompatibleElectricalProfile, @@ -254,9 +253,7 @@ function drawTrain( `powerRestrictions_${index}`, source, 'speedSpaceChart', - 'curveLinear', - ['position', 'height'], - 'power_restrictions', + DRAWING_KEYS, rotate, source.isStriped, source.isIncompatiblePowerRestriction, diff --git a/front/src/utils/types.ts b/front/src/utils/types.ts index 0a2c9107255..b1f6e371d4b 100644 --- a/front/src/utils/types.ts +++ b/front/src/utils/types.ts @@ -2,3 +2,5 @@ export type ValueOf = T[keyof T]; export type ArrayElement = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; + +export type ArrayElementKeys = T extends (infer U)[] ? keyof U : null; diff --git a/front/yarn.lock b/front/yarn.lock index ca620fce979..48589215366 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -4381,6 +4381,13 @@ "@types/react" "*" csstype "^3.0.2" +"@types/react-modal@^3.16.0": + version "3.16.1" + resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.16.1.tgz#0d65e078b1290acfef8083b8cd92b7692e899720" + integrity sha512-HsH2E3luJ2hdVOAovq93x/r+CmWqbhwT8hXW05KMp3zzbSEZsBn4egnMGU/VLwScwlCRPTyZgIEPqLycaz5EOw== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.20": version "7.1.25" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88"