-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
1,603 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.chart-legend-color { | ||
height: 10px; | ||
width: 10px; | ||
margin-right: 5px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
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 { ChartData, extentToChartData } from './analysis_panel_tools/subsection' | ||
import { GenerateChart } from './analysis_panel_tools/charts' | ||
import './analysis_panel.css' | ||
|
||
|
||
export type ChartType = "pie" | "hist" | "bar" | ||
|
||
interface ChartProps { | ||
chartType: ChartType | undefined | ||
chartData: ChartData | undefined | ||
|
||
} | ||
|
||
const Chart = ({ chartType, chartData }: ChartProps) => { | ||
|
||
if (!chartType || !chartData) return <></> | ||
|
||
return <GenerateChart | ||
chartData={chartData} | ||
chartType={chartType} | ||
/> | ||
} | ||
|
||
interface ChartSelectionProps { | ||
SourceType: string | ||
ChartTypeSelected: ChartType | undefined | ||
SetChartType: (type: ChartType) => void | ||
} | ||
|
||
const ChartSelection = ({ SourceType, ChartTypeSelected, SetChartType }: ChartSelectionProps) => { | ||
|
||
const typeArray = ChartTypeArray.get(SourceType) || [] | ||
|
||
const options = [ | ||
{ value: "pie", label: "Pie chart", icon: "fa-chart-pie", disabled: false }, | ||
{ value: "bar", label: "Bar chart", icon: "fa-chart-bar", disabled: true }, | ||
{ value: "hist", label: "Histogram", icon: "fa-chart-bar", disabled: true }, | ||
].filter(option => (typeArray as string[]).includes(option.value)) | ||
|
||
return ( | ||
<div className="d-flex align-items-center mt-3 ml-3 mr-3"> | ||
<div className="btn-group mr-2"> | ||
{options.map(option => ( | ||
<button disabled={option.disabled} title={option.label} type="button" onClick={() => SetChartType(option.value as ChartType)} className={`btn ${ChartTypeSelected == option.value ? "btn-primary" : "btn-outline-primary"}`}><i className={`fas ${option.icon}`} /></button> | ||
))} | ||
</div> | ||
|
||
<div className="btn-group mr-2"> | ||
<button disabled title='Expand' type="button" className={`btn btn-outline-primary`}><i className="fas fa-expand"></i></button> | ||
</div> | ||
|
||
<div className="btn-group mr-2 "> | ||
<button disabled title='Download' type="button" className={`btn btn-outline-primary`}><i className="fas fa-download"></i></button> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
interface ChartLegendProps { | ||
chartData: ChartData | undefined | ||
sourceType: string | ||
} | ||
|
||
const ChartLegend = ({ chartData, sourceType }: ChartLegendProps) => { | ||
if (!chartData) { | ||
return null | ||
} | ||
|
||
let LegendItems | ||
|
||
if (sourceType !== "NumericTileGrid") { | ||
const totalCount = Array.from(chartData.count.values()).reduce((acc, count) => acc + count, 0) | ||
const sortedLegendItems = Array.from(chartData.count.entries()).sort((a, b) => b[1] - a[1]) | ||
LegendItems = sortedLegendItems.map(([label, count], index) => ( | ||
<div style={{ display: "flex", alignItems: "center", fontSize: ".8em" }}> | ||
<div | ||
style={{ backgroundColor: `rgb(${chartData.colors?.get(label)?.slice(0, 3)?.join(",")})` }} | ||
className="chart-legend-color" | ||
/> | ||
{`${label} (${((count / totalCount) * 100).toFixed(2)}%)`} | ||
</div> | ||
)) | ||
} else { | ||
LegendItems = "" | ||
} | ||
|
||
|
||
return ( | ||
<div className="chart-legend" style={{ display: "flex", flexDirection: "column", padding: "10px" }}> | ||
{LegendItems} | ||
</div> | ||
); | ||
} | ||
|
||
|
||
|
||
interface AnalysisPanelProps { | ||
setShowAP: () => void | ||
selectedArea: Extent | null | ||
selectedLayer: Layer | null | ||
layerStats: (layer: DatasetLayer | ModelOutputLayer) => BooleanTileGrid | NumericTileGrid | CategoricalTileGrid | null | ||
currentTab: number | ||
} | ||
|
||
let dataSourceType: string = "null" | ||
|
||
const ChartTypeArray: Map<string, ChartType[]> = new Map() | ||
ChartTypeArray.set("BooleanTileGrid", ["pie", "bar"]) | ||
ChartTypeArray.set("CategoricalTileGrid", ["pie", "bar"]) | ||
ChartTypeArray.set("NumericTileGrid", ["hist"]) | ||
|
||
|
||
|
||
export const AnalysisPanel = ({ selectedArea, setShowAP, selectedLayer, layerStats, currentTab }: AnalysisPanelProps) => { | ||
|
||
const [chartType, setChartType] = React.useState<ChartType>() | ||
const [chartData, setChartData] = React.useState<ChartData>() | ||
|
||
console.log(chartType) | ||
|
||
let errorMsg: string = "" | ||
let showChart: boolean = false | ||
let data: BooleanTileGrid | NumericTileGrid | CategoricalTileGrid | null = null | ||
|
||
React.useEffect(() => { | ||
|
||
if (data !== null && selectedArea && (selectedLayer?.type == "ModelOutputLayer" || selectedLayer?.type == "DatasetLayer")) { | ||
|
||
setChartData(extentToChartData(selectedLayer.colors, data, selectedArea, selectedLayer.fill)) | ||
|
||
let dataType: string | undefined = undefined | ||
|
||
if (data instanceof BooleanTileGrid) { | ||
dataType = "BooleanTileGrid" | ||
} else if (data instanceof NumericTileGrid) { | ||
dataType = "NumericTileGrid" | ||
} else if (data instanceof CategoricalTileGrid) { | ||
dataType = "CategoricalTileGrid" | ||
} | ||
if (dataType !== undefined) { | ||
if (dataSourceType !== dataType || chartType === undefined) { | ||
dataSourceType = dataType | ||
const charts = ChartTypeArray.get(dataType) | ||
if (charts) setChartType(charts[0]) | ||
} | ||
} | ||
} else { | ||
setChartData(undefined) | ||
} | ||
|
||
}, [selectedArea, selectedLayer, data, currentTab]) | ||
|
||
|
||
if (selectedArea === null) { | ||
errorMsg = "Please select an area to analyze." | ||
} else if (selectedLayer === null) { | ||
errorMsg = "Please select a suitable layer." | ||
} else if (selectedLayer.type !== "DatasetLayer" && selectedLayer.type !== "ModelOutputLayer") { | ||
errorMsg = "Unsuitable layer type, please select a model output or dataset layer." | ||
} else { | ||
data = layerStats(selectedLayer) | ||
if (data === null) { | ||
errorMsg = "Model is not yet available." | ||
} else { | ||
showChart = true | ||
} | ||
} | ||
|
||
return ( | ||
<div className="bg-light border-right d-flex flex-column" style={{ minWidth: "450px", maxWidth: "450px" }}> | ||
<div className="px-3 py-2 border-top border-bottom d-flex align-items-center justify-content-between"> | ||
Snapshot tool | ||
<i className="fas fa-times" style={{ cursor: "pointer" }} onClick={setShowAP} /> | ||
</div> | ||
|
||
<div className="flex-grow-1" style={{ overflowY: "auto", flexBasis: "0px", backgroundColor: 'white' }}> | ||
|
||
{ | ||
!!errorMsg && | ||
<div style={{ textAlign: "center", padding: 30 }}>{errorMsg}</div> | ||
} | ||
|
||
{ | ||
showChart && | ||
<> | ||
<ChartSelection | ||
SourceType={dataSourceType} | ||
ChartTypeSelected={chartType} | ||
SetChartType={setChartType} | ||
/> | ||
<div style={{ textAlign: 'center' }}> | ||
<Chart | ||
chartType={chartType} | ||
chartData={chartData} | ||
/> | ||
</div> | ||
<ChartLegend | ||
chartData={chartData} | ||
sourceType={dataSourceType} | ||
/> | ||
</> | ||
} | ||
|
||
</div> | ||
|
||
<div className="px-3 py-2 border-top border-bottom bg-light">Selected coordinates (EPSG:3857)</div> | ||
|
||
<div className="px-3 py-2 border-top border-bottom bg-white text-center"> | ||
|
||
<div> | ||
<label style={{ width: 60 }} >Xmin</label> | ||
<input | ||
disabled | ||
type="text" | ||
value={selectedArea ? selectedArea[0] : 0} | ||
/> | ||
</div> | ||
<div> | ||
<label style={{ width: 60 }}>Ymin</label> | ||
<input | ||
disabled | ||
type="text" | ||
value={selectedArea ? selectedArea[1] : 0} | ||
/> | ||
</div> | ||
<div> | ||
<label style={{ width: 60 }}>Xmax</label> | ||
<input | ||
disabled | ||
type="text" | ||
value={selectedArea ? selectedArea[2] : 0} | ||
/> | ||
</div> | ||
<div> | ||
<label style={{ width: 60 }}>Ymax</label> | ||
<input | ||
disabled | ||
type="text" | ||
value={selectedArea ? selectedArea[3] : 0} | ||
/> | ||
</div> | ||
</div> | ||
|
||
</div > | ||
|
||
|
||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
app/javascript/projects/analysis_panel_tools/charts/bar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as React from "react" | ||
import { ChartData } from "../subsection" | ||
import * as d3 from "d3" | ||
|
||
|
||
interface BarChartProps { | ||
chartData: ChartData | ||
} | ||
export const GenerateBarChart = ({ chartData }: BarChartProps) => { | ||
|
||
const [h, w, m, bar_pad] = [400, 400, { top: 30, bottom: 10, left: 10, right: 10 }, 0.5] | ||
const [bounds_w, bounds_h] = [(w - m.right - m.left), (h - m.top, m.bottom)] | ||
|
||
const data = Array.from(chartData.count, ([name, value]) => ({ name, value })) | ||
|
||
return ( | ||
<div> | ||
{/* <svg width={w} height={h}> | ||
</svg> */} | ||
</div> | ||
) | ||
} |
28 changes: 28 additions & 0 deletions
28
app/javascript/projects/analysis_panel_tools/charts/histogram.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as React from "react" | ||
import * as d3 from 'd3' | ||
import { ChartType } from "../../analysis_panel" | ||
import { ChartData } from "../subsection" | ||
|
||
interface HistogramProps { | ||
chartData: ChartData | ||
} | ||
export const GenerateHistogram = ({ chartData }: HistogramProps) => { | ||
|
||
const [width, height] = [500, 500] | ||
|
||
const svgRef = React.useRef(null) | ||
|
||
React.useEffect(() => { | ||
|
||
const svg = d3.select(svgRef.current) | ||
|
||
const dataValues = Object.values(chartData.count) | ||
|
||
console.log(chartData.count) | ||
|
||
}, [chartData, width, height]); | ||
|
||
return ( | ||
<svg ref={svgRef} width={width} height={height}></svg> | ||
); | ||
} |
Oops, something went wrong.