Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { useEffect, useState } from 'react';
import { ISymbologyDialogProps } from '../symbologyDialog';
import Canonical from './types/Canonical';
import Categorized from './types/Categorized';
import Graduated from './types/Graduated';
import Heatmap from './types/Heatmap';
import SimpleSymbol from './types/SimpleSymbol';
import { useGetProperties } from '../hooks/useGetProperties';
import {
getColorCodeFeatureAttributes,
getNumericFeatureAttributes
} from '../../../tools';

const VectorRendering = ({
model,
Expand All @@ -28,20 +34,38 @@ const VectorRendering = ({
return;
}

const { featureProperties } = useGetProperties({
layerId,
model: model
});

useEffect(() => {
let renderType = layer.parameters?.symbologyState?.renderType;
if (!renderType) {
renderType = layer.type === 'HeatmapLayer' ? 'Heatmap' : 'Single Symbol';
}
setSelectedRenderType(renderType);

const vectorLayerOptions = ['Single Symbol', 'Heatmap'];

if (
Object.keys(getColorCodeFeatureAttributes(featureProperties)).length > 0
) {
vectorLayerOptions.push('Canonical');
}
if (
Object.keys(getNumericFeatureAttributes(featureProperties)).length > 0
) {
vectorLayerOptions.push('Graduated', 'Categorized');
}

const options: Record<string, string[]> = {
VectorLayer: ['Single Symbol', 'Graduated', 'Categorized', 'Heatmap'],
VectorLayer: vectorLayerOptions,
VectorTileLayer: ['Single Symbol'],
HeatmapLayer: ['Single Symbol', 'Graduated', 'Categorized', 'Heatmap']
};
setRenderTypeOptions(options[layer.type]);
}, []);
}, [featureProperties]);

useEffect(() => {
switch (selectedRenderType) {
Expand Down Expand Up @@ -78,6 +102,17 @@ const VectorRendering = ({
/>
);
break;
case 'Canonical':
RenderComponent = (
<Canonical
model={model}
state={state}
okSignalPromise={okSignalPromise}
cancel={cancel}
layerId={layerId}
/>
);
break;
case 'Heatmap':
RenderComponent = (
<Heatmap
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { IVectorLayer } from '@jupytergis/schema';
import { ExpressionValue } from 'ol/expr/expression';
import React, { useEffect, useRef, useState } from 'react';
import { getColorCodeFeatureAttributes } from '../../../../tools';
import { useGetProperties } from '../../hooks/useGetProperties';
import { ISymbologyDialogProps } from '../../symbologyDialog';
import ValueSelect from '../components/ValueSelect';

const Canonical = ({
model,
state,
okSignalPromise,
cancel,
layerId
}: ISymbologyDialogProps) => {
const selectedValueRef = useRef<string>();

const [selectedValue, setSelectedValue] = useState('');
const [attributes, setAttributes] = useState<Record<string, Set<string>>>({});

if (!layerId) {
return;
}
const layer = model.getLayer(layerId);
if (!layer?.parameters) {
return;
}
const { featureProperties } = useGetProperties({
layerId,
model: model
});

useEffect(() => {
okSignalPromise.promise.then(okSignal => {
okSignal.connect(handleOk, this);
});

return () => {
okSignalPromise.promise.then(okSignal => {
okSignal.disconnect(handleOk, this);
});
};
}, [selectedValue]);

useEffect(() => {
// Filter for hex color code attributes
const hexAttributes = getColorCodeFeatureAttributes(featureProperties);

setAttributes(hexAttributes);

const layerParams = layer.parameters as IVectorLayer;
const value =
layerParams.symbologyState?.value ?? Object.keys(hexAttributes)[0];

setSelectedValue(value);
}, [featureProperties]);

useEffect(() => {
selectedValueRef.current = selectedValue;
}, [selectedValue]);

const handleOk = () => {
if (!layer.parameters) {
return;
}

const colorExpr: ExpressionValue[] = ['get', selectedValue];
const newStyle = { ...layer.parameters.color };
newStyle['fill-color'] = colorExpr;
newStyle['stroke-color'] = colorExpr;
newStyle['circle-fill-color'] = colorExpr;

const symbologyState = {
renderType: 'Canonical',
value: selectedValueRef.current
};

layer.parameters.symbologyState = symbologyState;
layer.parameters.color = newStyle;
if (layer.type === 'HeatmapLayer') {
layer.type = 'VectorLayer';
}

model.sharedModel.updateLayer(layerId, layer);
cancel();
};

return (
<div className="jp-gis-layer-symbology-container">
<ValueSelect
featureProperties={attributes}
selectedValue={selectedValue}
setSelectedValue={setSelectedValue}
/>
</div>
);
};

export default Canonical;
46 changes: 36 additions & 10 deletions packages/base/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,27 +855,53 @@ export const stringToArrayBuffer = async (
return await base64Response.arrayBuffer();
};

export const getNumericFeatureAttributes = (
featureProperties: Record<string, Set<any>>
) => {
// We only want number values here
const filteredRecord: Record<string, Set<number>> = {};
const getFeatureAttributes = <T>(
featureProperties: Record<string, Set<any>>,
predicate: (key: string, value: any) => boolean = (key: string, value) => true
): Record<string, Set<T>> => {
const filteredRecord: Record<string, Set<T>> = {};

for (const [key, set] of Object.entries(featureProperties)) {
const firstValue = set.values().next().value;
const isValid = predicate(key, firstValue);

// Check if the first value is a string that cannot be parsed as a number
const isInvalidString =
typeof firstValue === 'string' && isNaN(Number(firstValue));

if (!isInvalidString) {
if (isValid) {
filteredRecord[key] = set;
}
}

return filteredRecord;
};

/**
* Get attributes of the feature which are numeric.
*
* @param featureProperties - Attributes of a feature.
* @returns - Attributes which are numeric.
*/
export const getNumericFeatureAttributes = (
featureProperties: Record<string, Set<any>>
): Record<string, Set<number>> => {
return getFeatureAttributes<number>(featureProperties, (_: string, value) => {
return !(typeof value === 'string' && isNaN(Number(value)));
});
};

/**
* Get attributes of the feature which look like hex color codes.
*
* @param featureProperties - Attributes of a feature.
* @returns - Attributes which look like hex color codes.
*/
export const getColorCodeFeatureAttributes = (
featureProperties: Record<string, Set<any>>
): Record<string, Set<string>> => {
return getFeatureAttributes<string>(featureProperties, (_, value) => {
const regex = new RegExp('^#[0-9a-f]{6}$');
return typeof value === 'string' && regex.test(value);
});
};

export function downloadFile(
content: BlobPart,
fileName: string,
Expand Down
Loading