From a8dca856311623e8f61a9e3559dd001c63fbeb41 Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 12:30:08 -0400 Subject: [PATCH 01/12] Add modal to paste query URL --- src/components/App.css | 8 +++ src/components/App.js | 131 +++++++++++++++++++++++++++++------------ 2 files changed, 101 insertions(+), 38 deletions(-) diff --git a/src/components/App.css b/src/components/App.css index efdae8c..c7a4d9d 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -1,3 +1,11 @@ .ui.menu { margin: 0 !important; } + +.modal-description { + padding-bottom: 20px; +} + +code.example { + color: grey; +} diff --git a/src/components/App.js b/src/components/App.js index 95e5f9b..4ca46d6 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,56 +1,22 @@ import React, { Component } from 'react'; import './App.css'; import Map from './Map'; -import { Menu } from 'semantic-ui-react'; +import { Button, Menu, Modal, Header, Icon, Form } from 'semantic-ui-react'; //============== // Top Menu Bar //============== class TopMenuBar extends Component { - constructor(props) { - super(props); - this.state = { - activeItem: 'home', - }; - this.handleItemClick = this.handleItemClick.bind(this); - } - - handleItemClick(e, { name }) { - this.setState({ activeItem: name }); - } - render() { - const { activeItem } = this.state; - return ( React Logo - - Home - - - - Project - - - - About + + @@ -61,15 +27,104 @@ class TopMenuBar extends Component { } } +//========================== +// Modal to paste query url +//========================== + +class ModalQuery extends React.Component { + constructor(props) { + super(props); + this.state = { + modalOpen: false, + url: '', + }; + } + + handleOpen = () => this.setState({ modalOpen: true }); + + handleClose = () => this.setState({ modalOpen: false }); + + handleChange = (event) => this.setState({ url: event.target.value }); + + handleSubmit = (event) => { + this.props.handleInputURL(this.state.url); + this.setState({ modalOpen: false }); + this.setState({ url: '' }); + }; + + render() { + return ( + + Map Search Results + + } + open={this.state.modalOpen} + onClose={this.handleClose} + size="small" + > +
+ + +
+ + + + +
+
+ + + + + ); + } +} + +const ModalDescription = () => ( +
+ To map your search results, +
    +
  • + Go to the{' '} + + PhiloLogic database. + +
  • +
  • Make a search query.
  • +
  • Click "Export results" button on top-right corner.
  • +
  • Paste the resulting url below. The url should look something like this:
  • +
+ + https://rdorin.website/philologic/beta/query?report=concordance&method=proxy&q=corpus&start=0&end=0&format=json + +
+); + //==================== // Main App Component //==================== class App extends Component { + constructor(props) { + super(props); + this.state = { + queryURL: null, + }; + } + + handleInputURL = (url) => { + console.log('The input url is: ' + url); + this.setState({ queryURL: url }); + }; + render() { return (
- +
); From e301283ee165b751dca0212933e5f37c0d8b0d47 Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 14:22:24 -0400 Subject: [PATCH 02/12] Fetch data from PhiloLogic for visualization --- src/components/App.js | 53 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index 4ca46d6..7fea141 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import './App.css'; import Map from './Map'; +import axios from 'axios'; import { Button, Menu, Modal, Header, Icon, Form } from 'semantic-ui-react'; //============== @@ -112,20 +113,64 @@ class App extends Component { constructor(props) { super(props); this.state = { - queryURL: null, + mappingData: null, }; } handleInputURL = (url) => { - console.log('The input url is: ' + url); - this.setState({ queryURL: url }); + const testURL = url.replace('end=0', 'end=1'); + let query = null; + axios + .get(testURL) + .then((response) => response.data) + .then((data) => { + query = data.query; + query.end = data.results_length; + this.fetchData(query); + }) + .catch((error) => { + console.error(error); + }); + }; + + fetchData = (query) => { + const baseURL = 'https://rdorin.website/philologic/beta/query'; + axios + .get(baseURL, { params: query }) + .then((response) => response.data) + .then((data) => { + console.log(data); + this.processData(data); + }) + .catch((error) => { + console.error(error); + }); + }; + + processData = ({ results }) => { + const dioceseMap = {}; + results.forEach((result) => { + const metadata = result.metadata_fields; + const { record_id, diocese_id } = metadata; + if (diocese_id) { + if (dioceseMap.hasOwnProperty(diocese_id)) { + dioceseMap[diocese_id].add(record_id); + } else { + dioceseMap[diocese_id] = new Set([record_id]); + } + } + }); + console.log(dioceseMap); + this.setState({ + mappingData: dioceseMap, + }); }; render() { return (
- +
); } From 0c622cd9ef882aa35ee2c54d99585966838d0bbe Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 14:39:25 -0400 Subject: [PATCH 03/12] Highlight only the regions that appear in search results Notice that some regions appear darker because of overlap. (Every region should have the same color if there is no overlap.) --- src/components/Map.js | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/components/Map.js b/src/components/Map.js index 9629da7..421c95d 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -53,8 +53,8 @@ class GeoJSONLayer extends React.Component { componentDidMount() { fetch(process.env.REACT_APP_GEOJSON_URL) - .then(response => response.json()) - .then(data => { + .then((response) => response.json()) + .then((data) => { this.setState({ geojson: data, isLoading: false, @@ -62,12 +62,22 @@ class GeoJSONLayer extends React.Component { }); } - getColor(shpfid, props) { - const { colorSchemes } = this.props.config; - const { currentColorScheme } = this.props; - const colors = colorSchemes[currentColorScheme]; - const id = parseInt(shpfid.substring(1, 5), 10); - return colors[id % colors.length]; + getColor(shpfid) { + const map = this.props.mappingData; + if (map) { + const dioceseID = this.shapeToDiocese[shpfid]; + if (map.hasOwnProperty(dioceseID)) { + return '#67a9cf'; + } else { + return '#f2f0f7'; + } + // const { colorSchemes } = this.props.config; + // const { currentColorScheme } = this.props; + // const colors = colorSchemes[currentColorScheme]; + // const id = parseInt(shpfid.substring(1, 5), 10); + // return colors[id % colors.length]; + } + return '#f2f0f7'; } style(feature) { @@ -75,7 +85,7 @@ class GeoJSONLayer extends React.Component { fillColor: this.getColor(feature.properties.SHPFID), weight: 1, opacity: 3, - color: 'white', + color: 'grey', dashArray: '3', fillOpacity: 0.7, }; @@ -94,15 +104,15 @@ class GeoJSONLayer extends React.Component { const { SHPFID: shpfid } = layer.feature.properties; const info = { text: 'No data available' }; if (this.shapeToDiocese.hasOwnProperty(shpfid)) { - const dioceseId = this.shapeToDiocese[shpfid]; - info.text = dioceseId; - if (dioceseInfo.hasOwnProperty(dioceseId)) { + const dioceseID = this.shapeToDiocese[shpfid]; + info.text = dioceseID; + if (dioceseInfo.hasOwnProperty(dioceseID)) { const { diocese_name, diocese_alt, province, country_modern, - } = dioceseInfo[dioceseId]; + } = dioceseInfo[dioceseID]; const diocese = diocese_alt ? `${diocese_name} (${diocese_alt})` : diocese_name; @@ -296,6 +306,7 @@ class LocalLegislationMap extends Component { mapRef={this.mapRef} updateInfo={this.updateInfo} currentColorScheme={this.state.currentColorScheme} + mappingData={this.props.mappingData} /> From f74e9d771048d1b6aefb521a15043082e2d2bf4f Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 15:53:42 -0400 Subject: [PATCH 04/12] Highlight regions based on search results data :sparkles: --- src/components/Map.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/components/Map.js b/src/components/Map.js index 421c95d..f99667e 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -63,21 +63,25 @@ class GeoJSONLayer extends React.Component { } getColor(shpfid) { + const { colorSchemes } = this.props.config; + const { currentColorScheme } = this.props; + const colors = colorSchemes[currentColorScheme]; const map = this.props.mappingData; + if (map) { const dioceseID = this.shapeToDiocese[shpfid]; + const maxNumEntries = 10; + const numPerBucket = Math.ceil(maxNumEntries / colors.length); if (map.hasOwnProperty(dioceseID)) { - return '#67a9cf'; - } else { - return '#f2f0f7'; + const numEntries = map[dioceseID].size; + let index = Math.ceil(numEntries / numPerBucket); + if (index > colors.length - 1) { + index = colors.length - 1; + } + return colors[index]; } - // const { colorSchemes } = this.props.config; - // const { currentColorScheme } = this.props; - // const colors = colorSchemes[currentColorScheme]; - // const id = parseInt(shpfid.substring(1, 5), 10); - // return colors[id % colors.length]; } - return '#f2f0f7'; + return '#fff'; } style(feature) { @@ -87,7 +91,7 @@ class GeoJSONLayer extends React.Component { opacity: 3, color: 'grey', dashArray: '3', - fillOpacity: 0.7, + fillOpacity: 0.9, }; } From 845c8368149be97fb74af349b980fb4806da0b7e Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 16:06:02 -0400 Subject: [PATCH 05/12] Compute max number of entries for a given search query --- src/components/Map.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/Map.js b/src/components/Map.js index f99667e..6fea15f 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -70,7 +70,7 @@ class GeoJSONLayer extends React.Component { if (map) { const dioceseID = this.shapeToDiocese[shpfid]; - const maxNumEntries = 10; + const maxNumEntries = this.props.maxNumEntries; const numPerBucket = Math.ceil(maxNumEntries / colors.length); if (map.hasOwnProperty(dioceseID)) { const numEntries = map[dioceseID].size; @@ -289,8 +289,23 @@ class LocalLegislationMap extends Component { }); } + getMaxNumEntries = () => { + const mappingData = this.props.mappingData; + let maxNumEntries = 0; + for (const prop in mappingData) { + if (mappingData.hasOwnProperty(prop)) { + const numEntries = mappingData[prop].size; + if (numEntries > maxNumEntries) { + maxNumEntries = numEntries; + } + } + } + return maxNumEntries; + }; + render() { const config = this.state.config; + const maxNumEntries = this.getMaxNumEntries(); return (
@@ -311,6 +326,7 @@ class LocalLegislationMap extends Component { updateInfo={this.updateInfo} currentColorScheme={this.state.currentColorScheme} mappingData={this.props.mappingData} + maxNumEntries={maxNumEntries} />
From d215b17379f51551ed4b458555a5363075f4a513 Mon Sep 17 00:00:00 2001 From: Thawsitt Naing Date: Mon, 17 Sep 2018 17:58:32 -0400 Subject: [PATCH 06/12] Add color legend at bottom left corner --- src/components/Map.css | 23 +++++++++++++++++ src/components/Map.js | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/components/Map.css b/src/components/Map.css index 0430a3b..cdf4851 100644 --- a/src/components/Map.css +++ b/src/components/Map.css @@ -23,3 +23,26 @@ .panel.panel-info { margin-top: 120px !important; } + +.panel-left { + position: fixed !important; + z-index: 800; + left: 0 !important; + width: 100px !important; + margin: 10px !important; + background: rgba(255, 255, 255, 0.8) !important; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1) !important; +} + +.panel-left.panel-legend { + bottom: 50px !important; + line-height: 19px; +} + +.panel-left.panel-legend i { + width: 18px; + height: 18px; + margin-right: 8px; + float: left; + opacity: 0.9; +} diff --git a/src/components/Map.js b/src/components/Map.js index 6fea15f..0cfc88e 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -260,6 +260,58 @@ class InfoPanel extends React.Component { } } +//=============== +// Color Legend +//=============== + +class ColorLegend extends React.Component { + render() { + if (!this.props.mappingData) { + return null; + } + const { maxNumEntries, currentColorScheme } = this.props; + const { colorSchemes } = this.props.config; + const colors = colorSchemes[currentColorScheme]; + const numPerBucket = Math.ceil(maxNumEntries / colors.length); + + const getStyle = (colorHex) => ({ + background: colorHex, + }); + + const colorBlocks = []; + + colorBlocks.push( + + + 0 +
+
, + ); + + for (let i = 0; i < colors.length; i++) { + const start = i * numPerBucket + 1; + const end = (i + 1) * numPerBucket; + let range = `${start} - ${end}`; + if (i === colors.length - 1) { + range = `> ${start}`; + } + colorBlocks.push( + + + {range} +
+
, + ); + } + + return ( + + {colorBlocks} + + ); + } +} + //==================== // Main map component //==================== @@ -310,6 +362,12 @@ class LocalLegislationMap extends Component {
+ Date: Mon, 17 Sep 2018 19:27:49 -0400 Subject: [PATCH 07/12] Shows the mapping data (record IDs) in info panel --- src/components/Map.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/Map.js b/src/components/Map.js index 0cfc88e..16ec457 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -106,6 +106,7 @@ class GeoJSONLayer extends React.Component { highlightFeature(e) { var layer = e.target; const { SHPFID: shpfid } = layer.feature.properties; + const { mappingData } = this.props; const info = { text: 'No data available' }; if (this.shapeToDiocese.hasOwnProperty(shpfid)) { const dioceseID = this.shapeToDiocese[shpfid]; @@ -124,6 +125,11 @@ class GeoJSONLayer extends React.Component { info.province = province; info.country = country_modern; } + + if (mappingData && mappingData.hasOwnProperty(dioceseID)) { + const recordIDs = mappingData[dioceseID]; + info.recordIDs = Array.from(recordIDs).sort(); + } } this.props.updateInfo(info); @@ -249,11 +255,23 @@ class InfoPanel extends React.Component { }, []); } + let recordIDList = null; + if (info && info.recordIDs) { + recordIDList = info.recordIDs.map((id) =>
  • {id}
  • ); + } + return (

    {text}

    {info &&
      {listItems}
    } + {info && + info.recordIDs && ( +
    + This diocese appears in: {recordIDList} + Total: ({info.recordIDs.length}) +
    + )}
    ); From b4acc4b80704c1a4a26e788986d9bd6f7be9d9ce Mon Sep 17 00:00:00 2001 From: thawsitt Date: Mon, 17 Sep 2018 22:02:47 -0400 Subject: [PATCH 08/12] Fix color bucket off-by-one error --- src/components/Map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Map.js b/src/components/Map.js index 16ec457..21652d8 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -74,7 +74,7 @@ class GeoJSONLayer extends React.Component { const numPerBucket = Math.ceil(maxNumEntries / colors.length); if (map.hasOwnProperty(dioceseID)) { const numEntries = map[dioceseID].size; - let index = Math.ceil(numEntries / numPerBucket); + let index = Math.floor((numEntries - 1) / numPerBucket); if (index > colors.length - 1) { index = colors.length - 1; } From 8edebfcce80617d2ba0cbaf5ac05b295a6119ba6 Mon Sep 17 00:00:00 2001 From: thawsitt Date: Mon, 17 Sep 2018 22:07:39 -0400 Subject: [PATCH 09/12] Update B&W color scheme --- src/assets/map_config.js | 2 +- src/components/Map.css | 2 +- src/components/Map.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/map_config.js b/src/assets/map_config.js index 76ef08f..c03223d 100644 --- a/src/assets/map_config.js +++ b/src/assets/map_config.js @@ -27,7 +27,7 @@ config.colorSchemes = { color2: ['#feebe2', '#fbb4b9', '#f768a1', '#c51b8a', '#7a0177'], color3: ['#f2f0f7', '#cbc9e2', '#9e9ac8', '#756bb1', '#54278f'], color4: ['#ffffb2', '#fecc5c', '#fd8d3c', '#f03b20', '#bd0026'], - bw: ['#f7f7f7', '#cccccc', '#969696', '#636363', '#252525'], + bw: ['#ccc', '#999', '#666', '#323232', '#000'], } export default config; diff --git a/src/components/Map.css b/src/components/Map.css index cdf4851..bf7fc9d 100644 --- a/src/components/Map.css +++ b/src/components/Map.css @@ -44,5 +44,5 @@ height: 18px; margin-right: 8px; float: left; - opacity: 0.9; + opacity: 1; } diff --git a/src/components/Map.js b/src/components/Map.js index 21652d8..859fb03 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -91,7 +91,7 @@ class GeoJSONLayer extends React.Component { opacity: 3, color: 'grey', dashArray: '3', - fillOpacity: 0.9, + fillOpacity: 1.0, }; } From d9089037b5a5b37dc12b066e31ff901e83285b65 Mon Sep 17 00:00:00 2001 From: thawsitt Date: Mon, 17 Sep 2018 23:00:08 -0400 Subject: [PATCH 10/12] Show loading spinner while fetching search results --- src/components/App.css | 37 +++++++++++++++++++++++++++++++++++++ src/components/App.js | 9 +++++++++ 2 files changed, 46 insertions(+) diff --git a/src/components/App.css b/src/components/App.css index c7a4d9d..55e7778 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -9,3 +9,40 @@ code.example { color: grey; } + +.center { + position: absolute; + z-index: 800; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.loading-circle { + display: inline-block; + width: 100px; + height: 100px; + margin: auto; + border-radius: 50%; + background: #2faee0; + opacity: 0.8; + animation: loading-circle 2.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; +} + +@keyframes loading-circle { + 0%, + 100% { + animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); + } + 0% { + transform: rotateY(0deg); + } + 50% { + transform: rotateY(1800deg); + animation-timing-function: cubic-bezier(0, 0.5, 0.5, 1); + } + 100% { + transform: rotateY(3600deg); + } +} diff --git a/src/components/App.js b/src/components/App.js index 7fea141..a6caccc 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -114,12 +114,14 @@ class App extends Component { super(props); this.state = { mappingData: null, + loading: false, }; } handleInputURL = (url) => { const testURL = url.replace('end=0', 'end=1'); let query = null; + this.setState({ loading: true }); axios .get(testURL) .then((response) => response.data) @@ -130,6 +132,7 @@ class App extends Component { }) .catch((error) => { console.error(error); + this.setState({ loading: false }); }); }; @@ -163,6 +166,7 @@ class App extends Component { console.log(dioceseMap); this.setState({ mappingData: dioceseMap, + loading: false, }); }; @@ -171,6 +175,11 @@ class App extends Component {
    + {this.state.loading && ( +
    +
    +
    + )}
    ); } From 29d8589c8d50b74269f7f46f9c8744643f81ee64 Mon Sep 17 00:00:00 2001 From: thawsitt Date: Mon, 17 Sep 2018 23:52:18 -0400 Subject: [PATCH 11/12] Show current search query in menu bar --- src/components/App.js | 10 ++++++++-- src/components/Map.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index a6caccc..6f81848 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -20,6 +20,10 @@ class TopMenuBar extends Component { + {this.props.searchTerm && ( + Current query: "{this.props.searchTerm}" + )} + v 0.1.0 @@ -113,6 +117,7 @@ class App extends Component { constructor(props) { super(props); this.state = { + searchTerm: null, mappingData: null, loading: false, }; @@ -132,7 +137,7 @@ class App extends Component { }) .catch((error) => { console.error(error); - this.setState({ loading: false }); + this.setState({ loading: false, searchTerm: null }); }); }; @@ -143,6 +148,7 @@ class App extends Component { .then((response) => response.data) .then((data) => { console.log(data); + this.setState({ searchTerm: query.q }); this.processData(data); }) .catch((error) => { @@ -173,7 +179,7 @@ class App extends Component { render() { return (
    - + {this.state.loading && (
    diff --git a/src/components/Map.js b/src/components/Map.js index 859fb03..8728b36 100644 --- a/src/components/Map.js +++ b/src/components/Map.js @@ -172,7 +172,7 @@ class GeoJSONLayer extends React.Component { render() { if (this.state.isLoading) { - return Loading...; + return ; } return ( Date: Tue, 18 Sep 2018 22:26:12 -0400 Subject: [PATCH 12/12] Update domain names to corpus-synodalium.com --- src/components/App.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/App.js b/src/components/App.js index 6f81848..d43f8fd 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -21,7 +21,9 @@ class TopMenuBar extends Component { {this.props.searchTerm && ( - Current query: "{this.props.searchTerm}" + + Current query: "{this.props.searchTerm}" + )} @@ -75,7 +77,11 @@ class ModalQuery extends React.Component {
    - +
    @@ -95,16 +101,22 @@ const ModalDescription = () => (
    • Go to the{' '} - + PhiloLogic database.
    • Make a search query.
    • Click "Export results" button on top-right corner.
    • -
    • Paste the resulting url below. The url should look something like this:
    • +
    • + Paste the resulting url below. The url should look something like this: +
    - https://rdorin.website/philologic/beta/query?report=concordance&method=proxy&q=corpus&start=0&end=0&format=json + https://corpus-synodalium.com/philologic/beta/query?report=concordance&method=proxy&q=corpus&start=0&end=0&format=json
    ); @@ -142,7 +154,7 @@ class App extends Component { }; fetchData = (query) => { - const baseURL = 'https://rdorin.website/philologic/beta/query'; + const baseURL = 'https://corpus-synodalium.com/philologic/beta/query'; axios .get(baseURL, { params: query }) .then((response) => response.data) @@ -179,7 +191,10 @@ class App extends Component { render() { return (
    - + {this.state.loading && (