Skip to content

Commit

Permalink
Merge pull request #467 from wearepal/units-beta
Browse files Browse the repository at this point in the history
Units in model view
  • Loading branch information
paulthatjazz authored Dec 13, 2024
2 parents 7927a8c + 731028b commit d93977a
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 26 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ yarn-debug.log*
/coverage

# Ignore local vs code properties
.vscode
.vscode

.DS_Store
10 changes: 10 additions & 0 deletions app/assets/stylesheets/model-editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
background: steelblue;
}

.socket.property {
background: purple;
}

.socket--number,
.socket.number {
background: #46b44c;
Expand Down Expand Up @@ -83,6 +87,12 @@
}
}

.connection.socket-output-property {
.main-path {
stroke: purple;
}
}

.connection.socket-output-number,
.connection.socket-output-number {
.main-path {
Expand Down
18 changes: 13 additions & 5 deletions app/javascript/projects/analysis_panel.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import { Extent } from 'ol/extent'
import * as React from 'react'
import { DatasetLayer, Layer, ModelOutputLayer } from './state'
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid } from './modelling/tile_grid'
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid, TileGridProps } from './modelling/tile_grid'
import { ChartData, extentToChartDataCached } from './analysis_panel_tools/subsection'
import { GenerateChart } from './analysis_panel_tools/charts'
import './analysis_panel.css'
import { getArea } from 'ol/sphere'
import { fromExtent } from 'ol/geom/Polygon'
import { TeamExtentData } from './project_editor'
import { getMedianCellSize } from './modelling/components/cell_area_component'

export type ChartType = "pie" | "hist" | "bar" | "kde"

interface ChartProps {
chartType: ChartType | undefined
chartData: ChartData | undefined

props: TileGridProps | undefined
cellArea: number
}

const Chart = ({ chartType, chartData }: ChartProps) => {
const Chart = ({ chartType, chartData, props, cellArea }: ChartProps) => {

if (!chartType || !chartData) return <></>

return <GenerateChart
chartData={chartData}
chartType={chartType}
props={props}
cellArea={cellArea}
/>
}

Expand Down Expand Up @@ -90,9 +94,10 @@ const ChartSelection = ({ SourceType, ChartTypeSelected, SetChartType }: ChartSe
interface ChartLegendProps {
chartData: ChartData | undefined
sourceType: string
props: TileGridProps | undefined
}

const ChartLegend = ({ chartData, sourceType }: ChartLegendProps) => {
const ChartLegend = ({ chartData, sourceType, props }: ChartLegendProps) => {
if (!chartData) {
return null
}
Expand Down Expand Up @@ -122,7 +127,7 @@ const ChartLegend = ({ chartData, sourceType }: ChartLegendProps) => {
<input
disabled
type="text"
value={NumStats[key]}
value={(key === "sum" && props && props.unit) ? `${NumStats[key]} ${props.unit}` : NumStats[key]}
/>
</div>
))
Expand Down Expand Up @@ -264,6 +269,8 @@ export const AnalysisPanel = ({ selectedArea, setSelectedArea, setShowAP, select
<Chart
chartType={chartType}
chartData={chartData}
props={data instanceof NumericTileGrid ? data.properties : undefined}
cellArea={data ? getMedianCellSize(data).area : 0}
/>
</div>
<div style={{ textAlign: 'center' }}>
Expand All @@ -283,6 +290,7 @@ export const AnalysisPanel = ({ selectedArea, setSelectedArea, setShowAP, select
<ChartLegend
chartData={chartData}
sourceType={dataSourceType}
props={data instanceof NumericTileGrid ? data.properties : undefined}
/>
</div>
</>
Expand Down
27 changes: 25 additions & 2 deletions app/javascript/projects/analysis_panel_tools/charts/histogram.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from "react"
import * as d3 from 'd3'
import { ChartData } from "../subsection"
import { TileGridProps } from "../../modelling/tile_grid"

interface HistogramProps {
chartData: ChartData
props: TileGridProps | undefined
cellArea: number
}
export const GenerateHistogram = ({ chartData }: HistogramProps) => {

export const GenerateHistogram = ({ chartData, props, cellArea }: HistogramProps) => {


const axesRef = React.useRef(null)
Expand Down Expand Up @@ -62,7 +66,7 @@ export const GenerateHistogram = ({ chartData }: HistogramProps) => {


return (
<svg id="hist" width={width} height={height}>
<svg id="hist" width={width} height={height} style={{marginBottom: 15, overflow: "auto"}}>
<g
width={boundsWidth}
height={boundsHeight}
Expand All @@ -76,6 +80,25 @@ export const GenerateHistogram = ({ chartData }: HistogramProps) => {
ref={axesRef}
transform={`translate(${[MARGIN.left, MARGIN.top].join(",")})`}
/>
<text
x={width / 2}
y={height - 5}
textAnchor="middle"
fontSize="14px"
fill="black"
>
{props?.area && props.unit ? `${props.unit}/${props.area}` : `value`}
</text>
<text
x={-height / 2}
y={15}
transform="rotate(-90)"
textAnchor="middle"
fontSize="14px"
fill="black"
>
km²
</text>
</svg>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { ChartType } from "../../analysis_panel"
import { GeneratePieChart } from "./pie"
import { GenerateHistogram } from "./histogram"
import { GenerateBarChart } from "./bar"
import { TileGridProps } from "../../modelling/tile_grid"

interface ChartProps {
chartData: ChartData
chartType: ChartType
props: TileGridProps | undefined
cellArea: number
}
export const GenerateChart = ({ chartData, chartType }: ChartProps) => {
export const GenerateChart = ({ chartData, chartType, props, cellArea }: ChartProps) => {
switch (chartType) {
case "pie":
return <GeneratePieChart
Expand All @@ -19,6 +22,8 @@ export const GenerateChart = ({ chartData, chartType }: ChartProps) => {
case "hist":
return <GenerateHistogram
chartData={chartData}
props={props}
cellArea={cellArea}
/>
break;
case "bar":
Expand Down
18 changes: 13 additions & 5 deletions app/javascript/projects/analysis_panel_tools/subsection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getArea } from "ol/sphere"
import { fromExtent } from "ol/geom/Polygon"
import { getColorStops } from "../reify_layer/model_output"
import { re, sum } from "mathjs"
import { getMedianCellSize } from "../modelling/components/cell_area_component"

type Color = [number, number, number, number]

Expand Down Expand Up @@ -35,6 +36,14 @@ export function findColor(value: number, colorArray: any[]): Color {
return alpha
}

function unitsAdjustmentFactor(unit: string | undefined, grid: NumericTileGrid): number {
const area = getMedianCellSize(grid).area
if (unit === "m²") return area / 1
if (unit === "ha") return area / 10000
if (unit === "km²") return area / (1000 ** 2)
return 1
}

function medianFromMap(arr: [number, number][], total: number): number | undefined {
let idx = total / 2;

Expand Down Expand Up @@ -162,17 +171,17 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil
let counts = new Map<any, number>()
let color = new Map<any, [number, number, number, number]>()
let numeric_stats: NumericStats | undefined
const cellSize = getMedianCellSize(model).area / 1000000

for (let x = outputTileRange.minX; x <= outputTileRange.maxX; x++) {
for (let y = outputTileRange.minY; y <= outputTileRange.maxY; y++) {

if (model instanceof CategoricalTileGrid) {

const area = getArea(fromExtent(tileGrid.getTileCoordExtent([model.zoom, x, y]))) / 1000000

const value = model.labels.get(model.get(x, y)) ? model.labels.get(model.get(x, y)) : "No Data"
const count = counts.get(value) || 0
counts.set(value, count + area)
counts.set(value, count + cellSize)


if (colors) {
Expand All @@ -182,12 +191,11 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil

} else {

const area = model instanceof NumericTileGrid ? 1 : getArea(fromExtent(tileGrid.getTileCoordExtent([model.zoom, x, y]))) / 1000000
const value = model.get(x, y)

const count = counts.get(value) || 0

counts.set(value, count + area)
counts.set(value, count + cellSize)

if (colors && model instanceof BooleanTileGrid) {
const col_value = colors[value ? 1 : 0]
Expand Down Expand Up @@ -219,7 +227,7 @@ export function extentToChartData(colors: Color[] | undefined, model: BooleanTil
const range = max - min
const step = range / bins

const _sum = sum(mapEntries.map((x) => x[1] * x[0]))
const _sum = sum(mapEntries.map((x) => x[1] * x[0])) * unitsAdjustmentFactor(model.properties.area, model)
const total_entries = mapEntries.reduce((acc, cur) => acc + cur[1], 0)

const _mean = _sum / total_entries
Expand Down
6 changes: 6 additions & 0 deletions app/javascript/projects/modelling/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { SegmentComponent } from "./segment_component"
import { KewSamplesComponent } from "./kew_samples_component"
import { InterpolationComponent } from "./interpolation_component"
import { NatmapSoilComponent } from "./natmap_soil_component"
import { UnitComponent } from "./units_component"
import { areas, units } from "../tile_grid"

export interface ProjectProperties {
extent: Extent
Expand Down Expand Up @@ -87,6 +89,10 @@ export function createDefaultComponents(saveMapLayer: SaveMapLayer, saveModel: S
new MapLayerComponent(saveMapLayer),
new SaveModelOutputComponent(saveModel),

// Properties
new UnitComponent('Unit', projectProps, units.map((unit, idx) => ({ name: unit, id: idx }))),
new UnitComponent('Area', projectProps, areas.map((unit, idx) => ({ name: unit, id: idx }))),

// Conversions
new NumberToNumericDatasetComponent(),
new NumericDatasetToNumberComponent(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Input, Node } from "rete"
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data"
import { dataSocket } from "../socket_types"
import { dataSocket, propertySocket } from "../socket_types"
import { BooleanTileGrid, CategoricalTileGrid, NumericTileGrid } from "../tile_grid"
import { BaseComponent } from "./base_component"

Expand All @@ -18,6 +18,7 @@ export class MapLayerComponent extends BaseComponent {
async builder(node: Node) {
node.meta.toolTip = "Output a model to the map view."
node.addInput(new Input("in", "Output", dataSocket))
node.addInput(new Input("props", "Properties (optional)", propertySocket, true))
}

async worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]) {
Expand All @@ -32,7 +33,18 @@ export class MapLayerComponent extends BaseComponent {

const name = editorNode.data.name as string

if (inputs["in"][0]) this.callback(node.id, name ? (name !== "" ? name.trim() : undefined) : undefined, inputs["in"][0] as BooleanTileGrid | NumericTileGrid | CategoricalTileGrid)
let out = inputs["in"][0] as BooleanTileGrid | NumericTileGrid | CategoricalTileGrid
const props = inputs["props"]

out = (out instanceof NumericTileGrid && props.length > 0) ? out.clone() : out

props.forEach((prop: any) => {
if (out instanceof NumericTileGrid){
out.properties[(prop.type as string).toLowerCase()] = prop.unit
}
})

if (out) this.callback(node.id, name ? (name !== "" ? name.trim() : undefined) : undefined, out)
else editorNode.meta.errorMessage = 'No input'

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ export class SegmentComponent extends BaseComponent {
}

if (!('cls_conf' in node.data)) {
node.data.cls_conf = "90"
node.data.cls_conf = "0"
}

if (!('n_repeats' in node.data)) {
node.data.n_repeats = "5"
node.data.n_repeats = "1"
}

if (!('prompt' in node.data)) {
Expand Down
37 changes: 37 additions & 0 deletions app/javascript/projects/modelling/components/units_component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Node, Output } from "rete";
import { NodeData, WorkerInputs, WorkerOutputs } from "rete/types/core/data";
import { BaseComponent } from "./base_component";
import { ProjectProperties } from ".";
import { propertySocket } from "../socket_types";
import { SelectControl, SelectControlOptions } from "../controls/select";

export class UnitComponent extends BaseComponent {
unitArray: SelectControlOptions[]
unitType: string
unit: string

constructor(unit: string, projectProps : ProjectProperties, unitArray: SelectControlOptions[]) {
super(`${unit} Property`)
this.category = "Properties"
this.unitArray = unitArray
this.unit = unit
}

async builder(node: Node) {
node.addControl(new SelectControl(
this.editor,
this.unit,
() => this.unitArray,
() => []
))
node.addOutput(new Output(this.unit, this.unit, propertySocket))
}

worker(node: NodeData, inputs: WorkerInputs, outputs: WorkerOutputs, ...args: unknown[]): void {
const idx = (node.data[this.unit] ?? 0 )as number
outputs[this.unit] = {
type: this.unit,
unit: this.unitArray[idx].name
}
}
}
2 changes: 2 additions & 0 deletions app/javascript/projects/modelling/socket_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const numericDataSocket = new Socket('Numeric dataset')

export const categoricalDataSocket = new Socket('Categorical dataset')

export const propertySocket = new Socket('Property')

export const dataSocket = new Socket('Dataset')
booleanDataSocket.combineWith(dataSocket)
numericDataSocket.combineWith(dataSocket)
Expand Down
Loading

0 comments on commit d93977a

Please sign in to comment.