Skip to content

Commit 262a66c

Browse files
authored
Merge pull request #158 from GNS-Science/feature/dotted_lines
Feature/dotted lines
2 parents 1a52840 + 528507e commit 262a66c

File tree

6 files changed

+4869
-23
lines changed

6 files changed

+4869
-23
lines changed

jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
transform: {
1010
'^.+\\.(ts|js)x?$': 'ts-jest',
1111
},
12-
transformIgnorePatterns: ['<rootDir>/node_modules/(?!d3-array|internmap)'],
12+
transformIgnorePatterns: ['<rootDir>/node_modules/(?!d3-array|internmap|d3-interpolate|d3-scale|d3-selection|d3-shape|d3-axis|d3-color|d3-format|d3-time|d3-time-format)'],
1313
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/src'],
1414
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
1515
// All imported modules in your tests should be mocked automatically

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gns-science/toshi-nest",
3-
"version": "3.9.9",
3+
"version": "3.9.10",
44
"description": "The toshi-nest is for fledgling work e.g. reusable Node components to share across TUI and other projects",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.esm.js",
@@ -106,6 +106,7 @@
106106
"@visx/group": "^2.1.0",
107107
"@visx/heatmap": "^3.0.0",
108108
"@visx/legend": "^2.2.2",
109+
"@visx/marker": "^3.5.0",
109110
"@visx/responsive": "^2.8.0",
110111
"@visx/scale": "^2.2.2",
111112
"@visx/shape": "^2.11.1",

src/GroupCurveChart/GroupCurveChart.stories.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import GroupCurveChart from './GroupCurveChart';
55
import { curveGroup4, curveGroup3, curveGroup2, curveGroup1 } from '../__tests__/testData/uncertaintyTestData';
66
import spectralAccelUncertaintyTestData from '../__tests__/testData/spectralAccelUncertaintyTestData';
77
import spectralAccelUncertaintyLog from '../__tests__/testData/spectralAccelUncertaintyLog';
8+
import { multipleImtData } from '../__tests__/testData/hazardChartMultipleSATestData';
89

910
export default {
1011
title: 'Charts/GroupCurveChart',
@@ -21,6 +22,7 @@ export const UncertaintyFalse = Template.bind({});
2122
export const SpectralAccelUncertaintyTrue = Template.bind({});
2223
export const SpectralAccelUncertaintyFalse = Template.bind({});
2324
export const SpectralAccelLog = Template.bind({});
25+
export const MultipleSA = Template.bind({});
2426

2527
Primary.args = {
2628
scaleType: 'log',
@@ -200,3 +202,25 @@ SpectralAccelLog.args = {
200202
spectral: true,
201203
timePeriod: 100,
202204
};
205+
206+
MultipleSA.args = {
207+
scaleType: 'log',
208+
xLimits: [1e-2, 10],
209+
yLimits: [1e-6, 1],
210+
xLabel: 'Acceleration(g)',
211+
yLabel: 'Annual Probability of Exceedance',
212+
gridColor: '#efefef',
213+
backgroundColor: '#f3f6f4',
214+
numTickX: 5,
215+
numTickY: 5,
216+
width: 600,
217+
curves: multipleImtData,
218+
tooltip: false,
219+
crosshair: false,
220+
heading: 'Group Curve Chart for Hazard',
221+
subHeading: 'WLG 250',
222+
poe: 0.02,
223+
uncertainty: false,
224+
spectral: false,
225+
timePeriod: 100,
226+
};

src/GroupCurveChart/GroupCurveChart.tsx

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
1111
import { Line } from '@visx/shape';
1212
import { localPoint } from '@visx/event';
1313
import { bisector } from 'd3-array';
14-
import { LegendOrdinal } from '@visx/legend';
14+
import { Legend, LegendItem, LegendLabel } from '@visx/legend';
1515
import { GlyphSquare } from '@visx/glyph';
1616

1717
import { GroupCurveChartProps, Datum } from './groupCurveChart.types';
1818
import { getAreaData, getSortedMeanCurves } from './groupCurveChart.service';
1919
import PlotHeadings from '../common/PlotHeadings';
2020
import { HazardColorScale } from '../types/hazardCharts.types';
2121
import AxisLabel from '../common/AxisLabel';
22+
import { Typography } from '@mui/material';
2223

2324
const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartProps) => {
2425
const {
@@ -90,19 +91,7 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
9091
return colorScale;
9192
}, [curves]);
9293

93-
const ordinalColorScale = useMemo(() => {
94-
if (!spectral) {
95-
return scaleOrdinal({
96-
domain: !poe ? [...curvesDomain.domain] : [...curvesDomain.domain, !spectral && `POE ${poe * 100}% (${timePeriod} Yrs)`],
97-
range: !poe ? [...curvesDomain.range] : [...curvesDomain.range, !spectral && '#989C9C'],
98-
});
99-
} else {
100-
return scaleOrdinal({
101-
domain: [...curvesDomain.domain],
102-
range: [...curvesDomain.range],
103-
});
104-
}
105-
}, [curvesDomain, poe, spectral, timePeriod]);
94+
const legendGlyphSize = 15;
10695

10796
const poeLine = useMemo(() => {
10897
const getPoE = (poeValue: number) => {
@@ -120,6 +109,73 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
120109
detectBounds: true,
121110
});
122111

112+
const locations = [...new Set(Object.keys(curves).map((k) => k.split(' ')[2]))];
113+
const vs30s = [...new Set(Object.keys(curves).map((k) => k.split(' ')[0]))];
114+
const imts = [...new Set(Object.keys(curves).map((k) => k.split(' ')[1]))];
115+
116+
const locationsWithVs30 = locations.map((location) => {
117+
return vs30s.map((vs30) => {
118+
return curvesDomain.domain.filter((k) => ~k.indexOf(vs30) && ~k.indexOf(location));
119+
});
120+
});
121+
122+
const generateLegendRange = () => {
123+
const domain = locationsWithVs30.flat(1);
124+
125+
return domain
126+
.map((d, i) => {
127+
switch (d.length) {
128+
case 1:
129+
return [<rect key={'a' + i} fill={curvesDomain.range[i]} width={legendGlyphSize} height={legendGlyphSize / 5} y={7} />];
130+
case 2:
131+
return [
132+
<rect key={'a' + i} fill={curvesDomain.range[i]} width={legendGlyphSize} height={legendGlyphSize / 5} y={7} />,
133+
<line key={'b' + i} stroke={curvesDomain.range[i]} x1="0" y1="10" x2="250" y2="10" strokeDasharray="4,5" y={7} strokeWidth={legendGlyphSize / 5} />,
134+
];
135+
case 3:
136+
return [
137+
<rect key={'a' + i} fill={curvesDomain.range[i]} width={legendGlyphSize} height={legendGlyphSize / 5} y={7} />,
138+
<line key={'b' + i} stroke={curvesDomain.range[i]} x1="0" y1="10" x2="250" y2="10" strokeDasharray="4,5" y={7} strokeWidth={legendGlyphSize / 5} />,
139+
<line key={'c' + i} stroke={curvesDomain.range[i]} x1="0" y1="10" x2="250" y2="10" strokeDasharray="1,3" y={7} strokeWidth={legendGlyphSize / 5} />,
140+
];
141+
default:
142+
return [];
143+
}
144+
})
145+
.flat();
146+
};
147+
148+
const strokeDashArray = (index: number) => {
149+
const values = ['0', '4,5', '1,3'];
150+
const repeatCount = imts.length === 1 ? 1 : imts.length === 2 ? 2 : imts.length === 3 ? 3 : 1;
151+
const valuesIndex = (index % repeatCount) % values.length;
152+
153+
return values[valuesIndex];
154+
};
155+
156+
function generateColorArray(length: number, interval: number) {
157+
const array: string[] = [];
158+
let valuesIndex = 0;
159+
for (let i = 0; i < length; i++) {
160+
if (i % interval === 0) {
161+
array.push(curvesDomain.range[valuesIndex]);
162+
valuesIndex = (valuesIndex + 1) % curvesDomain.range.length;
163+
} else {
164+
array.push(array[i - 1]);
165+
}
166+
}
167+
return array;
168+
}
169+
170+
const strokeColorArray = generateColorArray(Object.keys(curves).length, imts.length);
171+
172+
const legendRange = generateLegendRange();
173+
174+
const shapeScale = scaleOrdinal<string, React.FC | React.ReactNode>({
175+
domain: locationsWithVs30.flat(2),
176+
range: legendRange,
177+
});
178+
123179
const {
124180
showTooltip,
125181
tooltipOpen,
@@ -221,8 +277,9 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
221277
data={curves[key][curveType].data}
222278
x={(d) => xScale(d[0])}
223279
y={(d) => yScale(d[1])}
224-
stroke={curves[key][curveType].strokeColor ?? ''}
280+
stroke={strokeColorArray[index] ?? ''}
225281
strokeOpacity={curves[key][curveType].strokeOpacity ?? 1}
282+
strokeDasharray={strokeDashArray(index)}
226283
defined={(d, index) => {
227284
if (scaleType === 'log' && index === 0) {
228285
return false;
@@ -240,7 +297,7 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
240297
clipAboveTo={0}
241298
clipBelowTo={yMax}
242299
aboveAreaProps={{
243-
fill: curves[key]['upper1'].strokeColor,
300+
fill: strokeColorArray[index],
244301
fillOpacity: 0.4,
245302
}}
246303
defined={(d, index) => {
@@ -272,8 +329,10 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
272329
data={curves[key]['mean'].data}
273330
x={(d) => xScale(d[0])}
274331
y={(d) => yScale(d[1])}
275-
stroke={curves[key]['mean'].strokeColor ?? ''}
332+
markerMid="url(#marker-x)"
333+
stroke={strokeColorArray[index] ?? ''}
276334
strokeOpacity={curves[key]['mean'].strokeOpacity ?? 1}
335+
strokeDasharray={strokeDashArray(index)}
277336
defined={(d, index) => {
278337
if (scaleType === 'log' && index === 0) {
279338
return false;
@@ -320,8 +379,28 @@ const GroupCurveChart: React.FC<GroupCurveChartProps> = (props: GroupCurveChartP
320379
)}
321380
</Group>
322381
</svg>
323-
<div style={{ width: width * 0.24, height: 100, position: 'absolute', top: marginTop, left: width * 0.7, display: 'flex' }}>
324-
<LegendOrdinal direction="column" scale={ordinalColorScale} shape="line" style={{ fontSize: width * 0.016 <= 13 ? 13 : width * 0.015 }} shapeHeight={width * 0.02} />
382+
<div style={{ width: width * 0.24, height: 100, position: 'absolute', top: marginTop, left: width * 0.8, display: 'flex' }}>
383+
<Legend scale={shapeScale}>
384+
{(labels) => (
385+
<div style={{ display: 'flex', flexDirection: 'column' }}>
386+
{labels.map((label, i) => {
387+
// const color = curvesDomain.range[i];
388+
const shape = shapeScale(label.datum);
389+
const isValidElement = React.isValidElement(shape);
390+
return (
391+
<LegendItem key={`legend-quantile-${i}`} margin="0 4px 0 0" flexDirection="row">
392+
<svg width={legendGlyphSize} height={legendGlyphSize} style={{ margin: '4px' }}>
393+
{isValidElement ? React.cloneElement(shape as React.ReactElement) : React.createElement(shape as React.ComponentType<{ fill: string }>)}
394+
</svg>
395+
<LegendLabel align="left" margin={0} size={4}>
396+
<Typography fontSize={'smaller'}>{label.text}</Typography>
397+
</LegendLabel>
398+
</LegendItem>
399+
);
400+
})}
401+
</div>
402+
)}
403+
</Legend>
325404
</div>
326405
</div>
327406
</>

0 commit comments

Comments
 (0)