Skip to content

Commit

Permalink
Merge pull request #238 from x-atlas-consortia/tjmadonna/236-visualiz…
Browse files Browse the repository at this point in the history
…ation-v1.0.1

Tjmadonna/236 visualization v1.0.1
  • Loading branch information
yuanzhou authored Nov 5, 2024
2 parents 8169e35 + 9b34551 commit 7c63483
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 166 deletions.
12 changes: 1 addition & 11 deletions src/components/DataTable/DatasetTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {STATUS} from "../../lib/constants";
import BulkEditForm from "../BulkEditForm";
import Visualizations from "@/components/Visualizations";
import {ChartProvider} from "@/context/ChartContext";
import { getHierarchy } from "@/lib/helpers/hierarchy";

const DatasetTable = ({
data,
Expand Down Expand Up @@ -54,17 +55,6 @@ const DatasetTable = ({
return [...new Set(data.map(item => item[f]))]
}

const getHierarchy = (str) => {
let res = window.UBKG.organTypesGroups[str.trim()]
if (!res) {
const r = new RegExp(/.+?(?=\()/)
res = str.match(r)

return (res && res.length) ? res[0].trim() : str
}
return res
}

const makeHierarchyFilters = (items) => {
const hierarchyNames = new Set()
for (let i of items) {
Expand Down
32 changes: 23 additions & 9 deletions src/components/Visualizations/Charts/Bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ function Bar({

const colors = {}

const truncateLabel = (label) => {
return label.length > 30 ? label.substring(0, 27) + "..." : label;
}

const buildChart = () => {
data.sort((a, b) => b.value - a.value)
const groups = d3.groupSort(data, ([d]) => -d.value, (d) => d.label);
Expand All @@ -34,14 +38,15 @@ function Bar({
const marginTop = 30;
const marginRight = 0;
let marginBottom = 30;
const marginLeft = 40;
let marginLeft = 80;

if (showXLabels) {
// We need to calculate the maximum label width to adjust for the label being at 45 degrees.
const tempSvg = d3.select("body").append("svg").attr("class", "temp-svg").style("visibility", "hidden");
let maxLabelWidth = 0;
names.forEach(name => {
const textElement = tempSvg.append("text").text(name).style("font-size", "10px");
const truncName = truncateLabel(name);
const textElement = tempSvg.append("text").text(truncName).style("font-size", "11px");
const bbox = textElement.node().getBBox();
if (bbox.width > maxLabelWidth) {
maxLabelWidth = bbox.width;
Expand All @@ -66,9 +71,13 @@ function Bar({
.domain(data.map(d => d.label))
.range(d3.quantize(t => d3.interpolateSpectral(t * 0.8 + 0.1), data.length))

// Bar must have a minimum height to be able to click. 2% of the max value seems good
const maxY = d3.max(data, (d) => d.value);
const yStartPos = -(maxY * .02)

// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.value)])
.domain([yStartPos, maxY])
.range([height - marginBottom, marginTop]);

// Create the SVG container.
Expand All @@ -89,8 +98,8 @@ function Bar({
const color = colorMethods[column] ? colorMethods[column](d.label) : colorS(d.label);
colors[d.label] = {color, value: d.value, label: d.label};
return color; })
.attr("y", (d) => y(0))
.attr("height", (d) => y(0) - y(0))
.attr("y", (d) => y(yStartPos))
.attr("height", (d) => y(yStartPos) - y(yStartPos))
.attr("width", x.bandwidth())
.on("click", function(event, d) {
if (onSectionClick) {
Expand All @@ -103,7 +112,7 @@ function Bar({
.transition()
.duration(800)
.attr("y", (d) => y(d.value))
.attr("height", function(d) { return y(0) - y(d.value); })
.attr("height", function(d) { return y(yStartPos) - y(d.value); })
.delay(function(d,i){return(i*100)})

svg.selectAll("rect")
Expand All @@ -118,10 +127,13 @@ function Bar({
.selectAll("text")
.style("display", showXLabels ? "block" : "none")
.style("text-anchor", "end")
.style("font-size", "10px")
.style("font-size", "11px")
.attr("dx", "-0.8em")
.attr("dy", "0.15em")
.attr("transform", "rotate(-45)");
.attr("transform", "rotate(-45)")
.text(function(d) {
return truncateLabel(d);
});

// Add the y-axis and label, and remove the domain line.
svg.append("g")
Expand All @@ -133,7 +145,9 @@ function Bar({
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ Frequency"));
.text("↑ Frequency"))
.selectAll("text")
.style("font-size", "11px");

// Return the SVG element.
return svg.node();
Expand Down
2 changes: 1 addition & 1 deletion src/components/Visualizations/Legend.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function Legend({legend, setLegend, selectedValues = [], onItemClick}) {
<InfoCircleOutlined role='button' style={{ color: 'var(--bs-link-color)' }} />
</Tooltip>
</div>
<ul>
<ul className='c-legend__list'>
{buildLegend()}
</ul>
</div>
Expand Down
179 changes: 91 additions & 88 deletions src/components/Visualizations/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import THEME from "@/lib/helpers/theme";
import FilmStrip from "@/components/Visualizations/FilmStrip";
import Pie from "@/components/Visualizations/Charts/Pie";
import ENVS from '@/lib/helpers/envs';
import {getHierarchy} from "@/lib/helpers/hierarchy";

function Visualizations({ data, filters, applyFilters, defaultColumn = 'group_name' }) {
const defaultChartTypes = ENVS.datasetCharts().reduce((acc, c) => {
Expand All @@ -27,16 +28,21 @@ function Visualizations({ data, filters, applyFilters, defaultColumn = 'group_na
const [showModal, setShowModal] = useState(false)
const [selectedFilterValues, setSelectedFilterValues] = useState([])

const hierarchyColumns = ['organ']

const filterChartData = (col) => {
let dict = {}
for (let d of data) {
let key = d[col]
if (hierarchyColumns.includes(col)) {
key = getHierarchy(key)
}
if (dict[key] === undefined) {
dict[key] = {label: key, value: 0, id: d.uuid}
}
dict[key].value++
}
const values = Object.values(dict)
const values = Object.values(dict).filter((v) => v.label !== '')
return values.sort((a, b) => b.value - a.value)
}

Expand Down Expand Up @@ -216,100 +222,97 @@ function Visualizations({ data, filters, applyFilters, defaultColumn = 'group_na
</div>
),
children: (
<div>
<Row>
<Col span={3} offset={15}>
<Modal
className='c-chart-modal'
title={TABLE.cols.n(
column,
getColumnName()
)}
centered
closable={false}
open={showModal}
onOk={() => handleModalClose(true)}
onCancel={() => handleModalClose(false)}
okText='Set filters and close'
cancelText='Close'
okButtonProps={{ disabled: selectedFilterValues.length === 0 }}
>
<Row>
<Dropdown
className='c-visualizations__columnDropdown'
menu={columnMenuProps}
placement='bottomLeft'
arrow
>
<Button>
Select a data column
</Button>
</Dropdown>

<Dropdown
className='c-visualizations__chartDropdown'
menu={chartMenuProps}
placement='bottomRight'
arrow
>
<Button>
<AreaChartOutlined />
</Button>
</Dropdown>
</Row>
<Row>
{!hasMeaningfulData() && (
<div className='text-center w-100 my-4' >
There is not enough data to present a meaningful chart visualization.
</div>
)}

<Col className='mt-4' lg={{ span: 18, push: 6}} md={{ span: 24 }}>
{isBar(column) && hasMeaningfulData() && (
<Bar
setLegend={setLegend}
data={chartData}
column={column}
colorMethods={colorMethods}
showXLabels={true}
onSectionClick={handleChartItemClick}
/>
)}
{isPie(column) && hasMeaningfulData() && (
<Pie
setLegend={setLegend}
data={chartData}
column={column}
colorMethods={colorMethods}
onSectionClick={handleChartItemClick}
/>
)}
</Col>

<Col className='mt-4' lg={{ span: 6, pull: 18 }} md={{ span: 24 }}>
<Row>
{hasMeaningfulData() && <Legend
legend={legend}
setLegend={setLegend}
selectedValues={selectedFilterValues}
onItemClick={handleChartItemClick}
/>}
</Row>
</Col>

</Row>
</Modal>
</Col>
</Row>

<>
<Row>
<div className='container'>
<FilmStrip>
{getMiniCharts()}
</FilmStrip>
</div>
</Row>
</div>

<Modal
className='c-chart-modal'
classNames={{ body: 'c-chart-modal__body' }}
title={TABLE.cols.n(
column,
getColumnName()
)}
centered
closable={false}
open={showModal}
onOk={() => handleModalClose(true)}
onCancel={() => handleModalClose(false)}
okText='Set filters and close'
cancelText='Close'
okButtonProps={{ disabled: selectedFilterValues.length === 0 }}
>
<Row>
<Col span={24}>
<Dropdown
className='c-visualizations__columnDropdown'
menu={columnMenuProps}
placement='bottomLeft'
arrow
>
<Button>
Select a data column
</Button>
</Dropdown>

<Dropdown
className='c-visualizations__chartDropdown'
menu={chartMenuProps}
placement='bottomRight'
arrow
>
<Button>
<AreaChartOutlined />
</Button>
</Dropdown>
</Col>
</Row>

<Row>
{!hasMeaningfulData() && (
<Col className='text-center w-100' span={24} >
There is not enough data to present a meaningful chart visualization.
</Col>
)}

<Col className='mt-4 ps-md-4' md={{ span: 18, push: 6 }} sm={{ span: 24 }} xs={{ span: 24 }}>
{isBar(column) && hasMeaningfulData() && (
<Bar
setLegend={setLegend}
data={chartData}
column={column}
colorMethods={colorMethods}
showXLabels={true}
onSectionClick={handleChartItemClick}
/>
)}
{isPie(column) && hasMeaningfulData() && (
<Pie
setLegend={setLegend}
data={chartData}
column={column}
colorMethods={colorMethods}
onSectionClick={handleChartItemClick}
/>
)}
</Col>

<Col className='mt-4' md={{ span: 6, pull: 18 }} sm={{ span: 24 }} xs={{ span: 24 }}>
{hasMeaningfulData() && <Legend
legend={legend}
setLegend={setLegend}
selectedValues={selectedFilterValues}
onItemClick={handleChartItemClick}
/>}
</Col>
</Row>
</Modal>
</>
)
}
]}
Expand Down
11 changes: 11 additions & 0 deletions src/lib/helpers/hierarchy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const getHierarchy = (str) => {
if (!window.UBKG) return str
let res = window.UBKG.organTypesGroups[str.trim()]
if (!res) {
const r = new RegExp(/.+?(?=\()/)
res = str.match(r)

return res && res.length ? res[0].trim() : str
}
return res
}
Loading

0 comments on commit 7c63483

Please sign in to comment.