Skip to content

Commit

Permalink
ui-speedspacechart: add declivities layer in gev v2
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainValls committed Jul 11, 2024
1 parent dfce11d commit 46ceff3
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 24 deletions.
69 changes: 63 additions & 6 deletions ui-speedspacechart/src/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getAdaptiveHeight,
positionOnGraphScale,
getLinearLayerMarginTop,
slopesValues,
} from '../components/utils';
import type { Store } from '../types/chartTypes';
import type { ConsolidatedPositionSpeedTime } from '../types/simulationTypes';
Expand Down Expand Up @@ -57,12 +58,34 @@ const store: Store = {
};

describe('getGraphOffsets', () => {
it('should return the correct width and height offsets', () => {
const width = 100;
const height = 200;
const { WIDTH_OFFSET, HEIGHT_OFFSET } = getGraphOffsets(width, height);
expect(WIDTH_OFFSET).toBe(40);
expect(HEIGHT_OFFSET).toBe(120);
const width = 200;
const height = 150;

it('should return correct width and height offsets when declivities is true', () => {
const result = getGraphOffsets(width, height, true);

expect(result).toEqual({
WIDTH_OFFSET: 98,
HEIGHT_OFFSET: 70,
});
});

it('should return correct width and height offsets when declivities is false', () => {
const result = getGraphOffsets(width, height, false);

expect(result).toEqual({
WIDTH_OFFSET: 140,
HEIGHT_OFFSET: 70,
});
});

it('should return correct width and height offsets when declivities is undefined', () => {
const result = getGraphOffsets(width, height);

expect(result).toEqual({
WIDTH_OFFSET: 140,
HEIGHT_OFFSET: 70,
});
});
});

Expand Down Expand Up @@ -94,6 +117,40 @@ describe('maxPositionValues', () => {
});
});

describe('slopesValues', () => {
it('should return correct minGradient, maxGradient, slopesRange, and maxPosition', () => {
const storeWithSlopes: Store = {
...store,
slopes: [
{ gradient: 1, position: 10 },
{ gradient: 3, position: 20 },
{ gradient: 2, position: 15 },
{ gradient: 5, position: 25 },
],
};
const result = slopesValues(storeWithSlopes);
expect(result).toEqual({
minGradient: 1,
maxGradient: 5,
slopesRange: 4,
maxPosition: 25,
});
});
it('should handle empty slopes array', () => {
const storeWithoutSlopes: Store = {
...store,
slopes: [],
};
const result = slopesValues(storeWithoutSlopes);
expect(result).toEqual({
minGradient: Infinity,
maxGradient: -Infinity,
slopesRange: -Infinity,
maxPosition: -Infinity,
});
});
});

describe('clearCanvas', () => {
it('should clear the canvas', () => {
const fn = () => vi.fn();
Expand Down
41 changes: 33 additions & 8 deletions ui-speedspacechart/src/components/SpeedSpaceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import PowerRestrictionsLayer from './layers/PowerRestrictionsLayer';
import SettingsPanel from './common/SettingsPanel';
import InteractionButtons from './common/InteractionButtons';
import { LINEAR_LAYERS_HEIGHTS } from './const';
import TickLayerYRight from './layers/TickLayerYRight';
import DeclivityLayer from './layers/DeclivityLayer';
import { MARGINS } from './const';

export type SpeedSpaceChartProps = {
width: number;
Expand Down Expand Up @@ -85,9 +88,17 @@ const SpeedSpaceChart = ({
isSettingsPanelOpened: false,
});

const { WIDTH_OFFSET, HEIGHT_OFFSET } = getGraphOffsets(width, height);
const { WIDTH_OFFSET, HEIGHT_OFFSET } = getGraphOffsets(
width,
height,
store.layersDisplay.declivities
);
const dynamicHeight = getAdaptiveHeight(height, store.layersDisplay);
const dynamicHeightOffset = getAdaptiveHeight(HEIGHT_OFFSET, store.layersDisplay);
const { OFFSET_RIGHT_AXIS } = MARGINS;
const adjustedWidthRightAxis = store.layersDisplay.declivities
? width - OFFSET_RIGHT_AXIS
: width;

const [showDetailsBox, setShowDetailsBox] = useState(false);

Expand Down Expand Up @@ -136,11 +147,17 @@ const SpeedSpaceChart = ({
}}
tabIndex={0}
>
<div className="flex justify-end absolute base-margin-top" style={{ width: width }}>
<div
className="flex justify-end absolute base-margin-top"
style={{ width: adjustedWidthRightAxis }}
>
<InteractionButtons reset={reset} openSettingsPanel={openSettingsPanel} store={store} />
</div>
{store.isSettingsPanelOpened && (
<div className="flex justify-end absolute ml-2 base-margin-top" style={{ width: width }}>
<div
className="flex justify-end absolute ml-2 base-margin-top"
style={{ width: adjustedWidthRightAxis }}
>
<SettingsPanel
color={backgroundColor}
store={store}
Expand All @@ -149,10 +166,13 @@ const SpeedSpaceChart = ({
/>
</div>
)}
{store.layersDisplay.declivities && (
<DeclivityLayer width={WIDTH_OFFSET} height={HEIGHT_OFFSET} store={store} />
)}
<CurveLayer width={WIDTH_OFFSET} height={HEIGHT_OFFSET} store={store} />
<AxisLayerY width={width} height={height} store={store} />
<MajorGridY width={width} height={height} store={store} />
<AxisLayerX width={width} height={height} store={store} />
<AxisLayerY width={adjustedWidthRightAxis} height={height} store={store} />
<MajorGridY width={adjustedWidthRightAxis} height={height} store={store} />
<AxisLayerX width={adjustedWidthRightAxis} height={height} store={store} />
{store.layersDisplay.steps && (
<>
<StepLayer width={WIDTH_OFFSET} height={HEIGHT_OFFSET} store={store} />
Expand All @@ -179,15 +199,20 @@ const SpeedSpaceChart = ({
store={store}
/>
)}
<TickLayerX width={width} height={dynamicHeight} store={store} />
<TickLayerX width={adjustedWidthRightAxis} height={dynamicHeight} store={store} />

{store.layersDisplay.declivities && (
<TickLayerYRight width={width} height={height} store={store} />
)}
<ReticleLayer
width={width}
width={adjustedWidthRightAxis}
height={dynamicHeight}
heightOffset={dynamicHeightOffset}
store={store}
showDetailsBox={showDetailsBox}
setShowDetailsBox={setShowDetailsBox}
/>

<FrontInteractivityLayer
width={WIDTH_OFFSET}
height={dynamicHeightOffset}
Expand Down
6 changes: 6 additions & 0 deletions ui-speedspacechart/src/components/const.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Store } from '../types/chartTypes';

export const SLOPE_FILL_COLOR = '#CFDDCE';

export const RIGHT_TICK_HEIGHT_OFFSET = 2;

export const MARGINS = {
MARGIN_LEFT: 48,
MARGIN_RIGHT: 12,
Expand All @@ -8,6 +12,8 @@ export const MARGINS = {
CURVE_MARGIN_TOP: 40,
CURVE_MARGIN_SIDES: 16,
ELECTRICAL_PROFILES_MARGIN_TOP: 8,
RIGHT_TICK_MARGINS: 60,
OFFSET_RIGHT_AXIS: 42,
};

export const LINEAR_LAYERS_HEIGHTS = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { clearCanvas, slopesValues, maxPositionValues } from '../../utils';
import type { Store } from '../../../types/chartTypes';
import { MARGINS } from '../../const';
import { SLOPE_FILL_COLOR } from '../../../components/const';

const { CURVE_MARGIN_SIDES, MARGIN_TOP, MARGIN_BOTTOM, RIGHT_TICK_MARGINS } = MARGINS;

export const drawDeclivity = (
ctx: CanvasRenderingContext2D,
width: number,
height: number,
store: Store
) => {
const { slopes, ratioX, leftOffset } = store;

const { maxGradient } = slopesValues(store);
const { maxPosition } = maxPositionValues(store);

if (!slopes || slopes.length === 0) {
console.error('Slopes data is missing or empty.');
return;
}

clearCanvas(ctx, width, height);

ctx.save();
ctx.translate(leftOffset, 0);

// Calculate total height available for drawing, excluding the margins
const availableHeight = height - MARGIN_TOP - MARGIN_BOTTOM - RIGHT_TICK_MARGINS / 2;

// Calculate the vertical center of the chart
const centerY = MARGIN_TOP + RIGHT_TICK_MARGINS / 2 + availableHeight / 2;

ctx.fillStyle = SLOPE_FILL_COLOR;

try {
const coef = ((width - CURVE_MARGIN_SIDES) / maxPosition) * ratioX;

for (let i = 0; i < slopes.length - 1; i++) {
const current = slopes[i];
const next = slopes[i + 1];

const x1 = current.position * coef + CURVE_MARGIN_SIDES / 2;
const x2 = next.position * coef + CURVE_MARGIN_SIDES / 2;

const rectWidth = x2 - x1;

const rectHeight = (current.gradient / maxGradient) * (availableHeight / 2);

const rectY = current.gradient >= 0 ? centerY - rectHeight : centerY;

ctx.fillRect(x1, rectY, rectWidth, Math.abs(rectHeight));
}
} catch (error) {
console.error('Error while drawing declivity:', error);
}
ctx.restore();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { clearCanvas, slopesValues } from '../../utils';
import type { Store } from '../../../types/chartTypes';
import { MARGINS, RIGHT_TICK_HEIGHT_OFFSET } from '../../const';

const { MARGIN_LEFT, MARGIN_TOP, MARGIN_BOTTOM, RIGHT_TICK_MARGINS } = MARGINS;

export const drawTickYRight = (
ctx: CanvasRenderingContext2D,
width: number,
height: number,
store: Store
) => {
clearCanvas(ctx, width, height);

const { minGradient, maxGradient } = slopesValues(store);

// Calculate total height available for ticks excluding the margins
const availableHeight = height - MARGIN_TOP - MARGIN_BOTTOM - RIGHT_TICK_MARGINS;

const tickSpacing = availableHeight / 12; // 12 intervals for 13 ticks

// Calculate the vertical center of the chart
const centerY =
MARGIN_TOP + RIGHT_TICK_MARGINS / 2 + availableHeight / 2 + RIGHT_TICK_HEIGHT_OFFSET;

const textOffsetX = width - MARGIN_LEFT + 10;
const tickWidth = 6;

ctx.font = 'normal 12px IBM Plex Sans';
ctx.lineWidth = 0.5;

// Calculate gradient step to avoid decimals
const maxAbsGradient = Math.max(maxGradient, minGradient);
const roundedMaxAbsGradient = Math.ceil(maxAbsGradient / 6) * 6;
const gradientStep = roundedMaxAbsGradient / 6;

ctx.beginPath();
for (let i = -6; i <= 6; i++) {
const tickValue = i * gradientStep;
const tickY = centerY - i * tickSpacing;

ctx.moveTo(width - MARGIN_LEFT - tickWidth, tickY);
ctx.lineTo(width - MARGIN_LEFT, tickY);
ctx.strokeStyle = 'rgb(121, 118, 113)';

ctx.textAlign = 'left';
const text = tickValue.toString();
const textPositionYRight = tickY + 4;
const opacity = 1;

ctx.fillStyle = `rgba(182, 179, 175, ${opacity})`;
ctx.fillText(text, textOffsetX, textPositionYRight);

const maxTickY = centerY - 6 * tickSpacing;
ctx.fillText('‰', width - MARGIN_LEFT, height / 12); // 12 ticks intervals
console.log({ maxTickY, tickSpacing, height }, 'for ticks');
}
ctx.stroke();

// prevent overlapping with margin top
ctx.clearRect(0, 0, width, MARGIN_TOP);
ctx.clearRect(width - MARGIN_LEFT, height - MARGIN_BOTTOM, width, MARGIN_BOTTOM);
ctx.clearRect(0, height - MARGIN_BOTTOM + 6, width - MARGIN_LEFT, MARGIN_BOTTOM);
};
9 changes: 8 additions & 1 deletion ui-speedspacechart/src/components/layers/CurveLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import type { Store } from '../../types/chartTypes';
import { drawCurve } from '../helpers/drawElements/curve';
import { useCanvas } from '../hooks';
import cx from 'classnames';

type CurveLayerProps = {
width: number;
Expand All @@ -13,7 +14,13 @@ const CurveLayer = ({ width, height, store }: CurveLayerProps) => {
const canvas = useCanvas(drawCurve, width, height, store);

return (
<canvas id="curve-layer" className="absolute" ref={canvas} width={width} height={height} />
<canvas
id="curve-layer"
className={cx('absolute rounded-t-xl', { 'bg-white-25': !store.layersDisplay.declivities })}
ref={canvas}
width={width}
height={height}
/>
);
};

Expand Down
27 changes: 27 additions & 0 deletions ui-speedspacechart/src/components/layers/DeclivityLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import type { Store } from '../../types/chartTypes';
import { drawDeclivity } from '../helpers/drawElements/declivity';
import { useCanvas } from '../hooks';
import cx from 'classnames';

type DeclivityLayerProps = {
width: number;
height: number;
store: Store;
};

const DeclivityLayer = ({ width, height, store }: DeclivityLayerProps) => {
const canvas = useCanvas(drawDeclivity, width, height, store);

return (
<canvas
id="declivity-layer"
className={cx('absolute rounded-t-xl', { 'bg-white-25': store.layersDisplay.declivities })}
ref={canvas}
width={width}
height={height}
/>
);
};

export default DeclivityLayer;
Loading

0 comments on commit 46ceff3

Please sign in to comment.