Commit 5bbd523b authored by ETretyakov's avatar ETretyakov
Browse files

Added arcs view table

parent 82923abc
......@@ -26,3 +26,9 @@ body {
position: absolute;
display: grid;
}
.viewers-container {
position: absolute;
display: grid;
right: 0;
}
......@@ -3,6 +3,7 @@ import {StaticMap} from 'react-map-gl';
import {PhongMaterial} from '@luma.gl/core';
import AffiliationsFilter from './filters/affiliations'
import CountriesFilter from './filters/countries'
import ArcsViewer from './viewers/arcs'
import {FlyToInterpolator} from 'deck.gl';
import DeckGL from '@deck.gl/react';
import {ArcLayer, ScatterplotLayer, ColumnLayer} from '@deck.gl/layers';
......@@ -63,6 +64,7 @@ export default class App extends Component {
recalculateArcs(data, selectedAffiliation, country) {
if (!data || selectedAffiliation === null) {
this.setState({arcs: []});
return;
}
......@@ -81,6 +83,7 @@ export default class App extends Component {
return {
source: centroid,
target: f.properties.centroid,
targetName: f.properties.name,
value: flows[toId]
};
});
......@@ -129,7 +132,8 @@ export default class App extends Component {
this.setState({
selectedAffiliation: null,
countries: [],
selectedCountry: ""
selectedCountry: "",
arcs: []
});
return
}
......@@ -140,11 +144,15 @@ export default class App extends Component {
selectedAffiliation: affiliation,
selectedCountry: ""
});
this.flyToAffiliation(affiliation);
this.flyToAffiliation(affiliation.geometry.coordinates);
this.recalculateArcs(this.props.data, affiliation, "");
this.recalculateCountries(this.props.data, affiliation);
};
arcsViewCallback = (object) => {
this.flyToAffiliation(object.target);
};
countriesCallback = (country) => {
this.setState({
selectedCountry: country
......@@ -203,8 +211,7 @@ export default class App extends Component {
this.setState({viewState});
}
flyToAffiliation(affiliation) {
const coordinates = affiliation.geometry.coordinates;
flyToAffiliation(coordinates) {
this.setState({
viewState: {
......@@ -324,6 +331,14 @@ export default class App extends Component {
selectedCountry={this.state.selectedCountry}
/>
</div>
<div className={"viewers-container"}>
<ArcsViewer
parentCallback={this.arcsViewCallback}
filterId={"arc-viewer"}
filterName={"Publications"}
items={this.state.arcs}
/>
</div>
<DeckGL
layers={this.renderLayers()}
initialViewState={this.state.viewState}
......
......@@ -37,6 +37,9 @@ class AffiliationsFilter extends Component {
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.emptySelected = this.emptySelected.bind(this);
this.sortByName = this.sortByName.bind(this);
this.sortByCount = this.sortByCount.bind(this);
}
sendData = (item) => {
......@@ -49,6 +52,61 @@ class AffiliationsFilter extends Component {
})
}
rotateCaret(element) {
const rotateDegrees = element.dataset.rotate;
const rotateTarget = document.getElementById(element.dataset["rotatetarget"]);
rotateTarget.style.transform = "rotate(" + rotateDegrees + "deg)";
if (rotateDegrees === "180") {
element.dataset.rotate = "0"
} else {
element.dataset.rotate = "180"
}
}
sortByName(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.itemVal < b.itemVal) ? 1 : -1)
});
element.dataset.order = "dsc"
} else {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.itemVal > b.itemVal) ? 1 : -1)
});
element.dataset.order = "asc"
}
}
sortByCount(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.properties.publications_total < b.properties.publications_total) ? 1 : -1)
});
element.dataset.order = "dsc"
} else {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.properties.publications_total > b.properties.publications_total) ? 1 : -1)
});
element.dataset.order = "asc"
}
}
onChangeSearch(event) {
let substring = event.target.value;
......@@ -231,10 +289,12 @@ class AffiliationsFilter extends Component {
>
<div
className={"filter-item " + (this.state.targetedItem === index ? "targeted" : "") + (this.state.clickedItem === item["itemCounter"] ? " clicked" : "")}>
<span
className={"filter-value" + (this.state.clickedItem === item["itemCounter"] ? " clicked" : "")}>{item["itemVal"]}
</span>
<span>{item["properties"]["publications_total"]}</span>
<div className={"filter-value-wrapper"}>
<span
className={"filter-value" + (this.state.clickedItem === item["itemCounter"] ? " clicked" : "")}>{item["itemVal"]}
</span>
</div>
<span className={"filter-count publications-total" + (this.state.clickedItem === item["itemCounter"] ? " clicked" : "")} >{item["properties"]["publications_total"]}</span>
</div>
</li>
)
......@@ -308,6 +368,16 @@ class AffiliationsFilter extends Component {
/>
</div>
<div className={"filter-list"}>
<div className={"filter-list-sort"}>
<div className={"filter-list-column name"}>
<button data-order={"asc"} data-rotate={"180"} data-rotatetarget={"affiliations-sort-name-caret"} onClick={this.sortByName}>Name</button>
<i id={"affiliations-sort-name-caret"} className="sort-caret im im-care-down"/>
</div>
<div className={"filter-list-column publications-total"}>
<button data-order={"asc"} data-rotate={"180"} data-rotatetarget={"affiliations-sort-count-caret"} onClick={this.sortByCount}>Publications</button>
<i id={"affiliations-sort-count-caret"} className="sort-caret im im-care-down"/>
</div>
</div>
<ul ref={this.itemsList}>{this.prepareListItems()}</ul>
</div>
<div className={"filter-toolbar"}>
......
......@@ -18,6 +18,9 @@ class CountriesFilter extends Component {
this.emptySelected = this.emptySelected.bind(this);
this.onMouseEnterFilterItem = this.onMouseEnterFilterItem.bind(this);
this.onClickFilterItem = this.onClickFilterItem.bind(this);
this.sortByCount = this.sortByCount.bind(this);
this.sortByName = this.sortByName.bind(this);
}
sendData = (country) => {
......@@ -31,6 +34,61 @@ class CountriesFilter extends Component {
})
}
rotateCaret(element) {
const rotateDegrees = element.dataset.rotate;
const rotateTarget = document.getElementById(element.dataset["rotatetarget"]);
rotateTarget.style.transform = "rotate(" + rotateDegrees + "deg)";
if (rotateDegrees === "180") {
element.dataset.rotate = "0"
} else {
element.dataset.rotate = "180"
}
}
sortByName(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
countries: this.props.countries.sort((a, b) => (a.name < b.name) ? 1 : -1)
});
element.dataset.order = "dsc"
} else {
this.setState({
countries: this.props.countries.sort((a, b) => (a.name > b.name) ? 1 : -1)
});
element.dataset.order = "asc"
}
}
sortByCount(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
countries: this.props.countries.sort((a, b) => (a.counter < b.counter) ? 1 : -1)
});
element.dataset.order = "dsc"
} else {
this.setState({
countries: this.props.countries.sort((a, b) => (a.counter > b.counter) ? 1 : -1)
});
element.dataset.order = "asc"
}
}
onMouseEnterFilterItem(event) {
this.setState({
targetedItem: Number(event.target.parentElement.dataset.listindex)
......@@ -60,10 +118,12 @@ class CountriesFilter extends Component {
className={"filter-item " + (this.state.targetedItem === index ? "targeted" : "")
+ (this.props.selectedCountry.indexOf(item.name) > -1 ? " selected" : "")}
>
<span
className={"filter-value"}>{item["name"]}
</span>
<span>{item["counter"]}</span>
<div className={"filter-value-wrapper"}>
<span
className={"filter-value"}>{item["name"]}
</span>
</div>
<span className={"filter-count organisations-total"}>{item["counter"]}</span>
</div>
</li>
)
......@@ -108,6 +168,16 @@ class CountriesFilter extends Component {
<h3 className={"filter-name no-user-select"}>{this.props['filterName']}</h3>
</div>
<div className={"filter-list"}>
<div className={"filter-list-sort"}>
<div className={"filter-list-column name"}>
<button data-order={"asc"} data-rotate={"180"} data-rotatetarget={"countries-sort-name-caret"} onClick={this.sortByName}>Name</button>
<i id={"countries-sort-name-caret"} className="sort-caret im im-care-down"/>
</div>
<div className={"filter-list-column organisations-total"}>
<button data-order={"dsc"} data-rotate={"0"} data-rotatetarget={"countries-sort-count-caret"} onClick={this.sortByCount}>Organisations</button>
<i id={"countries-sort-count-caret"} className="sort-caret im im-care-down"/>
</div>
</div>
<ul ref={this.itemsList}>
<ScrollArea className={"area basket"}>
{this.prepareListItems()}
......
......@@ -80,10 +80,10 @@ button:focus,button:active {
.filter-list ul {
min-height: 360px;
margin: 10px 0;
margin: 0;
padding: 5px 0;
list-style: none;
border-top: 1px solid #575757;
/*! border-top: 1px solid #575757; */
border-bottom: 1px solid #575757;
}
......@@ -128,8 +128,8 @@ button:focus,button:active {
grid-template-rows: 1fr;
margin: 1px 0;
padding: 5px;
border: 2px solid #191a1a;
background-color: #191a1a;
border: 2px solid #242424;
background-color: #242424;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
......@@ -137,7 +137,7 @@ button:focus,button:active {
}
.filter-item > span {
background-color: #191a1a;
background-color: #242424;
}
.filter-item.clicked {
......@@ -149,13 +149,29 @@ button:focus,button:active {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 5px;
}
.filter-value-wrapper {
overflow: hidden;
text-overflow: ellipsis;
}
.filter-value:hover {
overflow: visible;
white-space: normal;
position: absolute;
background-color: #242424;
}
.filter-value.clicked {
/*.filter-value:hover {*/
/* overflow: visible;*/
/* white-space: normal;*/
/* position: absolute;*/
/* border: 1px solid #ddd;*/
/*}*/
.filter-value.clicked, .filter-count.clicked {
color: #000;
background-color: #ddd;
}
......@@ -167,7 +183,7 @@ button:focus,button:active {
.filter-item.no-data {
cursor: default;
background-color: #191a1a;
background-color: #242424;
border: none;
text-align: center;
}
......@@ -199,3 +215,56 @@ button:focus,button:active {
-webkit-user-select: none;
user-select: none;
}
.filter-list-sort {
display: grid;
grid-template-columns: 3fr 1fr;
margin: 10px 0 0 0;
border-top: 1px solid #575757;
padding-top: 5px;
}
.filter-list-column i {
position: absolute;
right: 5px;
top: 15px;
color: #ddd;
font-size: .5rem;
transform: rotate(0deg);
transition: transform .2s linear;
}
.filter-list-column {
position: relative;
}
.filter-list-column:last-child {
padding-left: 10px;
}
.filter-list-column button {
width: 100%;
margin: 5px 0 0 0;
padding: 5px;
background-color: #343332;
border: 1px solid #343332;
color: #ddd;
font-size: .7rem;
box-shadow: 0 0 4px #131313;
cursor: pointer;
}
.filter-list-column button:hover {
background-color: #3a3a3a;
transition: background-color 500ms linear;
}
.publications-total {
width: 100px;
}
.filter-count.publications-total {
width: 97px;
text-align: center;
border-left: 1px solid #343332;
}
......@@ -9,7 +9,7 @@
}
.countries-filter .filter-list ul {
min-height: 240px;
min-height: 195px;
}
.countries-filter .filter-item {
......@@ -31,5 +31,23 @@
}
.basket {
height: 240px;
height: 195px;
}
.filter-list-column.organisations-total {
margin-right: 16px;
}
.organisations-total {
width: 100px;
}
.countries-filter .filter-list-column:last-child i {
transform: rotate(180deg);
}
.filter-count.organisations-total {
width: 97px;
text-align: center;
border-left: 1px solid #343332;
}
import React, {Component} from 'react';
import './css/arcs.css'
import '../icons/css/iconmonstr-iconic-font.min.css'
function paginate(array, page_size, page_number) {
--page_number;
return array.slice(page_number * page_size, (page_number + 1) * page_size);
}
class ArcsViewer extends Component {
constructor(props) {
super(props);
this.itemsPerPage = 20;
this.itemsList = React.createRef();
this.state = {
shownItems: [],
sortBy: "count",
order: "dsc",
page: 1,
previousPage: 1
};
this.onChangeSearch = this.onChangeSearch.bind(this);
this.onPageBlur = this.onPageBlur.bind(this);
this.onChangePage = this.onChangePage.bind(this);
this.onPageNext = this.onPageNext.bind(this);
this.onPagePrevious = this.onPagePrevious.bind(this);
this.onMouseEnterFilterItem = this.onMouseEnterFilterItem.bind(this);
this.onClickViewItem = this.onClickViewItem.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.sortByName = this.sortByName.bind(this);
this.sortByCount = this.sortByCount.bind(this);
}
sendData = (item) => {
this.props['parentCallback'](item);
};
//
componentDidMount() {
this.setState({
shownItems: this.props['items'].sort((a, b) => (a.value > b.value) ? 1 : -1)
})
}
componentDidUpdate(prevProps, prevState, snapshot) {
const items = this.props.items;
const {sortBy, order} = this.state;
let shownItems = [];
if (prevProps.items !== items) {
if (sortBy === "name") {
if (order === "asc") {
shownItems = [...items].sort((a, b) => (a.targetName > b.targetName) ? 1 : -1)
} else {
shownItems = [...items].sort((a, b) => (a.targetName < b.targetName) ? 1 : -1)
}
} else {
if (order === "asc") {
shownItems = [...items].sort((a, b) => (a.value > b.value) ? 1 : -1)
} else {
shownItems = [...items].sort((a, b) => (a.value < b.value) ? 1 : -1)
}
}
this.setState({
shownItems: shownItems
})
}
}
rotateCaret(element) {
const rotateDegrees = element.dataset.rotate;
const rotateTarget = document.getElementById(element.dataset["rotatetarget"]);
rotateTarget.style.transform = "rotate(" + rotateDegrees + "deg)";
if (rotateDegrees === "180") {
element.dataset.rotate = "0"
} else {
element.dataset.rotate = "180"
}
}
sortByName(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.targetName < b.targetName) ? 1 : -1)
});
element.dataset.order = "dsc";
this.setState({sortBy: "name", order: "dsc"});
} else {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.targetName > b.targetName) ? 1 : -1)
});
element.dataset.order = "asc";
this.setState({sortBy: "name", order: "asc"});
}
}
sortByCount(event) {
const element = event.target;
const order = element.dataset.order;
this.rotateCaret(element);
if (order === "asc") {
this.setState({
shownItems: [...this.state.shownItems].sort((a, b) => (a.value < b.value) ? 1 : -1)
});
element.dataset.order = "dsc";
this.setState({sortBy: "count", order: "dsc"});
} else {
this.setState({
shownItems: