Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion demo/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const Index = () => {
title={'@carbonplan/maps'}
/>
<Box sx={{ position: 'absolute', top: 0, bottom: 0, width: '100%' }}>
<Map zoom={2} center={[0, 0]} debug={debug}>
<Map zoom={2} center={[-10, 10]} debug={debug}>
<Fill
color={theme.rawColors.background}
source={bucket + 'basemaps/ocean'}
Expand All @@ -59,7 +59,10 @@ const Index = () => {
backgroundColor={theme.colors.background}
fontFamily={theme.fonts.mono}
fontSize={'14px'}
minRadius={300}
maxRadius={2000}
initialRadius={1000}
// mode={'rectangle'} // options: 'circle', 'rectangle', defaults to 'circle'
/>
)}
<Raster
Expand Down
8 changes: 4 additions & 4 deletions src/region/region-picker/circle-picker/circle-renderer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { select } from 'd3-selection'
import { FLOATING_HANDLE, SHOW_RADIUS_GUIDELINE } from '../constants'
import { getPathMaker, project } from './utils'
import { getPathMaker, project } from '../utils'
import {
area,
convertArea,
Expand All @@ -12,7 +12,7 @@ import {
circle as turfCircle,
point,
} from '@turf/turf'
import CursorManager from './cursor-manager'
import CursorManager from '../cursor-manager'

const POLES = [point([0, -90]), point([0, 90])]
const abbreviations = {
Expand Down Expand Up @@ -132,7 +132,7 @@ export default function CircleRenderer({

const onMouseUp = () => {
onIdle(circle)
setCursor({ draggingCircle: false })
setCursor({ draggingRegion: false })
map.off('mousemove', onMouseMove)
map.off('touchmove', onMouseMove)
map.dragPan.enable()
Expand Down Expand Up @@ -160,7 +160,7 @@ export default function CircleRenderer({
lng: lngLat.lng - center.lng,
lat: lngLat.lat - center.lat,
}
setCursor({ draggingCircle: true })
setCursor({ draggingRegion: true })
svgCircle.style('pointer-events', 'none')
svgHandle.style('pointer-events', 'none')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export default function CursorManager(map) {
let mouseState = {
onHandle: false,
draggingHandle: false,
onCircle: false,
draggingCircle: false,
onRegion: false,
draggingRegion: false,
}

return function setCursor(newState) {
Expand All @@ -17,7 +17,7 @@ export default function CursorManager(map) {

if (mouseState.onHandle || mouseState.draggingHandle)
canvas.style.cursor = 'ew-resize'
else if (mouseState.onCircle || mouseState.draggingCircle)
else if (mouseState.onRegion || mouseState.draggingRegion)
canvas.style.cursor = 'move'
else canvas.style.cursor = originalStyle
}
Expand Down
71 changes: 48 additions & 23 deletions src/region/region-picker/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useRef, useCallback, useEffect } from 'react'
import CirclePicker from './circle-picker'
import RectanglePicker from './rectangle-picker'
import { UPDATE_STATS_ON_DRAG } from './constants'
import { distance } from '@turf/turf'
import { v4 as uuidv4 } from 'uuid'
Expand Down Expand Up @@ -46,8 +47,7 @@ function getInitialCenter(map, center) {
}
}

// TODO:
// - accept mode (only accept mode="circle" to start)
// only accept mode="circle" or mode="rectangle" to start
function RegionPicker({
backgroundColor,
color,
Expand All @@ -58,6 +58,7 @@ function RegionPicker({
initialCenter: initialCenterProp,
minRadius,
maxRadius,
mode = 'circle',
}) {
const { map } = useMap()
const id = useRef(uuidv4())
Expand All @@ -78,34 +79,58 @@ function RegionPicker({
}
}, [])

const handleCircle = useCallback((circle) => {
if (!circle) return
setRegion(circle)
setCenter(circle.properties.center)
const handleShape = useCallback((shape) => {
if (!shape) return
setRegion(shape)
setCenter(shape.properties.center)
}, [])

// TODO: consider extending support for degrees and radians
if (!['kilometers', 'miles'].includes(units)) {
throw new Error('Units must be one of miles, kilometers')
}

return (
<CirclePicker
id={id.current}
map={map}
center={initialCenter.current}
radius={initialRadius.current}
onDrag={UPDATE_STATS_ON_DRAG ? handleCircle : undefined}
onIdle={handleCircle}
backgroundColor={backgroundColor}
color={color}
units={units}
fontFamily={fontFamily}
fontSize={fontSize}
maxRadius={maxRadius}
minRadius={minRadius}
/>
)
if (mode == 'circle') {
Comment thread
Jakidxav marked this conversation as resolved.
Outdated
return (
<CirclePicker
key={`${mode}-picker`}
id={id.current}
center={initialCenter.current}
radius={initialRadius.current}
onDrag={UPDATE_STATS_ON_DRAG ? handleShape : undefined}
onIdle={handleShape}
backgroundColor={backgroundColor}
color={color}
units={units}
fontFamily={fontFamily}
fontSize={fontSize}
maxRadius={maxRadius}
minRadius={minRadius}
/>
)
} else if (mode == 'rectangle') {
Comment thread
Jakidxav marked this conversation as resolved.
Outdated
return (
<RectanglePicker
key={`${mode}-picker`}
id={id.current}
center={initialCenter.current}
radius={initialRadius.current}
onDrag={UPDATE_STATS_ON_DRAG ? handleShape : undefined}
onIdle={handleShape}
backgroundColor={backgroundColor}
color={color}
units={units}
fontFamily={fontFamily}
fontSize={fontSize}
maxRadius={maxRadius}
minRadius={minRadius}
/>
)
} else {
throw new ValueError(
Comment thread
Jakidxav marked this conversation as resolved.
Outdated
"RegionPicker `mode` must be one of ['circle', 'rectangle']"
)
}
}

export default RegionPicker
108 changes: 108 additions & 0 deletions src/region/region-picker/rectangle-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useState, useEffect } from 'react'
Comment thread
Jakidxav marked this conversation as resolved.
Outdated
import { useMap } from '../../../map-provider'
import RectangleRenderer from './rectangle-renderer'

import { HANDLE_RADIUS } from './rectangle-renderer'

const RectanglePicker = ({
id,
backgroundColor,
center,
color,
fontFamily,
fontSize,
radius,
onIdle,
onDrag,
units,
maxRadius,
minRadius,
}) => {
const { map } = useMap()
const [renderer, setRenderer] = useState(null)

useEffect(() => {
const renderer = RectangleRenderer({
id,
map,
onIdle,
onDrag,
initialCenter: center,
initialRadius: radius,
units,
maxRadius,
minRadius,
})

setRenderer(renderer)

return function cleanup() {
// need to check load state for fast-refresh purposes
if (map.loaded()) renderer.remove()
}
}, [])

return (
<svg
id={`rectangle-picker-${id}`}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
}}
>
<defs>
<clipPath id={`rect-clip-${id}`}>
<path id={`rectangle-cutout-${id}`} />
</clipPath>
</defs>

<path
id={`rectangle-${id}`}
stroke={color}
strokeWidth={1}
fill='transparent'
cursor='move'
/>

<rect
x='0'
y='0'
width='100%'
height='100%'
clipPath={`url(#rect-clip-${id})`}
fill={backgroundColor}
fillOpacity={0.5}
Comment thread
Jakidxav marked this conversation as resolved.
Outdated
/>

<circle
id={`handle-${id}`}
r={HANDLE_RADIUS}
fill={color}
cursor='ew-resize'
/>

<line
id={`radius-guideline-${id}`}
stroke={color}
strokeOpacity={0}
strokeWidth={1}
strokeDasharray='3,2'
/>

<g id={`radius-text-container-${id}`}>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we change to show side length here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is probably much more intuitive.

Then a circular region picker could be created with:

<RegionPicker
  ...
  mode={'circle'}
  minRadius={300}
  maxRadius={2000}
  initialRadius={1000}
/>

And a rectangular region picker with:

<RegionPicker
  ...
  mode={'rectangle'}
  minSideLength={300}
  maxSideLength={2000}
  initialSideLength={1000}
/>

or something similar?

We could then check to make sure that radius props are only used when mode is set to 'circle' and side length props when mode is 'rectangle'.

<text
id={`radius-text-${id}`}
textAnchor='middle'
fontFamily={fontFamily}
fontSize={fontSize}
fill={color}
/>
</g>
</svg>
)
}

export default RectanglePicker
Loading