false}>
└
{subject_class && (
-
+
S
-
- {subject_class || 'resource'}
+
+ {subject_class ? omitUri(subject_class) : 'resource'}
)}
- {subject_class && object_class && }
{(object_class || object_datatype) && (
O
- {object_class || object_datatype || 'resource'}
+ {(() => {
+ if (object_class) {
+ return omitUri(object_class)
+ }
+ if (object_datatype) {
+ return omitUri(object_datatype)
+ }
+ return 'resource'
+ })()}
)}
@@ -75,6 +111,7 @@ ClassRelation.displayName = 'ClassRelation'
type ClassRelationsProps = {
classRelations: ClassRelationType[]
index: number
+ propertyClass?: string
}
const selector = ({ property: { showPropertyClassIndex } }: RootState) => ({
@@ -82,7 +119,7 @@ const selector = ({ property: { showPropertyClassIndex } }: RootState) => ({
})
const ClassRelations: React.FC = (props) => {
- const { classRelations, index } = props
+ const { classRelations, index, propertyClass } = props
const {
showPropertyClassIndex: [selectedIndex1, selectedIndex2],
} = useSelector(selector)
@@ -95,6 +132,7 @@ const ClassRelations: React.FC = (props) => {
relation={relation}
indexes={[index, key]}
selected={selectedIndex1 === index && selectedIndex2 === key}
+ propertyClass={propertyClass}
/>
))}
diff --git a/node/src/ts/visualizer/components/ClassRelationsDetail.tsx b/node/src/ts/visualizer/components/ClassRelationsDetail.tsx
index 721318ba..c950809b 100644
--- a/node/src/ts/visualizer/components/ClassRelationsDetail.tsx
+++ b/node/src/ts/visualizer/components/ClassRelationsDetail.tsx
@@ -1,7 +1,8 @@
import React, { useCallback, useMemo } from 'react'
import { useIntl } from 'react-intl'
-import { useDispatch } from 'react-redux'
+import { useDispatch, useSelector } from 'react-redux'
import { DetailAction } from '../actions/detail'
+import { RootState } from '../reducers'
import { Classes } from '../types/class'
import { getPreferredLabel } from '../utils'
import GraphRepository from '../utils/GraphRepository'
@@ -15,6 +16,10 @@ type ClassRelationsDetailProps = {
showRightHand: boolean
}
+const selector = ({ detail: { showingRelation } }: RootState) => ({
+ showingRelation,
+})
+
const ClassRelationsDetail: React.FC = (props) => {
const {
title,
@@ -30,6 +35,7 @@ const ClassRelationsDetail: React.FC = (props) => {
classes,
focusingURI,
])
+ const { showingRelation } = useSelector(selector)
const getPreferredTriple = useCallback(
(triple: string[]) => {
@@ -125,6 +131,12 @@ const ClassRelationsDetail: React.FC = (props) => {
type="button"
title={getPreferredTriple(triple)}
onClick={relationClass}
+ style={{
+ backgroundColor:
+ showingRelation === rhs
+ ? 'rgba(170, 170, 170, 0.5)'
+ : undefined,
+ }}
>
{`<${triple[0]}>`}
@@ -152,6 +164,7 @@ const ClassRelationsDetail: React.FC = (props) => {
intl,
showRightHand,
getPreferredTriple,
+ showingRelation,
]
)
@@ -182,6 +195,12 @@ const ClassRelationsDetail: React.FC = (props) => {
type="button"
title={getPreferredTriple(triple)}
onClick={relationClass}
+ style={{
+ backgroundColor:
+ showingRelation === lhs
+ ? 'rgba(170, 170, 170, 0.5)'
+ : undefined,
+ }}
>
{`<${triple[0]}>`}
@@ -209,6 +228,7 @@ const ClassRelationsDetail: React.FC = (props) => {
intl,
showLeftHand,
getPreferredTriple,
+ showingRelation,
]
)
if (!classDetail) {
diff --git a/node/src/ts/visualizer/components/ClassStructure.tsx b/node/src/ts/visualizer/components/ClassStructure.tsx
index f1f9f534..efa37c1c 100644
--- a/node/src/ts/visualizer/components/ClassStructure.tsx
+++ b/node/src/ts/visualizer/components/ClassStructure.tsx
@@ -1,6 +1,7 @@
import _ from 'lodash'
import React, { useRef } from 'react'
import { useIntl } from 'react-intl'
+import { useContextMenu } from 'react-contexify'
import { useDispatch, useSelector } from 'react-redux'
import { DetailAction } from '../actions/detail'
import { RootState } from '../reducers'
@@ -12,9 +13,15 @@ import GraphRepository, {
SVGEventHandlerType,
} from '../utils/GraphRepository'
import { getChildrenRecursive } from '../utils/node'
-
import { ClassNames } from '../constants/ClassStructure'
import { TooltipAction } from '../actions/tooltip'
+import { Metadata } from '../types/metadata'
+import {
+ makeQueryWhenRightClickArrow,
+ makeQueryWhenRightClickClass,
+} from '../utils/sparql'
+
+export const CIRCLE_CONTEXT_MENU_ID = 'circle-context-menu-id'
function decideNormalClass(
d: NodeType,
@@ -115,20 +122,33 @@ type ClassStructureProps = {
nodes: NodeType[]
width: number | null
height: number | null
+ metadata: Metadata | null
+ getReferenceURL: (uri: string | null) => string | null
}
const selector = ({ detail }: RootState) => detail
const ClassStructure: React.FC = (props) => {
- const { classes, circleDiameter, nodes, width, height } = props
+ const {
+ classes,
+ metadata,
+ circleDiameter,
+ nodes,
+ width,
+ height,
+ getReferenceURL,
+ } = props
const dispatch = useDispatch()
const intl = useIntl()
+ const { show } = useContextMenu({
+ id: CIRCLE_CONTEXT_MENU_ID,
+ })
const handleClickTreeImg: SVGEventHandlerType = React.useCallback(() => {
dispatch(DetailAction.showTree())
}, [dispatch])
- const handleClickClass: SVGEventHandlerType = React.useCallback(
+ const handleClickClass = React.useCallback(
(event?: React.MouseEvent, d?: NodeType) => {
if (!d || !event || event.defaultPrevented) {
return
@@ -139,9 +159,28 @@ const ClassStructure: React.FC = (props) => {
[dispatch]
)
+ const handleRightClickClass = React.useCallback(
+ (event?: React.MouseEvent, d?: NodeType) => {
+ if (!d || !event) {
+ return
+ }
+
+ // コンテキストメニューは表示しない
+ event?.preventDefault()
+
+ const refUri = getReferenceURL(d?.data.uri ?? null)
+ if (refUri) {
+ const endpoint = metadata?.endpoint ?? ''
+ const query = makeQueryWhenRightClickClass(refUri)
+ show(event, { props: { endpoint, query } })
+ }
+ },
+ [metadata?.endpoint]
+ )
+
const handleShowTooltip: SVGEventHandlerType = React.useCallback(
(event?: React.MouseEvent, d?: NodeType) => {
- if (!d || !event || GraphRepository.isShowNodeText(d)) {
+ if (!d || !event) {
return
}
@@ -248,6 +287,31 @@ const ClassStructure: React.FC = (props) => {
GraphRepository.addTreeImg(targetKey, handleClickTreeImg)
}
+ const getPredicates = (d: NodeType) => {
+ const detail = targetClassDetail
+ const rhsProps =
+ showRhs && detail.rhs
+ ? detail.rhs
+ .filter((r) =>
+ relation
+ ? relation[0] === r[0] && relation[1] === r[1]
+ : r[1] === d.data.uri
+ )
+ .map((r) => r[0])
+ : []
+ const lhsProps =
+ showLhs && detail.lhs
+ ? detail.lhs
+ .filter((r) =>
+ relation
+ ? relation[0] === r[0] && relation[1] === r[1]
+ : r[0] === d.data.uri
+ )
+ .map((r) => r[1])
+ : []
+ return [...rhsProps, ...lhsProps]
+ }
+
function arrowMouseover(
event?: React.MouseEvent,
d?: NodeType
@@ -255,56 +319,13 @@ const ClassStructure: React.FC = (props) => {
if (!event || !d) return
const targetElement = event.currentTarget
-
- let [x1, y1] = [0, 0]
- let [x2, y2] = [0, 0]
- if (targetElement?.getAttribute('class')?.includes('self-line')) {
- x1 = GraphRepository.x(d.x)
- y1 = GraphRepository.y(d.y)
- x2 = GraphRepository.x(d.x)
- y2 = GraphRepository.y(d.y)
- } else {
- // eslint-disable-next-line no-extra-semi
- ;[[x1, y1], [x2, y2]] = targetElement
- ?.getAttribute('d')
- ?.split(' ')
- .slice(1, 3)
- .map((xy) => xy.split(',').map(Number)) || [
- [0, 0],
- [0, 0],
- ]
- }
-
- const predicates: string[] = []
- if (showRhs && targetClassDetail.rhs) {
- Array.prototype.push.apply(
- predicates,
- targetClassDetail.rhs
- .filter((r) =>
- relation
- ? relation[0] === r[0] && relation[1] === r[1]
- : r[1] === d.data.uri
- )
- .map((r) => r[0])
- )
- }
- if (showLhs && targetClassDetail.lhs) {
- Array.prototype.push.apply(
- predicates,
- targetClassDetail.lhs
- .filter((r) =>
- relation
- ? relation[0] === r[0] && relation[1] === r[1]
- : r[0] === d.data.uri
- )
- .map((r) => r[1])
- )
- }
-
+ const x = Number(targetElement.getAttribute('cx')) + 10
+ const y = Number(targetElement.getAttribute('cy')) + 10
+ const predicates = getPredicates(d)
const predicateMessage = intl.formatMessage({
id: 'classStructure.text.predicate',
})
- GraphRepository.addPopup(x1, y1, x2, y2, predicates, predicateMessage)
+ GraphRepository.addPopup(x, y, predicates, predicateMessage)
GraphRepository.updatePosition()
}
@@ -313,14 +334,56 @@ const ClassStructure: React.FC = (props) => {
GraphRepository.removePopup()
}
- GraphRepository.addArrowLineEvent(arrowMouseover, arrowMouseout)
+ const arrowRightClick = (
+ event?: React.MouseEvent,
+ d?: NodeType
+ ) => {
+ if (!event || !d) return
+
+ // コンテキストメニューは表示しない
+ event?.preventDefault()
+
+ const focusingUri = getReferenceURL(target.data.uri)!
+ const targetUri = getReferenceURL(d.data.uri)!
+ const predicateUris = getPredicates(d).map((p) => getReferenceURL(p)!)
+
+ const makeTriple = (): [string, string[], string] => {
+ const pathTypes = event.currentTarget?.classList
+ if (pathTypes?.contains('left-hand-line')) {
+ return [targetUri, predicateUris, focusingUri]
+ }
+ if (pathTypes?.contains('self-line')) {
+ return [targetUri, predicateUris, targetUri]
+ }
+ return [focusingUri, predicateUris, targetUri]
+ }
+
+ const endpoint = metadata?.endpoint ?? ''
+ const query = makeQueryWhenRightClickArrow(...makeTriple())
+ show(event, { props: { endpoint, query } })
+ }
+
+ GraphRepository.addArrowLineEvent(
+ arrowMouseover,
+ arrowMouseout,
+ arrowRightClick
+ )
const selfPath: NodeType[] = isOneself ? [target] : []
- GraphRepository.updateSelfLines(selfPath, arrowMouseover, arrowMouseout)
+ GraphRepository.updateSelfLines(
+ selfPath,
+ arrowMouseover,
+ arrowMouseout,
+ arrowRightClick
+ )
GraphRepository.avoidColidedLabel()
- GraphRepository.showNodes(visibleNodes, handleClickClass, intl.locale)
+ GraphRepository.showNodes(
+ visibleNodes,
+ handleClickClass,
+ handleRightClickClass
+ )
const decideClass = (d: NodeType) => {
if (_.includes(both, d.data.uri)) {
@@ -355,15 +418,18 @@ const ClassStructure: React.FC = (props) => {
)
const showCircles = React.useCallback(
- (circles: NodeType[], animate: boolean, updateScale: boolean = true) => {
- if (circles.length === 0) {
- return
- }
-
- if (updateScale) {
+ (
+ circles: NodeType[],
+ animate: boolean,
+ updateScale: boolean = true,
+ transparentLabel: boolean = false
+ ) => {
+ if (updateScale && circles.length > 0) {
GraphRepository.calcCircleScale(circles)
GraphRepository.updateScale()
}
+
+ GraphRepository.transparentLabel = transparentLabel
if (animate) {
GraphRepository.updatePositionWithAnimate()
} else {
@@ -377,13 +443,67 @@ const ClassStructure: React.FC = (props) => {
)
const showPropertyClass = React.useCallback(
- (domain: string | null, range: string | null) => {
+ (uri: string | null, domain: string | null, range: string | null) => {
const domainNodes = domain
? GraphRepository.nodes.filter((d) => d.data.uri === domain)
: []
const rangeNodes = range
? GraphRepository.nodes.filter((d) => d.data.uri === range)
: []
+
+ const sbj = domainNodes.length > 0 ? domainNodes[0] : null
+ GraphRepository.targetKey = sbj?.data?.key ?? null
+
+ const domainClassDetail = GraphRepository.classes[domain || '']
+ const rangeClassDetail = GraphRepository.classes[range || '']
+ const hasNoMultipleInheritance = (classDetail: ClassDetail) =>
+ classDetail &&
+ (!classDetail.subClassOf ||
+ (!!classDetail.subClassOf && classDetail.subClassOf.length === 1)) // 親がいるなら多重継承でないものに限る
+ const canDrawTriple =
+ hasNoMultipleInheritance(domainClassDetail) &&
+ hasNoMultipleInheritance(rangeClassDetail)
+
+ const arrowRightClick = (
+ event?: React.MouseEvent,
+ d?: NodeType
+ ) => {
+ if (!event || !d) return
+
+ // コンテキストメニューは表示しない
+ event.preventDefault()
+
+ const propertyUri = getReferenceURL(uri)!
+ const domainUri = getReferenceURL(domain)!
+ const rangeUri = getReferenceURL(range)!
+
+ const makeTriple = (): [string, string[], string] => {
+ const pathTypes = event.currentTarget?.classList
+ if (pathTypes?.contains('self-line')) {
+ return [domainUri, [propertyUri], domainUri]
+ }
+ return [domainUri, [propertyUri], rangeUri]
+ }
+
+ const endpoint = metadata?.endpoint ?? ''
+ const query = makeQueryWhenRightClickArrow(...makeTriple())
+ show(event, { props: { endpoint, query } })
+ }
+
+ const obj = rangeNodes.length > 0 ? rangeNodes[0] : null
+ if (domain && range && obj !== null && canDrawTriple) {
+ if (domain !== range) {
+ GraphRepository.updateRightLines([obj], arrowRightClick)
+ } else {
+ GraphRepository.updateSelfLines(
+ [obj],
+ undefined,
+ undefined,
+ arrowRightClick
+ )
+ }
+ }
+
const focusRootNodes = _.union(
getVisibleNodes(domainNodes),
getVisibleNodes(rangeNodes)
@@ -395,7 +515,11 @@ const ClassStructure: React.FC = (props) => {
const visibleNodesSet = getNodeSet(visibleNodes)
GraphRepository.visibleNodesSet = visibleNodesSet
- GraphRepository.showNodes(visibleNodes, handleClickClass, intl.locale)
+ GraphRepository.showNodes(
+ visibleNodes,
+ handleClickClass,
+ handleRightClickClass
+ )
GraphRepository.avoidColidedLabel()
let decideClass
@@ -435,7 +559,7 @@ const ClassStructure: React.FC = (props) => {
GraphRepository.addClass(visibleNodes, decideClass)
return _.union(domainNodes, rangeNodes, focusRootNodes)
},
- [handleClickClass, intl.locale]
+ [handleClickClass]
)
const search = React.useCallback(
@@ -451,7 +575,11 @@ const ClassStructure: React.FC = (props) => {
const visibleNodesSet = getNodeSet(visibleNodes)
GraphRepository.visibleNodesSet = visibleNodesSet
- GraphRepository.showNodes(visibleNodes, handleClickClass, intl.locale)
+ GraphRepository.showNodes(
+ visibleNodes,
+ handleClickClass,
+ handleRightClickClass
+ )
GraphRepository.avoidColidedLabel()
const decideClass = (d: NodeType) => {
@@ -464,7 +592,7 @@ const ClassStructure: React.FC = (props) => {
GraphRepository.addClass(visibleNodes, decideClass)
return matchedNodes
},
- [handleClickClass, intl.locale]
+ [handleClickClass]
)
const detail = useSelector(selector)
@@ -478,7 +606,7 @@ const ClassStructure: React.FC = (props) => {
showLeftHand,
showingRelation,
searchingURI,
- propertyClass: { domain, range },
+ propertyClass: { domain, range, uri: propertyUri },
} = detailState
GraphRepository.targetKey = focusingCircleKey
@@ -488,47 +616,32 @@ const ClassStructure: React.FC = (props) => {
if (showingRelation) {
showCircles(
focus(focusingCircleKey, true, true, showingRelation),
- animate
+ animate,
+ true,
+ true
)
return
}
if (showRightHand || showLeftHand) {
showCircles(
focus(focusingCircleKey, showRightHand, showLeftHand),
- animate
+ animate,
+ true,
+ true
)
return
}
- showCircles(focus(focusingCircleKey), animate)
+ showCircles(focus(focusingCircleKey), animate, true, true)
return
}
if (domain || range) {
focus(0)
- const subject = GraphRepository.findUriNode(domain)
- const object = GraphRepository.findUriNode(range)
- GraphRepository.targetKey = subject ? subject.data.key : null
-
- const domainClassDetail = classes[domain || '']
- const rangeClassDetail = classes[range || '']
-
- const hasNoMultipleInheritance = (classDetail: ClassDetail) =>
- classDetail &&
- (!classDetail.subClassOf ||
- (!!classDetail.subClassOf && classDetail.subClassOf.length === 1)) // 親がいるなら多重継承でないものに限る
- const canDrawTriple =
- hasNoMultipleInheritance(domainClassDetail) &&
- hasNoMultipleInheritance(rangeClassDetail)
-
- if (domain && range && object !== undefined && canDrawTriple) {
- if (domain !== range) {
- GraphRepository.updateRightLines([object])
- } else {
- GraphRepository.updateSelfLines([object])
- }
+ const targetNodes = showPropertyClass(propertyUri, domain, range)
+ if (targetNodes.length > 0) {
+ showCircles(targetNodes, animate, true, true)
+ return
}
- showCircles(showPropertyClass(domain, range), animate)
- return
}
if (searchingURI) {
focus(0)
@@ -542,8 +655,11 @@ const ClassStructure: React.FC = (props) => {
)
return
}
- showCircles(matchedNodes, animate)
- return
+
+ if (matchedNodes.length > 0) {
+ showCircles(matchedNodes, animate, true, true)
+ return
+ }
}
showCircles(focus(0), animate)
@@ -568,6 +684,7 @@ const ClassStructure: React.FC = (props) => {
)
React.useEffect(() => {
+ GraphRepository.locale = intl.locale
GraphRepository.classes = classes
GraphRepository.updateNode(nodes)
GraphRepository.removeCircles()
@@ -577,58 +694,53 @@ const ClassStructure: React.FC = (props) => {
GraphRepository.setSearching()
GraphRepository.setArrowHead()
- const [nonNullWidth, nonNullHeight, nonNullDiameter] = [
- width || 0,
- height || 0,
- circleDiameter || 1,
- ]
- GraphRepository.initialRootCircleSize = nonNullDiameter
-
- onResize(nonNullWidth, nonNullHeight, nonNullDiameter)
-
- GraphRepository.manuallyZoomed = false
- update(detail, true)
-
if (isIE11) {
setInterval(GraphRepository.forceRedrawLines, 10)
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [classes, nodes])
+ }, [isIE11, classes, nodes, intl.locale])
const oldPropsRef = useRef({ oldWidth: width, oldHeight: height })
const mounted = React.useRef(false)
+ const updating = React.useRef(false)
React.useEffect(() => {
+ if (updating.current) {
+ return
+ }
+
if (mounted.current) {
- const [nonNullWidth, nonNullHeight, nonNullDiameter] = [
- width || 0,
- height || 0,
- circleDiameter || 1,
- ]
const { oldWidth, oldHeight } = oldPropsRef.current
-
if (width !== oldWidth || height !== oldHeight) {
- onResize(nonNullWidth, nonNullHeight, nonNullDiameter)
+ onResize(width ?? 0, height ?? 0, circleDiameter ?? 1)
}
const { current: oldDetail } = oldDetailStateRef
- if (classes) {
- if (
- width !== oldWidth ||
- height !== oldHeight ||
- detail.focusingCircleKey !== oldDetail.focusingCircleKey ||
- detail.showRightHand !== oldDetail.showRightHand ||
- detail.showLeftHand !== oldDetail.showLeftHand ||
- detail.showingRelation !== oldDetail.showingRelation ||
- detail.propertyClass.domain !== oldDetail.propertyClass.domain ||
- detail.propertyClass.range !== oldDetail.propertyClass.range ||
- detail.searchingURI !== oldDetail.searchingURI
- ) {
- GraphRepository.manuallyZoomed = false
- update(detail, true)
- }
+ if (
+ classes ||
+ width !== oldWidth ||
+ height !== oldHeight ||
+ detail.focusingCircleKey !== oldDetail.focusingCircleKey ||
+ detail.showRightHand !== oldDetail.showRightHand ||
+ detail.showLeftHand !== oldDetail.showLeftHand ||
+ detail.showingRelation !== oldDetail.showingRelation ||
+ detail.propertyClass.domain !== oldDetail.propertyClass.domain ||
+ detail.propertyClass.range !== oldDetail.propertyClass.range ||
+ detail.searchingURI !== oldDetail.searchingURI
+ ) {
+ updating.current = true
+ GraphRepository.manuallyZoomed = false
+ update(detail, true)
+ updating.current = false
}
} else {
mounted.current = true
+
+ GraphRepository.initialRootCircleSize = circleDiameter ?? 1
+ onResize(width ?? 0, height ?? 0, circleDiameter ?? 1)
+
+ updating.current = true
+ GraphRepository.manuallyZoomed = false
+ update(detail, true)
+ updating.current = false
}
oldPropsRef.current = { oldWidth: width, oldHeight: height }
@@ -646,8 +758,8 @@ const ClassStructure: React.FC = (props) => {
width,
])
- const baseElement = React.useMemo(
- () => (
+ return (
+ <>
@@ -669,13 +781,11 @@ const ClassStructure: React.FC = (props) => {
+
- ),
- []
+ >
)
-
- return baseElement
}
ClassStructure.displayName = 'ClassStructure'
diff --git a/node/src/ts/visualizer/components/Filter.tsx b/node/src/ts/visualizer/components/Filter.tsx
index 4e916b8a..0c17da6f 100644
--- a/node/src/ts/visualizer/components/Filter.tsx
+++ b/node/src/ts/visualizer/components/Filter.tsx
@@ -16,11 +16,11 @@ const Filter: React.FC = () => {
const query = useQuery()
const defaultEntitiesLimit = useMemo(() => {
- const limit = Number(query.get('lower_limit'))
+ const limit = Number(query.get('lower_limit')) || 1
if (Number.isInteger(limit)) {
return limit
}
- return 0
+ return 1
}, [])
const handleClick = useCallback(() => {
diff --git a/node/src/ts/visualizer/components/Graph.tsx b/node/src/ts/visualizer/components/Graph.tsx
index 932cf7b2..1a2a8831 100644
--- a/node/src/ts/visualizer/components/Graph.tsx
+++ b/node/src/ts/visualizer/components/Graph.tsx
@@ -1,6 +1,8 @@
import * as d3 from 'd3'
import React, { useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
+import { Menu, Item, ItemParams } from 'react-contexify'
+
import _ from 'lodash'
import { UiAction } from '../actions/ui'
import { Classes } from '../types/class'
@@ -11,14 +13,18 @@ import { createNodeStructure } from '../utils/node'
import { NodeType } from '../utils/GraphRepository'
import Legend from './Legend'
import Breadcrumbs from './Breadcrumbs'
-import ClassStructure from './ClassStructure'
+import ClassStructure, { CIRCLE_CONTEXT_MENU_ID } from './ClassStructure'
import { Tree } from './Tree'
import LoadingSpinner from './LoadingSpinner'
import Filter from './Filter'
+import { Metadata } from '../types/metadata'
+import { navigateToYasgui } from '../utils/sparql'
type GraphProps = {
classes: Classes
structure: Structure[]
+ metadata: Metadata | null
+ getReferenceURL: (uri: string | null) => string | null
}
const selector = ({
@@ -31,6 +37,25 @@ const selector = ({
showTree,
})
+const ContextMenu: React.VFC = () => {
+ return (
+
+ )
+}
+
const calcPosition = (node: NodeType, circleDiameter: number) => {
const newNode: NodeType = node
@@ -92,7 +117,7 @@ const avoidStackedCircle = (
}
const Graph: React.FC = (props) => {
- const { classes, structure } = props
+ const { classes, structure, metadata, getReferenceURL } = props
const { circleDiameter, svgWidth, svgHeight, showTree } = useSelector(
selector
)
@@ -114,13 +139,13 @@ const Graph: React.FC = (props) => {
React.useEffect(() => {
if (circleDiameter && structure.length > 0 && classes && structure) {
- const diameter = circleDiameter || 0
+ const diameter = circleDiameter || 1
const pack = (data: NodeStructure) => {
return d3.pack().size([diameter, diameter])(
d3
.hierarchy(data)
- .sum((d) => classes[d.uri]?.entities || 1)
- .sort((a, b) => (a.value || 1) - (b.value || 1))
+ .sum((d) => classes[d.uri]?.entities || 0.5) // entityが1かfalsyかで差をつける
+ .sort((a, b) => (a.value ?? 0) - (b.value ?? 0))
)
}
const root: NodeStructure = createNodeStructure(structure)
@@ -144,9 +169,11 @@ const Graph: React.FC = (props) => {
)}
@@ -156,6 +183,7 @@ const Graph: React.FC = (props) => {
containerEl={containerRef.current}
loadElSelector="circle.root"
/>
+
)
}
diff --git a/node/src/ts/visualizer/components/Prefix.tsx b/node/src/ts/visualizer/components/Prefix.tsx
index d42f39a2..a067fc71 100644
--- a/node/src/ts/visualizer/components/Prefix.tsx
+++ b/node/src/ts/visualizer/components/Prefix.tsx
@@ -32,7 +32,7 @@ const Prefix: React.FC = (props) => {
.sort()
.map((key, idx) => [
- @prefix : {key}
+ @prefix {key}:
,
<
diff --git a/node/src/ts/visualizer/components/Property.tsx b/node/src/ts/visualizer/components/Property.tsx
index 1e8bd47e..88ba10b4 100644
--- a/node/src/ts/visualizer/components/Property.tsx
+++ b/node/src/ts/visualizer/components/Property.tsx
@@ -1,10 +1,11 @@
/* eslint-disable camelcase */
-import React, { useCallback } from 'react'
+import React, { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { PropertyAction } from '../actions/property'
import { RootState } from '../reducers'
import { Property as PropertyType } from '../types/property'
import ClassRelations from './ClassRelations'
+import { omitUri } from '../utils'
type PropertyProps = {
property: PropertyType
@@ -40,6 +41,10 @@ const Property: React.FC = (props) => {
const open = isOpen ? 'open' : ''
const className = [refered, open].join(' ')
+ const isOmittingUri = useMemo(() => {
+ return omitUri(uri) !== uri
+ }, [uri])
+
return (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
= (props) => {
onClick={class_relations.length ? handleClick : undefined}
onKeyDown={() => false}
>
- {uri}
+
+ {omitUri(uri)}
+
{triples}
{class_relations.length > 0 && }
{isOpen && (
-
+
)}
)
diff --git a/node/src/ts/visualizer/components/PropertyList.tsx b/node/src/ts/visualizer/components/PropertyList.tsx
index 9ad30164..67a4d1e7 100644
--- a/node/src/ts/visualizer/components/PropertyList.tsx
+++ b/node/src/ts/visualizer/components/PropertyList.tsx
@@ -1,6 +1,7 @@
-import React, { useCallback } from 'react'
+import React, { useCallback, useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useDispatch, useSelector } from 'react-redux'
+import ReactTooltip from 'react-tooltip'
import { UiAction } from '../actions/ui'
import { RootState } from '../reducers'
import { Property as PropertyType } from '../types/property'
@@ -11,16 +12,24 @@ type PropertyListProps = {
properties: PropertyType[]
}
-const selector = ({ ui: { propertyPaneVisibility } }: RootState) => ({
+const selector = ({
+ ui: { propertyPaneVisibility },
+ property: { openPropertyIndexes },
+}: RootState) => ({
propertyPaneVisibility,
+ openPropertyIndexes,
})
const PropertyList: React.FC = (props) => {
const { properties } = props
- const { propertyPaneVisibility } = useSelector(selector)
+ const { propertyPaneVisibility, openPropertyIndexes } = useSelector(selector)
const dispatch = useDispatch()
const intl = useIntl()
+ useEffect(() => {
+ ReactTooltip.rebuild()
+ }, [properties, openPropertyIndexes])
+
const handleToggle = useCallback(() => {
dispatch(UiAction.hidePropertyPane())
}, [dispatch])
@@ -38,11 +47,6 @@ const PropertyList: React.FC = (props) => {
- -
-
-
-
-
-
diff --git a/node/src/ts/visualizer/components/Tooltip.tsx b/node/src/ts/visualizer/components/Tooltip.tsx
index 34a6caa9..13a285d6 100644
--- a/node/src/ts/visualizer/components/Tooltip.tsx
+++ b/node/src/ts/visualizer/components/Tooltip.tsx
@@ -1,8 +1,10 @@
-import React, { useEffect, useRef, useState } from 'react'
+import React, { useEffect, useRef, useState, useMemo } from 'react'
import { useSelector } from 'react-redux'
+import { useIntl } from 'react-intl'
import { RootState } from '../reducers'
import { Classes } from '../types/class'
import SubjectDetail from './SubjectDetail'
+import { getPreferredLabel } from '../utils'
type TooltipProps = {
classes: Classes
@@ -13,6 +15,8 @@ const selector = ({ tooltip: { pos, uri } }: RootState) => ({
uri,
})
+type ArrowType = 'upward' | 'downward' | 'none'
+
const Tooltip: React.FC = (props) => {
const { classes } = props
const { pos, uri } = useSelector(selector)
@@ -21,7 +25,7 @@ const Tooltip: React.FC = (props) => {
x: 0,
y: 0,
visible: false,
- isOnBottom: false,
+ arrowType: 'none' as ArrowType,
})
const mounted = useRef(false)
@@ -30,51 +34,91 @@ const Tooltip: React.FC = (props) => {
useEffect(() => {
if (mounted.current) {
const { uri: oldUri } = oldTooltipStateRef.current
-
- if (uri !== oldUri) {
- setState({ ...state, visible: false })
-
- if (uri && !state.visible) {
- const tooltip = tooltipRef.current?.getBoundingClientRect()
-
- if (tooltip && pos) {
- const onBottom = pos.bottom < tooltip.height
- const arrowSize = 25
-
- setState({
- x: (pos.left + pos.right - tooltip.width) / 2,
- y: onBottom
- ? pos.bottom + arrowSize
- : pos.top - tooltip.height - arrowSize,
- visible: true,
- isOnBottom: onBottom,
- })
- }
- }
+ if (uri === oldUri) {
+ return
}
+ setState({ ...state, visible: false })
oldTooltipStateRef.current = { uri }
+
+ const tooltip = tooltipRef.current?.getBoundingClientRect()
+ const boundary = document
+ .getElementById('classes-structure')
+ ?.getBoundingClientRect()
+ if (!uri || !tooltip || !pos || !boundary) {
+ return
+ }
+
+ const arrowSize = 25
+ const [width, height] = [tooltip.width, tooltip.height + arrowSize]
+
+ const canPlaceOnTop = boundary.top <= pos.top - height
+ const canPlaceOnBottom = pos.bottom + height <= boundary.bottom
+
+ const x = (pos.left + pos.right - width) / 2
+ const outOfLeftBoundary = x < boundary.left
+ const outOfRightBoundary = boundary.right < x + width
+
+ if (
+ (canPlaceOnTop || canPlaceOnBottom) &&
+ !outOfLeftBoundary &&
+ !outOfRightBoundary
+ ) {
+ setState({
+ x,
+ y: canPlaceOnTop ? pos.top - height : pos.bottom + arrowSize,
+ visible: true,
+ arrowType: canPlaceOnTop ? 'downward' : 'upward',
+ })
+ } else {
+ const margin = 16
+ setState({
+ x: boundary.left + margin,
+ y: boundary.bottom - (tooltip.height + margin),
+ visible: true,
+ arrowType: 'none',
+ })
+ }
} else {
mounted.current = true
}
}, [classes, pos, uri])
- return (
-
- )
+ const intl = useIntl()
+ const { x, y, visible, arrowType } = state
+ const tooltipElement = useMemo(() => {
+ if (!uri) {
+ return null
+ }
+
+ const detail = classes[uri]
+ const entities = detail?.entities
+ const preferredLabel = getPreferredLabel(uri, classes, intl.locale)
+ return (
+
+ )
+ }, [uri, intl.locale, classes, x, y, visible, arrowType])
+
+ return tooltipElement
}
export default Tooltip
diff --git a/node/src/ts/visualizer/components/Tree.tsx b/node/src/ts/visualizer/components/Tree.tsx
index 9e1a60cf..c908163c 100644
--- a/node/src/ts/visualizer/components/Tree.tsx
+++ b/node/src/ts/visualizer/components/Tree.tsx
@@ -232,12 +232,12 @@ export const Tree: React.FC = (props) => {
return treeNodes.concat(
multipleInheritanceNodes.map((superClass, i) => ({
...superClass,
- depth: focusingNode.parent?.depth || 0,
+ depth: focusingNode.parent?.depth ?? 0,
data: {
...superClass.data,
isMultipleInheritanceSource: true,
oldTreeY: superClass.data.treeY,
- treeY: (focusingNode.data?.treeY || 0) + (Margin.Y / 2) * (i + 1),
+ treeY: (focusingNode.data?.treeY ?? 0) + (Margin.Y / 2) * (i + 1),
},
}))
)
@@ -343,7 +343,7 @@ export const Tree: React.FC = (props) => {
const rect = element.firstChild?.parentElement?.getBoundingClientRect()
return rect ? rect[prop] : 0
}
- return _.max(_.map(children, (child) => getGSize(child, prop))) || 0
+ return _.max(_.map(children, (child) => getGSize(child, prop))) ?? 0
},
[]
)
@@ -370,7 +370,7 @@ export const Tree: React.FC = (props) => {
const treeNodesRef = React.useRef([])
const oldTreeNodesRef = React.useRef([])
React.useEffect(() => {
- TreeRepository.setFocusingNodes(focusingCircleKey || 0)
+ TreeRepository.setFocusingNodes(focusingCircleKey ?? 0)
TreeRepository.setSvg()
TreeRepository.setFilters()
@@ -402,7 +402,7 @@ export const Tree: React.FC = (props) => {
let nextState: TreeState = state
if (focusingURI !== prevFocusingURI) {
- TreeRepository.setFocusingNodes(focusingCircleKey || 0)
+ TreeRepository.setFocusingNodes(focusingCircleKey ?? 0)
const { hiddenUris } = state
const newHiddenUris = hiddenUris.filter((uri) => uri !== focusingURI)
diff --git a/node/src/ts/visualizer/constants/ClassStructure.ts b/node/src/ts/visualizer/constants/ClassStructure.ts
index 956e8cfd..5789b46d 100644
--- a/node/src/ts/visualizer/constants/ClassStructure.ts
+++ b/node/src/ts/visualizer/constants/ClassStructure.ts
@@ -1,6 +1,6 @@
export const FOCUSING_PADDING = 50
export const SHOW_TEXT_MIN_CIRCLE_DIAMETER = 60
-export const SHOW_TEXT_MAX_CIRCLE_DIAMETER = SHOW_TEXT_MIN_CIRCLE_DIAMETER * 4
+export const SHOW_TEXT_MAX_CIRCLE_DIAMETER = SHOW_TEXT_MIN_CIRCLE_DIAMETER * 6
export const HIGHLIGHTING_MIN_SIZE = 17
diff --git a/node/src/ts/visualizer/index.tsx b/node/src/ts/visualizer/index.tsx
index 0ef92c63..872dbcff 100644
--- a/node/src/ts/visualizer/index.tsx
+++ b/node/src/ts/visualizer/index.tsx
@@ -17,11 +17,10 @@ import {
} from 'react-redux'
import _ from 'lodash'
import { useHistory } from 'react-router-dom'
+import ReactTooltip from 'react-tooltip'
import { getLocaleMessages, getLocaleShortString, useQuery } from './utils'
-import { Property } from './types/property'
-import { Prefixes } from './types/prefix'
-import { Classes } from './types/class'
import { Structure } from './types/structure'
+import { Metadata } from './types/metadata'
import Prefix from './components/Prefix'
import Tooltip from './components/Tooltip'
import SearchBox from './components/SearchBox'
@@ -35,24 +34,8 @@ import ApiClient from '../ApiClient'
import { useDBCLSFooter } from '../useDBCLSFooter'
import { RootState } from './reducers'
import { FilterAction } from './actions/filter'
-
-declare global {
- interface Document {
- documentMode?: number
- }
-
- interface Navigator {
- userLanguage?: string
- browserLanguage?: string
- }
-}
-
-export type AppState = {
- structure: Structure[]
- classes: Classes
- properties: Property[]
- prefixes: Prefixes
-}
+import { flattenStructure } from './utils/node'
+import { AppState, Content } from './types'
const initialAppState: AppState = {
structure: [],
@@ -61,7 +44,7 @@ const initialAppState: AppState = {
prefixes: {},
}
-const filterContent = (
+const filterStateDestructive = (
state: AppState,
condition: (uri: string) => boolean
) => {
@@ -103,26 +86,18 @@ const filterContent = (
})
}
-const isEmptyContent = (content: AppState) => {
+const isEmptyState = (state: AppState) => {
if (
- content.structure.length === 0 &&
- content.properties.length === 0 &&
- Object.keys(content.classes).length === 0 &&
- Object.keys(content.prefixes).length === 0
+ state.structure.length === 0 &&
+ state.properties.length === 0 &&
+ Object.keys(state.classes).length === 0 &&
+ Object.keys(state.prefixes).length === 0
) {
return true
}
return false
}
-export type Content = {
- inheritance_structure: Structure[]
- classes: Classes
- properties: Property[]
- prefixes: Prefixes
- meta_data: any
-}
-
type AppProps = {
content: Content
}
@@ -139,8 +114,8 @@ const App: React.FC = (props) => {
const [rawState, setRawState] = useState(initialAppState)
const [state, setState] = useState(initialAppState)
+ const [metadata, setMetadata] = useState(null)
- // utility
const getReferenceURL = useCallback(
(uri: string | null) => {
if (uri === null) {
@@ -155,15 +130,16 @@ const App: React.FC = (props) => {
},
[state.prefixes]
)
+
useEffect(() => {
- const preferredContent = {
+ const nextState = {
structure: content.inheritance_structure,
classes: content.classes,
properties: content.properties,
prefixes: content.prefixes,
}
- if (isEmptyContent(preferredContent) || !isEmptyContent(rawState)) {
+ if (isEmptyState(nextState) || !isEmptyState(rawState)) {
return
}
@@ -174,28 +150,28 @@ const App: React.FC = (props) => {
ApiClient.checkHealthy().then((res) => {
if (res.data.ok) {
- // set blacklist
Blacklist.configre({
classes: '/static/blacklists/bcl.txt',
prefixes: '/static/blacklists/bpr.txt',
})
- // filter content
const existsInBlacklist = (uri: string) =>
- Blacklist.has(uri, preferredContent.prefixes)
- filterContent(preferredContent, existsInBlacklist)
- setRawState(preferredContent)
+ Blacklist.has(uri, content.prefixes)
+ filterStateDestructive(nextState, existsInBlacklist)
+
+ setRawState(nextState)
+ setMetadata(content.meta_data)
}
})
}, [content, rawState]) // eslint-disable-line react-hooks/exhaustive-deps
const { lowerLimitOfClassEntities } = useSelector(selector)
useEffect(() => {
- if (isEmptyContent(rawState)) {
+ if (isEmptyState(rawState)) {
return
}
- if (lowerLimitOfClassEntities === 0) {
+ if (lowerLimitOfClassEntities <= 1) {
history.push({
pathname: history.location.pathname,
})
@@ -208,46 +184,40 @@ const App: React.FC = (props) => {
search: `?lower_limit=${lowerLimitOfClassEntities}`,
})
- const flattenChildren = (elem: Structure): Structure[] => {
- if (elem.children === undefined) {
- return [elem]
- }
- return _.flatMap(elem.children, flattenChildren).concat([elem])
- }
-
const urisToHide = rawState.structure
- .flatMap((elem) => flattenChildren(elem))
- .filter((elem) => {
- const classDetail = rawState.classes[elem.uri]
- return (
- elem.children === undefined &&
- (classDetail === undefined ||
- classDetail.entities === undefined ||
- classDetail.entities < lowerLimitOfClassEntities)
- )
+ .flatMap((e) => flattenStructure(e))
+ .filter((e) => {
+ const detail = rawState.classes[e.uri]
+ const hasNoChildren = e.children === undefined
+ const entityUndefined = !detail || detail.entities === undefined
+ const lessThanLimit = (detail.entities ?? 0) < lowerLimitOfClassEntities
+ return hasNoChildren && (entityUndefined || lessThanLimit)
})
- .reduce<{ [key: string]: true }>((prev, cur) => {
- // eslint-disable-next-line no-param-reassign
- prev[cur.uri] = true
- return prev
- }, {})
+ .reduce((prev, cur) => prev.add(cur.uri), new Set())
- const shouldHideElement = (uri: string) => urisToHide[uri]
+ const shouldHideElement = (uri: string) => urisToHide.has(uri)
const nextState = _.cloneDeep(rawState)
- filterContent(nextState, shouldHideElement)
+ filterStateDestructive(nextState, shouldHideElement)
setState(nextState)
}, [rawState, lowerLimitOfClassEntities])
+ const { structure, classes, properties, prefixes } = state
return (
)
}
@@ -270,8 +240,14 @@ const Visualizer: React.FC = (props) => {
}, [])
const footerElement = useMemo(() => {
- // eslint-disable-next-line react/no-danger
- return
+ return (
+
+ )
}, [copyElement])
return (
diff --git a/node/src/ts/visualizer/locales/en.ts b/node/src/ts/visualizer/locales/en.ts
index b088c45b..dc343656 100644
--- a/node/src/ts/visualizer/locales/en.ts
+++ b/node/src/ts/visualizer/locales/en.ts
@@ -36,7 +36,6 @@ const messages = {
'legend.class.parent': 'Parent class',
'propertyList.hideableWrapper.target': 'Property',
'propertyList.title': 'Properties in the dataset',
- 'propertyList.legend.label': 'Legend',
'propertyList.legend.text': 'Property',
'propertyList.legend.tripleCount': 'Triple count',
'searchBox.input.placeholder.search': 'Search',
diff --git a/node/src/ts/visualizer/locales/ja.ts b/node/src/ts/visualizer/locales/ja.ts
index c3ac9a29..2ecfecff 100644
--- a/node/src/ts/visualizer/locales/ja.ts
+++ b/node/src/ts/visualizer/locales/ja.ts
@@ -35,7 +35,6 @@ const messages = {
'legend.class.parent': '親クラス',
'propertyList.hideableWrapper.target': 'プロパティ',
'propertyList.title': 'データセット内プロパティ',
- 'propertyList.legend.label': '凡例',
'propertyList.legend.text': 'プロパティ名',
'propertyList.legend.tripleCount': 'トリプルの数',
'searchBox.input.placeholder.search': '検索する',
diff --git a/node/src/ts/visualizer/reducers/detail.ts b/node/src/ts/visualizer/reducers/detail.ts
index 4aa8a3ec..918158b5 100644
--- a/node/src/ts/visualizer/reducers/detail.ts
+++ b/node/src/ts/visualizer/reducers/detail.ts
@@ -6,6 +6,7 @@ export interface DetailState {
focusingCircleKey: number | null
showParentClassesURI: string | null
propertyClass: {
+ uri: string | null
domain: string | null
range: string | null
}
@@ -21,7 +22,7 @@ const initialState: DetailState = {
focusingURI: null,
focusingCircleKey: null,
showParentClassesURI: null,
- propertyClass: { domain: null, range: null },
+ propertyClass: { uri: null, domain: null, range: null },
showRightHand: false,
showLeftHand: false,
showingRelation: null,
@@ -40,7 +41,7 @@ export default function detail(
...state,
focusingURI: action.payload.uri,
showParentClassesURI: action.payload.uri,
- propertyClass: { domain: null, range: null },
+ propertyClass: { uri: null, domain: null, range: null },
searchingURI: null,
}
case types.FOCUS_CIRCLE_KEY:
@@ -48,7 +49,7 @@ export default function detail(
...state,
focusingCircleKey: action.payload.key,
focusingURI: action.payload.uri,
- propertyClass: { domain: null, range: null },
+ propertyClass: { uri: null, domain: null, range: null },
showRightHand: false,
showLeftHand: false,
showingRelation: null,
@@ -68,6 +69,7 @@ export default function detail(
showRightHand: false,
showLeftHand: false,
propertyClass: {
+ uri: action.payload.uri,
domain: action.payload.domain,
range: action.payload.range,
},
@@ -182,10 +184,7 @@ export default function detail(
focusingURI: null,
searchingURI: action.payload.uri,
showTree: false,
- propertyClass: {
- domain: null,
- range: null,
- },
+ propertyClass: { uri: null, domain: null, range: null },
}
default:
return state
diff --git a/node/src/ts/visualizer/reducers/filter.ts b/node/src/ts/visualizer/reducers/filter.ts
index 36449aa9..76f00228 100644
--- a/node/src/ts/visualizer/reducers/filter.ts
+++ b/node/src/ts/visualizer/reducers/filter.ts
@@ -7,7 +7,7 @@ export interface FilterState {
}
const initialState: FilterState = {
- lowerLimitOfClassEntities: 0,
+ lowerLimitOfClassEntities: 1,
showingConditions: false,
}
diff --git a/node/src/ts/visualizer/reducers/property.ts b/node/src/ts/visualizer/reducers/property.ts
index 9307c077..01b95b9c 100644
--- a/node/src/ts/visualizer/reducers/property.ts
+++ b/node/src/ts/visualizer/reducers/property.ts
@@ -27,13 +27,15 @@ export default function property(
): PropertyState {
switch (action.type) {
case types.SHOW_PROPERTY: {
- const newState = { ...state }
- newState.openPropertyIndexes[action.payload.index] = true
+ const openPropertyIndexes = [...state.openPropertyIndexes]
+ openPropertyIndexes[action.payload.index] = true
+ const newState = { ...state, ...{ openPropertyIndexes } }
return newState
}
case types.CLOSE_PROPERTY: {
- const newState = { ...state }
- newState.openPropertyIndexes[action.payload.index] = false
+ const openPropertyIndexes = [...state.openPropertyIndexes]
+ openPropertyIndexes[action.payload.index] = false
+ const newState = { ...state, ...{ openPropertyIndexes } }
return newState
}
case types.SELECT_PROERTY_CLASS:
diff --git a/node/src/ts/visualizer/reducers/search.ts b/node/src/ts/visualizer/reducers/search.ts
index 9e4013c1..5f870d75 100644
--- a/node/src/ts/visualizer/reducers/search.ts
+++ b/node/src/ts/visualizer/reducers/search.ts
@@ -73,7 +73,7 @@ export default function search(
return _.orderBy(
candidates,
- ({ entities }) => entities || Infinity * -1,
+ ({ entities }) => entities ?? Infinity * -1,
['desc']
)
}
diff --git a/node/src/ts/visualizer/types/index.ts b/node/src/ts/visualizer/types/index.ts
new file mode 100644
index 00000000..4959827a
--- /dev/null
+++ b/node/src/ts/visualizer/types/index.ts
@@ -0,0 +1,32 @@
+/* eslint-disable camelcase */
+import { Classes } from './class'
+import { Metadata } from './metadata'
+import { Prefixes } from './prefix'
+import { Property } from './property'
+import { Structure } from './structure'
+
+declare global {
+ interface Document {
+ documentMode?: number
+ }
+
+ interface Navigator {
+ userLanguage?: string
+ browserLanguage?: string
+ }
+}
+
+export type AppState = {
+ structure: Structure[]
+ classes: Classes
+ properties: Property[]
+ prefixes: Prefixes
+}
+
+export type Content = {
+ inheritance_structure: Structure[]
+ classes: Classes
+ properties: Property[]
+ prefixes: Prefixes
+ meta_data: Metadata
+}
diff --git a/node/src/ts/visualizer/types/metadata.ts b/node/src/ts/visualizer/types/metadata.ts
new file mode 100644
index 00000000..1bf94508
--- /dev/null
+++ b/node/src/ts/visualizer/types/metadata.ts
@@ -0,0 +1,9 @@
+/* eslint-disable camelcase */
+
+export type Metadata = {
+ properties: number
+ triples: number
+ classes: number
+ endpoint: string
+ crawl_date: string
+}
diff --git a/node/src/ts/visualizer/utils/BlackList.ts b/node/src/ts/visualizer/utils/BlackList.ts
index 17e790df..68cc0ccd 100644
--- a/node/src/ts/visualizer/utils/BlackList.ts
+++ b/node/src/ts/visualizer/utils/BlackList.ts
@@ -15,16 +15,12 @@ class Blacklist {
}
has(uri: string, prefixes: Prefixes) {
- const [prefixKey, className] = uri.split(':')
- const prefix = prefixes[prefixKey]
+ const [shorthand, className] = uri.split(':')
+ const longhand = prefixes[shorthand]
+ const rawUri = longhand ? `${longhand}${className}` : uri
return (
- prefix !== undefined &&
- (('classes' in this.blacklist &&
- this.blacklist.classes.includes(`${prefix}${className}`)) ||
- ('prefixes' in this.blacklist &&
- this.blacklist.prefixes.some(
- (item) => prefix === item || prefix.startsWith(item)
- )))
+ this.blacklist.classes.includes(rawUri) ||
+ this.blacklist.prefixes.some((item) => rawUri.indexOf(item) > -1)
)
}
}
diff --git a/node/src/ts/visualizer/utils/GraphRepository.ts b/node/src/ts/visualizer/utils/GraphRepository.ts
index c410a0c8..5337f81f 100644
--- a/node/src/ts/visualizer/utils/GraphRepository.ts
+++ b/node/src/ts/visualizer/utils/GraphRepository.ts
@@ -21,6 +21,8 @@ import {
import { getPreferredLabel, isIE11 } from '.'
import SVGElementsAccessor from './SVGElementsAccessor'
+export type Point = { x: number; y: number }
+
export type NodeType = d3.HierarchyCircularNode
export type SVGGElementType = d3.Selection<
SVGGElement,
@@ -32,10 +34,8 @@ export type SVGEventHandlerType = (
e?: React.MouseEvent,
d?: NodeType
) => void
-type HTMLElementType = d3.Selection
type ScaleLinearType = d3.ScaleLinear
type ZoomType = d3.ZoomBehavior
-export type PopupDataType = { x1: number; x2: number; y1: number; y2: number }
const distance = (x1: number, y1: number, x2: number, y2: number) => {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
@@ -45,6 +45,60 @@ const calcMoveCenter = (diameter: number, scale: number) => {
return (diameter * scale) / 2 - diameter / 2
}
+// 2点と半径から円の中心座標を求める (2点ある)
+const calcCenterFromPoints = (
+ x1: number,
+ y1: number,
+ x2: number,
+ y2: number,
+ r: number
+) => {
+ const x3 = (x1 + x2) / 2
+ const y3 = (y1 + y2) / 2
+ const r2 = r ** 2
+ const l1 = (x2 - x3) ** 2 + (y2 - y3) ** 2
+
+ if (r2 < l1) {
+ throw Error('Cannot calculate center of circle.')
+ }
+
+ const d = Math.sqrt(r2 / l1 - 1.0)
+ const dx = d * (y2 - y3)
+ const dy = d * (x2 - x3)
+
+ const p1 = [x3 + dx, y3 - dy]
+ const p2 = [x3 - dx, y3 + dy]
+
+ return [p1, p2].sort((a, b) => b[0] - a[0] || b[1] - a[1])
+}
+
+// 円の中心座標と円周上の2点から中心角を求める
+const calcCenterAngleFromPoints = (
+ r: number,
+ x1: number,
+ y1: number,
+ x2: number,
+ y2: number
+) => {
+ const theta = 2 * Math.asin(distance(x1, y1, x2, y2) / (2 * r))
+ return theta
+}
+
+// 基点を中心に座標を反時計回りにθ度回転させる
+const rotateCoordinate = (
+ x: number,
+ y: number,
+ refX: number,
+ refY: number,
+ theta: number
+) => {
+ const adjustedX = x - refX
+ const adjustedY = y - refY
+ const roratedX = Math.cos(theta) * adjustedX + -Math.sin(theta) * adjustedY
+ const roratedY = Math.sin(theta) * adjustedX + Math.cos(theta) * adjustedY
+ return [roratedX + refX, roratedY + refY]
+}
+
const textBeforeEdgePolyfill = (
element: SVGTSpanElement,
cond: boolean | undefined
@@ -60,20 +114,10 @@ const textBeforeEdgePolyfill = (
}
class GraphRepository {
- // private instance field
private _nodes: NodeType[]
private _svg: SVGGElementType | null
- private elements: {
- shadow: HTMLElementType | null
- searching: HTMLElementType | null
- both: HTMLElementType | null
- arrowHead: HTMLElementType | null
- }
-
- // public instance field
-
classes: Classes
manuallyZoomed: boolean
@@ -98,6 +142,10 @@ class GraphRepository {
ignoreEvent: Boolean
+ transparentLabel: boolean
+
+ locale: string
+
pos: {
top: number
bottom: number
@@ -120,12 +168,6 @@ class GraphRepository {
constructor() {
this._nodes = []
this._svg = null
- this.elements = {
- shadow: null,
- searching: null,
- both: null,
- arrowHead: null,
- }
this.classes = {}
@@ -147,6 +189,8 @@ class GraphRepository {
this.zoom = undefined
this.timer = undefined
this.ignoreEvent = false
+ this.transparentLabel = false
+ this.locale = 'en'
}
// public accessor
@@ -176,57 +220,46 @@ class GraphRepository {
}
setShadow() {
- this.elements.shadow = d3.select('#shadow')
+ const shadow = this.svg?.select('#shadow')
- this.shadow
+ shadow
?.append('feGaussianBlur')
.attr('in', 'SourceAlpha')
.attr('result', 'blur')
.attr('stdDeviation', 5)
- this.shadow
- ?.append('feBlend')
- .attr('in', 'SourceGraphic')
- .attr('mode', 'normal')
- }
-
- get shadow() {
- return this.elements.shadow
+ shadow?.append('feBlend').attr('in', 'SourceGraphic').attr('mode', 'normal')
}
setSearching() {
- this.elements.searching = d3.select('#searching')
+ const searching = this.svg?.select('#searching')
- this.searching
+ searching
?.append('feGaussianBlur')
.attr('stdDeviation', 9.5)
.attr('in', 'SourceAlpha')
- this.searching
+ searching
?.append('feOffset')
.attr('dx', 0.5)
.attr('dy', 0.5)
.attr('result', 'offsetblur')
- this.searching
+ searching
?.append('feFlood')
.attr('flood-color', '#FF4F20')
.attr('flood-opacity', 0.76)
- this.searching
+ searching
?.append('feComposite')
.attr('in2', 'offsetblur')
.attr('operator', 'in')
- const merge = this.searching?.append('feMerge')
+ const merge = searching?.append('feMerge')
merge?.append('feMergeNode')
merge?.append('feMergeNode').attr('in', 'SourceGraphic')
}
- get searching() {
- return this.elements.searching
- }
-
setArrowHead() {
- this.elements.arrowHead = d3.select('#arrow-head')
+ const arrowHead = this.svg?.select('#arrow-head')
- this.arrowHead
+ arrowHead
?.attr('orient', 'auto-start-reverse')
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', '10')
@@ -236,10 +269,6 @@ class GraphRepository {
.attr('viewBox', '0 0 10 10')
}
- get arrowHead() {
- return this.elements.arrowHead
- }
-
// custom accessor
get topLevelNodes() {
return this._nodes.filter((d) => d.depth <= 2)
@@ -270,6 +299,21 @@ class GraphRepository {
}
}
+ get linesNodes() {
+ const nodes = this.svg?.select('g#lines-nodes')
+ return {
+ same: nodes?.selectAll('circle.same-line'),
+ rightHand: nodes?.selectAll(
+ 'circle.right-hand-line'
+ ),
+ leftHand: nodes?.selectAll(
+ 'circle.left-hand-line'
+ ),
+ both: nodes?.selectAll('circle.both-line'),
+ self: nodes?.selectAll('circle.self-line'),
+ }
+ }
+
get texts() {
const texts = this.svg?.select('g#texts')
return texts?.selectAll('text')
@@ -281,7 +325,7 @@ class GraphRepository {
}
get popups() {
- return this.svg?.selectAll('.popup')
+ return this.svg?.selectAll('.popup')
}
// accesor modoki
@@ -297,15 +341,15 @@ class GraphRepository {
}
x(x: number) {
- return (this.XLinear?.(x) || 0) + this.coordinate[0]
+ return (this.XLinear?.(x) ?? 0) + this.coordinate[0]
}
y(y: number) {
- return (this.YLinear?.(y) || 0) + this.coordinate[1]
+ return (this.YLinear?.(y) ?? 0) + this.coordinate[1]
}
textY(d: NodeType) {
- return this.y(d.y + (d.data.labelY || 0))
+ return this.y(d.y + (d.data.labelY ?? 0))
}
r(r: number) {
@@ -348,6 +392,7 @@ class GraphRepository {
this.manuallyZoomed = true
this.scale = event.transform.k
this.translate = [event.transform.x, event.transform.y]
+ this.transparentLabel = false
this.updateScale()
this.updatePosition()
@@ -418,7 +463,7 @@ class GraphRepository {
lhsNodes: NodeType[],
bothNodes: NodeType[]
) {
- const { paths } = this
+ const { paths, linesNodes } = this
const sameLines = paths.same?.data(sameNodes, nodeKeyFn)
sameLines?.enter().append('path').attr('class', 'same-line')
@@ -428,7 +473,7 @@ class GraphRepository {
rightLines
?.enter()
.append('path')
- .attr('class', 'arrow-line-base right-hand-line')
+ .attr('class', 'right-hand-line')
.attr('marker-end', 'url(#arrow-head)')
rightLines?.exit().remove()
@@ -436,7 +481,7 @@ class GraphRepository {
leftLines
?.enter()
.append('path')
- .attr('class', 'arrow-line-base left-hand-line')
+ .attr('class', 'left-hand-line')
.attr('marker-end', 'url(#arrow-head)')
leftLines?.exit().remove()
@@ -444,36 +489,74 @@ class GraphRepository {
bothLines
?.enter()
.append('path')
- .attr('class', 'arrow-line-base both-line')
+ .attr('class', 'both-line')
.attr('marker-start', 'url(#arrow-head)')
.attr('marker-end', 'url(#arrow-head)')
bothLines?.exit().remove()
+
+ const samePoints = linesNodes.same?.data(sameNodes, nodeKeyFn)
+ samePoints?.enter().append('circle').attr('class', 'same-line')
+ samePoints?.exit().remove()
+
+ const rightPoints = linesNodes.rightHand?.data(rhsNodes, nodeKeyFn)
+ rightPoints
+ ?.enter()
+ .append('circle')
+ .attr('class', 'arrow-line-base right-hand-line')
+ rightPoints?.exit().remove()
+
+ const leftPoints = linesNodes.leftHand?.data(lhsNodes, nodeKeyFn)
+ leftPoints
+ ?.enter()
+ .append('circle')
+ .attr('class', 'arrow-line-base left-hand-line')
+ leftPoints?.exit().remove()
+
+ const bothPoints = linesNodes.both?.data(bothNodes, nodeKeyFn)
+ bothPoints
+ ?.enter()
+ .append('circle')
+ .attr('class', 'arrow-line-base both-line')
+ bothPoints?.exit().remove()
}
- updateRightLines(nodes: NodeType[]) {
+ updateRightLines(
+ nodes: NodeType[],
+ handleRightClick: SVGEventHandlerType = () => {}
+ ) {
const rightLines = this.paths.rightHand?.data(nodes, nodeKeyFn)
rightLines
?.enter()
.append('path')
- .attr('class', 'arrow-line-base right-hand-line')
+ .attr('class', 'right-hand-line')
.attr('marker-end', 'url(#arrow-head)')
+ .on('contextmenu', handleRightClick)
rightLines?.exit().remove()
}
updateSelfLines(
nodes: NodeType[],
handleMouseOver: SVGEventHandlerType = () => {},
- handleMouseOut: SVGEventHandlerType = () => {}
+ handleMouseOut: SVGEventHandlerType = () => {},
+ handleRightClick: SVGEventHandlerType = () => {}
) {
const selfLines = this.paths.self?.data(nodes, nodeKeyFn)
selfLines
?.enter()
.append('path')
- .attr('class', 'arrow-line-base self-line')
+ .attr('class', 'self-line')
.attr('marker-end', 'url(#arrow-head)')
+ selfLines?.exit().remove()
+
+ const selfPoints = this.linesNodes.self?.data(nodes, nodeKeyFn)
+ selfPoints
+ ?.enter()
+ .append('circle')
+ .attr('class', 'arrow-line-base self-line')
.on('mouseover', handleMouseOver)
.on('mouseout', handleMouseOut)
- selfLines?.exit().remove()
+ .on('contextmenu', handleRightClick)
+ selfPoints?.exit().remove()
}
removeTreeImg() {
@@ -491,15 +574,8 @@ class GraphRepository {
.on('click', handleClick)
}
- addPopup(
- x1: number,
- y1: number,
- x2: number,
- y2: number,
- predicates: string[],
- message: string
- ) {
- const popup = this.popups?.data([{ x1, y1, x2, y2 }])
+ addPopup(x: number, y: number, predicates: string[], message: string) {
+ const popup = this.popups?.data([{ x, y }])
const div = popup
?.enter()
.append('foreignObject')
@@ -597,29 +673,36 @@ class GraphRepository {
// texts
// Firefoxはdisplay:noneな要素にgetBoundingClientRectできない
- const { scale } = this
+ const { scale, transparentLabel } = this
+
+ // ctxがd3.Transitionを返すときにvisibilityTexts.dataがundefinedになるので、アクセサを呼び分ける
+ const filterVisibilityTexts = (accessor: SVGElementsAccessor) =>
+ accessor.gtexts
+ ?.filter((d) => {
+ return (
+ this.targetKey === d.data.key ||
+ !d.children ||
+ !(d.children[0].data.key in this.visibleNodesSet) ||
+ d.r * scale <= SHOW_TEXT_MAX_CIRCLE_DIAMETER
+ )
+ })
+ .filter((d) => this.isShowNodeText(d))
- const visibilityTexts = ctx.gtexts
- .style('visibility', 'hidden')
- .style('opacity', 0)
- ?.filter((d) => {
- return (
- this.targetKey === d.data.key ||
- !d.children ||
- !(d.children[0].data.key in this.visibleNodesSet) ||
- d.r * scale <= SHOW_TEXT_MAX_CIRCLE_DIAMETER
- )
- })
- .filter((d) => this.isShowNodeText(d))
+ ctx.gtexts.style('visibility', 'hidden').style('opacity', 0)
- visibilityTexts
+ const visibilityTextsCtx = filterVisibilityTexts(ctx)
+ const labelOpacity = transparentLabel ? 0.6 : 1
+ const shouldBeOpaqueLabel = (d: NodeType) =>
+ d.data.key === this.targetKey || this.urisToHighlight.includes(d.data.uri)
+ visibilityTextsCtx
.attr('class', '')
.style('visibility', 'visible')
- .style('opacity', 1)
+ .style('opacity', (d) => (shouldBeOpaqueLabel(d) ? 1 : labelOpacity))
.attr('transform', (d) => `translate(${this.x(d.x)}, ${this.textY(d)})`)
.selectAll('tspan')
.attr('x', 0)
+ const visibilityTexts = filterVisibilityTexts(this.svgAccessor)
if (typeof visibilityTexts?.data === 'function') {
// Leafノードでない && クラス単位で最上位にあたるノードを強調表示
const data = visibilityTexts.data()
@@ -638,9 +721,14 @@ class GraphRepository {
(acc, d) => acc.concat([getUpperParent(d)]),
[]
)
- const upperParentUris: string[] = _.uniq(_.map(upperParents, 'data.uri'))
- visibilityTexts
- .filter((d) => !!d.children && upperParentUris.includes(d.data.uri))
+ const upperParentUris = new Set(upperParents.map((v) => v.data.uri))
+ visibilityTextsCtx
+ .filter(
+ (d) =>
+ this.targetKey !== d.data.key &&
+ !!d.children &&
+ upperParentUris.has(d.data.uri)
+ )
.attr('class', 'emphasized-class')
}
@@ -653,127 +741,228 @@ class GraphRepository {
const textRect = g[i].parentElement
?.getElementsByTagName('text')[0]
.getBoundingClientRect()
- return (textRect?.width || 0) / 2 + imageSize
+ return (textRect?.width ?? 0) / 2 + imageSize
})
.attr('y', (d) => (d.data.isLabelOnTop ? 0 : -imageSize / 2))
// popup
- this.popups
- ?.attr('x', ({ x1, x2 }) => (x1 + x2) / 2)
- .attr('y', ({ y1, y2 }) => (y1 + y2) / 2)
+ this.popups?.attr('x', ({ x }) => x).attr('y', ({ y }) => y)
const f = this.targetNode
if (!f) return
- // lines
- // const { paths } = this
+ const makeArrowLineToCenter = (from: NodeType, to: NodeType) => {
+ // 中心から中心を指す
+ const fromX = this.x(from.x)
+ const fromY = this.y(from.y)
+ const toX = this.x(to.x)
+ const toY = this.y(to.y)
+ return `M${fromX},${fromY} ${toX},${toY}`
+ }
+
+ const makeArrowLineToSide = (
+ from: NodeType,
+ to: NodeType,
+ calculatedDistance?: number
+ ) => {
+ const dist = calculatedDistance ?? distance(from.x, from.y, to.x, to.y)
+
+ // 辺から辺を指す
+ const cutFrom = (dist - from.r) / dist
+ const fromX = this.x((from.x - to.x) * cutFrom + to.x)
+ const fromY = this.y((from.y - to.y) * cutFrom + to.y)
+ const cutTo = (dist - to.r) / dist
+ const toX = this.x((to.x - from.x) * cutTo + from.x)
+ const toY = this.y((to.y - from.y) * cutTo + from.y)
+ return `M${fromX},${fromY} ${toX},${toY}`
+ }
+
const minSpaceBetweenCircles = (10 / scale) * 2
- ctx.paths.rightHand.attr('d', (d) => {
- const dist = distance(f.x, f.y, d.x, d.y)
+ const makeArrowLine = (from: NodeType, to: NodeType) => {
+ const dist = distance(from.x, from.y, to.x, to.y)
+
// 小数点精度の問題か何かでまれに誤って判定されることがあったので余白を設ける
- if (dist < f.r + d.r + minSpaceBetweenCircles) {
- // 中心から中心を指す
- return [
- `M ${this.x(f.x)},${this.y(f.y)}`,
- `${this.x(d.x)},${this.y(d.y)}`,
- ].join(' ')
+ const shouldPointToCenter = dist < from.r + to.r + minSpaceBetweenCircles
+ if (shouldPointToCenter) {
+ return makeArrowLineToCenter(from, to)
}
- // 辺から辺を指す
- const cut1 = (dist - f.r) / dist
- const cut2 = (dist - d.r) / dist
- return [
- `M ${this.x((f.x - d.x) * cut1 + d.x)},${this.y(
- (f.y - d.y) * cut1 + d.y
- )}`,
- `${this.x((d.x - f.x) * cut2 + f.x)},${this.y(
- (d.y - f.y) * cut2 + f.y
- )}`,
- ].join(' ')
- })
+ return makeArrowLineToSide(from, to, dist)
+ }
+
+ ctx.paths.rightHand.attr('d', (d) => {
+ return makeArrowLine(f, d)
+ })
ctx.paths.leftHand.attr('d', (d) => {
d.data.pointToCenter = true
- const dist = distance(f.x, f.y, d.x, d.y)
- if (dist < f.r + d.r + minSpaceBetweenCircles) {
- return [
- `M ${this.x(d.x)},${this.y(d.y)}`,
- `${this.x(f.x)},${this.y(f.y)}`,
- ].join(' ')
- }
- const cut1 = (dist - d.r) / dist
- const cut2 = (dist - f.r) / dist
- return [
- `M ${this.x((d.x - f.x) * cut1 + f.x)},${this.y(
- (d.y - f.y) * cut1 + f.y
- )}`,
- `${this.x((f.x - d.x) * cut2 + d.x)},${this.y(
- (f.y - d.y) * cut2 + d.y
- )}`,
- ].join(' ')
+ return makeArrowLine(d, f)
})
-
ctx.paths.both.attr('d', (d) => {
d.data.pointToCenter = true
- const dist = distance(f.x, f.y, d.x, d.y)
- if (dist < f.r + d.r + minSpaceBetweenCircles) {
- return [
- `M ${this.x(f.x)},${this.y(f.y)}`,
- `${this.x(d.x)},${this.y(d.y)}`,
- ].join(' ')
- }
- const cut1 = (dist - f.r) / dist
- const cut2 = (dist - d.r) / dist
- return [
- `M ${this.x((f.x - d.x) * cut1 + d.x)},${this.y(
- (f.y - d.y) * cut1 + d.y
- )}`,
- `${this.x((d.x - f.x) * cut2 + f.x)},${this.y(
- (d.y - f.y) * cut2 + f.y
- )}`,
- ].join(' ')
+ return makeArrowLine(f, d)
+ })
+ ctx.paths.same.attr('d', (d) => {
+ return makeArrowLineToSide(f, d)
})
- ctx.paths.self.attr('d', (d) => {
+ const makeArrowLineToSelf = (node: NodeType) => {
+ // 4時から11時の方向を指す
+ const nodeR = node.r * 1.02
const fourOclock = Math.PI / 6
+ const fromX = this.x(node.x + nodeR * Math.cos(fourOclock))
+ const fromY = this.y(node.y + nodeR * Math.sin(fourOclock))
const elevenOclock = (-Math.PI * 4) / 6
- const r = d.r * 1.02
- const r2 = this.r(f.r * 1.1)
- return [
- `M ${this.x(d.x + r * Math.cos(fourOclock))},${this.y(
- d.y + r * Math.sin(fourOclock)
- )}`,
- `A ${r2},${r2}`,
- '0',
- '1,0',
- `${this.x(d.x + r * Math.cos(elevenOclock))},${this.y(
- d.y + r * Math.sin(elevenOclock)
- )}`,
- ].join(' ')
- })
+ const toX = this.x(node.x + nodeR * Math.cos(elevenOclock))
+ const toY = this.y(node.y + nodeR * Math.sin(elevenOclock))
- ctx.paths.same.attr('d', (d) => {
- const dist = distance(f.x, f.y, d.x, d.y)
- const cut1 = (dist - f.r) / dist
- const cut2 = (dist - d.r) / dist
- return [
- `M ${this.x((f.x - d.x) * cut1 + d.x)},${this.y(
- (f.y - d.y) * cut1 + d.y
- )}`,
- `${this.x((d.x - f.x) * cut2 + f.x)},${this.y(
- (d.y - f.y) * cut2 + f.y
- )}`,
- ].join(' ')
+ // 節を描画するために矢印の中点を求める
+ const lineR = this.r(node.r * 1.1)
+ const lineC = calcCenterFromPoints(fromX, fromY, toX, toY, lineR)[0]
+ const angle = calcCenterAngleFromPoints(lineR, fromX, fromY, toX, toY)
+ const theta = angle / 2 + Math.PI
+ const lineMid = rotateCoordinate(fromX, fromY, lineC[0], lineC[1], theta)
+
+ const moveToStart = `M${fromX},${fromY}`
+ const drawUppperHalf = `A${lineR},${lineR} 0,0,0 ${lineMid[0]},${lineMid[1]}`
+ const drawBottomHalf = `A${lineR},${lineR} 0,0,0 ${toX},${toY}`
+
+ return `${moveToStart} ${drawUppperHalf} ${drawBottomHalf}`
+ }
+
+ ctx.paths.self.attr('d', (d) => {
+ return makeArrowLineToSelf(d)
})
+
+ // 矢印の中点
+ const getMidPointCenterToCenter = (
+ from: NodeType,
+ to: NodeType,
+ xy: 'x' | 'y'
+ ) => {
+ // 中心から中心を指す
+ const fromX = this.x(from.x)
+ const fromY = this.y(from.y)
+ const toX = this.x(to.x)
+ const toY = this.y(to.y)
+ if (xy === 'x') {
+ const midX = (fromX + toX) / 2
+ return midX
+ }
+ const midY = (fromY + toY) / 2
+ return midY
+ }
+
+ const getMidPontSideToSide = (
+ from: NodeType,
+ to: NodeType,
+ xy: 'x' | 'y',
+ calculatedDistance?: number
+ ) => {
+ const dist = calculatedDistance ?? distance(from.x, from.y, to.x, to.y)
+
+ // 辺から辺を指す
+ const cutFrom = (dist - from.r) / dist
+ const fromX = this.x((from.x - to.x) * cutFrom + to.x)
+ const fromY = this.y((from.y - to.y) * cutFrom + to.y)
+ const cutTo = (dist - to.r) / dist
+ const toX = this.x((to.x - from.x) * cutTo + from.x)
+ const toY = this.y((to.y - from.y) * cutTo + from.y)
+ if (xy === 'x') {
+ const midX = (fromX + toX) / 2
+ return midX
+ }
+ const midY = (fromY + toY) / 2
+ return midY
+ }
+
+ const getMidPoint = (from: NodeType, to: NodeType, xy: 'x' | 'y') => {
+ const dist = distance(from.x, from.y, to.x, to.y)
+
+ // 小数点精度の問題か何かでまれに誤って判定されることがあったので余白を設ける
+ const shouldPointToCenter = dist < from.r + to.r + minSpaceBetweenCircles
+ if (shouldPointToCenter) {
+ return getMidPointCenterToCenter(from, to, xy)
+ }
+
+ return getMidPontSideToSide(from, to, xy, dist)
+ }
+
+ ctx.linesNodes.rightHand
+ .attr('cx', (d) => {
+ return getMidPoint(f, d, 'x')
+ })
+ .attr('cy', (d) => {
+ return getMidPoint(f, d, 'y')
+ })
+ ctx.linesNodes.leftHand
+ .attr('cx', (d) => {
+ d.data.pointToCenter = true
+ return getMidPoint(f, d, 'x')
+ })
+ .attr('cy', (d) => {
+ d.data.pointToCenter = true
+ return getMidPoint(f, d, 'y')
+ })
+
+ ctx.linesNodes.both
+ .attr('cx', (d) => {
+ d.data.pointToCenter = true
+ return getMidPoint(f, d, 'x')
+ })
+ .attr('cy', (d) => {
+ d.data.pointToCenter = true
+ return getMidPoint(f, d, 'y')
+ })
+
+ ctx.linesNodes.same
+ .attr('cx', (d) => {
+ return getMidPontSideToSide(f, d, 'x')
+ })
+ .attr('cy', (d) => {
+ return getMidPontSideToSide(f, d, 'y')
+ })
+
+ const getMidPointToSelf = (node: NodeType, xy: 'x' | 'y') => {
+ // 4時から11時の方向を指す
+ const nodeR = node.r * 1.02
+ const fourOclock = Math.PI / 6
+ const fromX = this.x(node.x + nodeR * Math.cos(fourOclock))
+ const fromY = this.y(node.y + nodeR * Math.sin(fourOclock))
+ const elevenOclock = (-Math.PI * 4) / 6
+ const toX = this.x(node.x + nodeR * Math.cos(elevenOclock))
+ const toY = this.y(node.y + nodeR * Math.sin(elevenOclock))
+
+ // 節を描画するために矢印の中点を求める
+ const lineR = this.r(node.r * 1.1)
+ const lineC = calcCenterFromPoints(fromX, fromY, toX, toY, lineR)[0]
+ const angle = calcCenterAngleFromPoints(lineR, fromX, fromY, toX, toY)
+ const theta = angle / 2 + Math.PI
+ const lineMid = rotateCoordinate(fromX, fromY, lineC[0], lineC[1], theta)
+ if (xy === 'x') {
+ return lineMid[0]
+ }
+ return lineMid[1]
+ }
+
+ ctx.linesNodes.self
+ .attr('cx', (d) => {
+ return getMidPointToSelf(d, 'x')
+ })
+ .attr('cy', (d) => {
+ return getMidPointToSelf(d, 'y')
+ })
}
addArrowLineEvent(
handleMouseOver: SVGEventHandlerType,
- handleMouseOut: SVGEventHandlerType
+ handleMouseOut: SVGEventHandlerType,
+ handleRightClick: SVGEventHandlerType
) {
this.svg
?.selectAll('.arrow-line-base')
.on('mouseover', handleMouseOver)
.on('mouseout', handleMouseOut)
+ .on('contextmenu', handleRightClick)
}
avoidColidedLabel() {
@@ -807,7 +996,7 @@ class GraphRepository {
.map((child) => child.data.labelY)
)
- node.data.labelY = (maxYInChildren || 0) + scale * -60
+ node.data.labelY = (maxYInChildren ?? 0) + scale * -60
}
})
}
@@ -815,26 +1004,30 @@ class GraphRepository {
showNodes(
nodes: NodeType[],
handleClickClass: SVGEventHandlerType,
- locale: string
+ handleRightClickClass: SVGEventHandlerType
) {
- this.showCircleNodes(nodes, handleClickClass)
+ this.showCircleNodes(nodes, handleClickClass, handleRightClickClass)
this.updateScale()
- this.showTextNodes(nodes, handleClickClass, locale)
+ this.showTextNodes(nodes, handleClickClass)
this.updatePosition()
}
- showCircleNodes(nodes: NodeType[], handleClickClass: SVGEventHandlerType) {
+ showCircleNodes(
+ nodes: NodeType[],
+ handleClickClass: SVGEventHandlerType,
+ handleRightClickClass: SVGEventHandlerType
+ ) {
const circles = this.circles?.data(nodes, nodeKeyFn)
- circles?.enter().append('svg:circle').on('click', handleClickClass)
+ circles
+ ?.enter()
+ .append('svg:circle')
+ .on('click', handleClickClass)
+ .on('contextmenu', handleRightClickClass)
circles?.exit().remove()
}
- showTextNodes(
- nodes: NodeType[],
- handleClickClass: SVGEventHandlerType,
- locale: string
- ) {
- const { classes } = this
+ showTextNodes(nodes: NodeType[], handleClickClass: SVGEventHandlerType) {
+ const { classes, locale } = this
const gtexts = this.gtexts?.data(
_.sortBy(nodes, ({ depth }) => depth * -1),
@@ -999,12 +1192,7 @@ class GraphRepository {
handleHideTooltip: SVGEventHandlerType
) {
this.circles
- ?.on('mouseenter', null)
- .on('mousemove', null)
- .on('wheel', null)
- .on('mouseleave', null)
- .filter((d) => this.urisToHighlight.includes(d.data.uri))
- .on('mouseenter', handleShowTooltip)
+ ?.on('mouseenter', handleShowTooltip)
.on('mousemove', handleShowTooltip)
.on('wheel', handleShowTooltip)
.on('mouseleave', handleHideTooltip)
diff --git a/node/src/ts/visualizer/utils/SVGElementsAccessor.ts b/node/src/ts/visualizer/utils/SVGElementsAccessor.ts
index 16471aad..61cb36fb 100644
--- a/node/src/ts/visualizer/utils/SVGElementsAccessor.ts
+++ b/node/src/ts/visualizer/utils/SVGElementsAccessor.ts
@@ -1,4 +1,4 @@
-import { NodeType, SVGGElementType, PopupDataType } from './GraphRepository'
+import { NodeType, SVGGElementType, Point } from './GraphRepository'
export default class SVGElementsAccessor {
ctx: SVGGElementType
@@ -28,6 +28,21 @@ export default class SVGElementsAccessor {
}
}
+ get linesNodes() {
+ const nodes = this.ctx?.select('g#lines-nodes')
+ return {
+ same: nodes?.selectAll('circle.same-line'),
+ rightHand: nodes?.selectAll(
+ 'circle.right-hand-line'
+ ),
+ leftHand: nodes?.selectAll(
+ 'circle.left-hand-line'
+ ),
+ both: nodes?.selectAll('circle.both-line'),
+ self: nodes?.selectAll('circle.self-line'),
+ }
+ }
+
get texts() {
const texts = this.ctx.selectAll('g#texts > g')
return texts?.selectAll('text')
@@ -39,6 +54,6 @@ export default class SVGElementsAccessor {
}
get popups() {
- return this.ctx.selectAll('.popup')
+ return this.ctx.selectAll('.popup')
}
}
diff --git a/node/src/ts/visualizer/utils/TreeRepository.ts b/node/src/ts/visualizer/utils/TreeRepository.ts
index eeb27ef2..efbaec14 100644
--- a/node/src/ts/visualizer/utils/TreeRepository.ts
+++ b/node/src/ts/visualizer/utils/TreeRepository.ts
@@ -33,9 +33,9 @@ export const shouldShowDisplayButton = (
isLinealChildren(node, focusingNode))
export const getTranslateX = (d: NodeType) => d.depth * Margin.X
export const getTranslateY = (d: NodeType) =>
- (d.data?.treeY || 0) + 35 + 12 * 2 + 6 + 20 + 8
+ (d.data?.treeY ?? 0) + 35 + 12 * 2 + 6 + 20 + 8
export const getTranslateOldY = (d: NodeType) =>
- (d.data?.oldTreeY || 0) + 35 + 12 * 2 + 6 + 20 + 8
+ (d.data?.oldTreeY ?? 0) + 35 + 12 * 2 + 6 + 20 + 8
export const getFontSize = (e: SVGTextElement) =>
Number(e.getAttribute('font-size') || '1')
@@ -391,7 +391,7 @@ class TreeRepository {
)
.each((d, i, g) => {
const updateProgress = (progress: number) => {
- const delta = (d.data.treeY || 0) - (d.data.oldTreeY || 0)
+ const delta = (d.data.treeY ?? 0) - (d.data.oldTreeY ?? 0)
g[i].style.transform = `translate(${getTranslateX(d)}px, ${
getTranslateOldY(d) + progress * delta
}px)`
@@ -473,12 +473,12 @@ class TreeRepository {
datum.data.delta > 0
? (progress: number) => {
group[index].style.strokeDashoffset = String(
- (datum.data.delta || 0) * (1 - progress)
+ (datum.data.delta ?? 0) * (1 - progress)
)
}
: (progress: number) => {
group[index].style.strokeDashoffset = String(
- (datum.data.delta || 0) * -1 * progress
+ (datum.data.delta ?? 0) * -1 * progress
)
}
this.animationFuncMap.set(group[index], updateProgress)
@@ -501,7 +501,7 @@ class TreeRepository {
const lastChild = _.last(d.children)
if (lastChild?.data.oldTreeY !== undefined) {
d.data.delta =
- (lastChild.data.treeY || 0) - (lastChild.data.oldTreeY || 0)
+ (lastChild.data.treeY ?? 0) - (lastChild.data.oldTreeY ?? 0)
}
}
})
@@ -509,8 +509,8 @@ class TreeRepository {
const lastChild = _.last(d.children)
const y =
d.data.delta !== undefined && d.data.delta < 0
- ? (lastChild?.data.oldTreeY || 0) - (d.data.treeY || 0)
- : (lastChild?.data.treeY || 0) - (d.data.treeY || 0)
+ ? (lastChild?.data.oldTreeY ?? 0) - (d.data.treeY ?? 0)
+ : (lastChild?.data.treeY ?? 0) - (d.data.treeY ?? 0)
return `M 0 0 H ${Margin.X / 2} V ${y}`
})
.attr('stroke-dasharray', getDatumTotalLength)
@@ -583,7 +583,7 @@ class TreeRepository {
const shouldBridgeMultipleInheritance =
multipleInheritanceUris.length > 0 &&
(_.max(focusingNode.parent?.children?.map((d) => d.data.treeY)) || [0]) >
- (focusingNode.data.treeY || 0)
+ (focusingNode.data.treeY ?? 0)
if (multipleInheritanceUris.length > 0) {
const target = svgg?.filter(
@@ -655,7 +655,7 @@ class TreeRepository {
const linealAscendant = children?.find((d) => isLinealAscendant(d))
const childFocusingY = linealAscendant?.data.treeY
- const y = (childFocusingY || 0) - (treeY || 0)
+ const y = (childFocusingY ?? 0) - (treeY ?? 0)
focusingNodePositions.push({
depth,
shouldPolygonal: !!children && children.length > 1 && y > 0,
@@ -819,7 +819,7 @@ class TreeRepository {
) {
const elapsed = window.performance.now() - start
if (elapsed > ANIMATION_DURATION) {
- this.finishAnimation(selection, this.animationKeyMap.get(selection) || 0)
+ this.finishAnimation(selection, this.animationKeyMap.get(selection) ?? 0)
this.animationKeyMap.delete(selection)
completion()
} else {
diff --git a/node/src/ts/visualizer/utils/index.ts b/node/src/ts/visualizer/utils/index.ts
index 5eb4abc6..8fbae421 100644
--- a/node/src/ts/visualizer/utils/index.ts
+++ b/node/src/ts/visualizer/utils/index.ts
@@ -2,18 +2,36 @@ import { useLocation } from 'react-router-dom'
import locales from '../locales'
import { Classes } from '../types/class'
+export const omitUri = (uri: string) => {
+ // Do not allow endwith '#' or '/' because fragment or path will be empty.
+ const uriWithoutEndDelim = uri.replace(/[#,/]$/, '')
+
+ if (uriWithoutEndDelim.lastIndexOf('#') > -1) {
+ const fragment = uri.split('#').slice(-1)[0]
+ return fragment
+ }
+
+ const uriWithoutScheme = uriWithoutEndDelim.split('://').slice(-1)[0]
+ if (uriWithoutScheme.lastIndexOf('/') > -1) {
+ const path = uriWithoutScheme.split('/').slice(-1)[0]
+ return path
+ }
+
+ return uri
+}
+
export const getPreferredLabel = (
uri: string,
classes: Classes,
locale: string
): string => {
- const detail = classes[uri]
- const labels = detail?.label
+ const labels = classes[uri]?.label
if (!labels) {
- return uri
+ return omitUri(uri)
}
- const label = labels[locale] || labels.en || labels[''] || null
- return label || uri
+
+ const label = labels[locale] ?? labels.en ?? labels[''] ?? undefined
+ return label ?? omitUri(uri)
}
export const getLocaleShortString = (): string => {
diff --git a/node/src/ts/visualizer/utils/node.ts b/node/src/ts/visualizer/utils/node.ts
index e8826698..6329af98 100644
--- a/node/src/ts/visualizer/utils/node.ts
+++ b/node/src/ts/visualizer/utils/node.ts
@@ -2,6 +2,13 @@ import _ from 'lodash'
import { Structure, NodeStructure } from '../types/structure'
import { NodeType } from './GraphRepository'
+export const flattenStructure = (elem: Structure): Structure[] => {
+ if (elem.children === undefined) {
+ return [elem]
+ }
+ return _.flatMap(elem.children, flattenStructure).concat([elem])
+}
+
const falsyStructure: NodeStructure = {
key: 0,
originalR: 0,
diff --git a/node/src/ts/visualizer/utils/sparql.ts b/node/src/ts/visualizer/utils/sparql.ts
new file mode 100644
index 00000000..e39dc12b
--- /dev/null
+++ b/node/src/ts/visualizer/utils/sparql.ts
@@ -0,0 +1,40 @@
+export const makeQueryWhenRightClickClass = (_class: string) => {
+ const query = `
+ SELECT ?i
+ WHERE {
+ ?i a <${_class}> .
+ }
+ LIMIT 20
+ `.replace(/^\n|\s+$|^ {4}/gm, '')
+ return query
+}
+
+export const makeQueryWhenRightClickArrow = (
+ domain: string,
+ properties: string[],
+ range: string
+) => {
+ const query = `
+ SELECT ?sbj ?obj
+ WHERE {
+ ?sbj ${properties.map((v) => `<${v}>`).join('|')} ?obj .
+ ?sbj a <${domain}> .
+ ?obj a <${range}> .
+ }
+ LIMIT 20
+ `.replace(/^\n|\s+$|^ {4}/gm, '')
+
+ return query
+}
+
+export const navigateToYasgui = (endpoint: string, query: string) => {
+ const params = new URLSearchParams()
+ params.append('endpoint', endpoint)
+ params.append('query', query)
+ const win = window.open(
+ `/yasgui?${params.toString()}`,
+ '_blank',
+ 'noreferrer'
+ )
+ win?.focus()
+}
diff --git a/node/webpack.config.js b/node/webpack.config.js
index 575e51d5..8670032c 100644
--- a/node/webpack.config.js
+++ b/node/webpack.config.js
@@ -10,11 +10,9 @@ const INDEX_FILE = 'html/index.html'
module.exports = (env, args) => {
- let API_ENDPOINT
let FIREBASE_CONFIG
switch (args.mode) {
case 'development':
- API_ENDPOINT = 'http://localhost/api/v1'
FIREBASE_CONFIG = {
apiKey: "AIzaSyB3GMmRd9JWGVvDlEtgpemtYZPo-WRkNpc",
authDomain: "fabled-alchemy-246207.firebaseapp.com",
@@ -26,7 +24,6 @@ module.exports = (env, args) => {
}
break
case 'production':
- API_ENDPOINT = 'https://umaka-viewer.dbcls.jp/api/v1'
FIREBASE_CONFIG = {
apiKey: "AIzaSyBNTb8DHaHbx32oifMkM_zKTGL4oI1QUNY",
authDomain: "dbcls-ead06.firebaseapp.com",
@@ -38,7 +35,6 @@ module.exports = (env, args) => {
}
break
default:
- API_ENDPOINT = 'http://localhost/api/v1'
FIREBASE_CONFIG = {
apiKey: "",
authDomain: "",
@@ -124,6 +120,7 @@ module.exports = (env, args) => {
new CopyPlugin([
{ from: 'src/images', to: 'static/images' },
{ from: 'src/blacklists', to: 'static/blacklists' },
+ { from: 'src/html/yasgui.html', to: 'yasgui.html'}
]),
new MiniCssExtractPlugin({
filename: 'static/css/[name].css',
@@ -131,8 +128,8 @@ module.exports = (env, args) => {
ignoreOrder: false,
}),
new webpack.DefinePlugin({
- API_ENDPOINT: JSON.stringify(API_ENDPOINT),
FIREBASE_CONFIG: JSON.stringify(FIREBASE_CONFIG),
+ process: JSON.stringify({env: 'process/browser'}) ,
}),
],
}
diff --git a/node/yarn.lock b/node/yarn.lock
index 67c1687d..0bc1a835 100644
--- a/node/yarn.lock
+++ b/node/yarn.lock
@@ -3275,7 +3275,7 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
-clsx@1.1.1:
+clsx@1.1.1, clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@@ -9343,6 +9343,13 @@ react-contenteditable@3.3.5:
fast-deep-equal "^2.0.1"
prop-types "^15.7.1"
+react-contexify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/react-contexify/-/react-contexify-5.0.0.tgz#11b477550a0ee5a9a144399bc17c7c56bbc60057"
+ integrity sha512-2FIp7lxJ6dtfGr8EZ4uVV5p5TQjd0n2h/JU7PrejNIMiCeZWvSVPFh4lj1ZvjXosglBvP7q5JQQ8yUCdSaMSaw==
+ dependencies:
+ clsx "^1.1.1"
+
react-dom@16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
@@ -9435,6 +9442,14 @@ react-toastify@5.5.0:
prop-types "^15.7.2"
react-transition-group "^4"
+react-tooltip@^4.2.21:
+ version "4.2.21"
+ resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
+ integrity sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==
+ dependencies:
+ prop-types "^15.7.2"
+ uuid "^7.0.3"
+
react-transition-group@^4:
version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
@@ -11159,6 +11174,11 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
+ integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
+
v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
diff --git a/server/dbcls/api/app.py b/server/dbcls/api/app.py
index 3717221b..cab6641e 100644
--- a/server/dbcls/api/app.py
+++ b/server/dbcls/api/app.py
@@ -7,6 +7,7 @@
from google.auth.exceptions import TransportError
from dbcls import app
+from dbcls.api.resources.proxy import Proxy
from dbcls.models import User, UserRole, UserRoleTypes
from dbcls.api.resources.signup import SignUp
from dbcls.api.resources.authenticate import Authenticate
@@ -55,6 +56,7 @@ def verify_authentication():
api_v1.add_resource(SignUp, '/signup', endpoint='signup')
api_v1.add_resource(Authenticate, '/auth', endpoint='auth')
api_v1.add_resource(DataSetList, '/data_sets')
+api_v1.add_resource(Proxy, '/proxy', endpoint='proxy')
api_v1.add_resource(DataSetDetail, '/data_sets/')
api_v1.add_resource(DataSetGenerator, '/data_sets/generate')
api_v1.add_resource(DataSetGenerateProcessStatus, '/data_sets/generate/')
@@ -66,7 +68,7 @@ def verify_authentication():
NOT_NEED_AUTHORIZATION_ENDPOINTS = [
f'{api_v1_bp.name}.{endpoint}'
- for endpoint in ('healthy', 'signup', 'auth', 'public_data_sets', 'visualize')
+ for endpoint in ('healthy', 'signup', 'auth', 'public_data_sets', 'visualize', 'proxy')
]
diff --git a/server/dbcls/api/resources/proxy.py b/server/dbcls/api/resources/proxy.py
new file mode 100644
index 00000000..9d536212
--- /dev/null
+++ b/server/dbcls/api/resources/proxy.py
@@ -0,0 +1,15 @@
+from flask_restful import Resource, reqparse, request
+from SPARQLWrapper import SPARQLWrapper, JSON
+
+
+
+class Proxy(Resource):
+ def post(self):
+ form = request.form
+ endpoint = form['endpoint']
+ query = form['query']
+ s = SPARQLWrapper(endpoint)
+ s.setQuery(query)
+ s.setReturnFormat(JSON)
+ res = s.query().convert()
+ return res
diff --git a/server/poetry.lock b/server/poetry.lock
index 33b4fbd6..2ec1909e 100644
--- a/server/poetry.lock
+++ b/server/poetry.lock
@@ -1,55 +1,54 @@
[[package]]
-category = "main"
-description = "A database migration tool for SQLAlchemy."
name = "alembic"
+version = "1.4.2"
+description = "A database migration tool for SQLAlchemy."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.4.2"
[package.dependencies]
Mako = "*"
-SQLAlchemy = ">=1.1.0"
python-dateutil = "*"
python-editor = ">=0.3"
+SQLAlchemy = ">=1.1.0"
[[package]]
-category = "main"
-description = "A library for parsing ISO 8601 strings."
name = "aniso8601"
+version = "8.0.0"
+description = "A library for parsing ISO 8601 strings."
+category = "main"
optional = false
python-versions = "*"
-version = "8.0.0"
[[package]]
-category = "dev"
-description = "Atomic file writes."
-marker = "sys_platform == \"win32\""
name = "atomicwrites"
+version = "1.4.0"
+description = "Atomic file writes."
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.4.0"
[[package]]
-category = "dev"
-description = "Classes Without Boilerplate"
name = "attrs"
+version = "20.2.0"
+description = "Classes Without Boilerplate"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.2.0"
[package.extras]
-dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
+dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"]
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
-tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
-tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
+tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
+tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"]
[[package]]
-category = "main"
-description = "httplib2 caching for requests"
name = "cachecontrol"
+version = "0.12.6"
+description = "httplib2 caching for requests"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.12.6"
[package.dependencies]
msgpack = ">=0.5.2"
@@ -60,90 +59,88 @@ filecache = ["lockfile (>=0.9)"]
redis = ["redis (>=2.10.5)"]
[[package]]
-category = "main"
-description = "Extensible memoizing collections and decorators"
name = "cachetools"
+version = "4.1.1"
+description = "Extensible memoizing collections and decorators"
+category = "main"
optional = false
python-versions = "~=3.5"
-version = "4.1.1"
[[package]]
-category = "main"
-description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
+version = "2020.6.20"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
optional = false
python-versions = "*"
-version = "2020.6.20"
[[package]]
-category = "main"
-description = "Foreign Function Interface for Python calling C code."
-marker = "python_version >= \"3.5\""
name = "cffi"
+version = "1.14.2"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
optional = false
python-versions = "*"
-version = "1.14.2"
[package.dependencies]
pycparser = "*"
[[package]]
+name = "charset-normalizer"
+version = "2.0.12"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
-description = "Universal encoding detector for Python 2 and 3"
-name = "chardet"
optional = false
-python-versions = "*"
-version = "3.0.4"
+python-versions = ">=3.5.0"
+
+[package.extras]
+unicode_backport = ["unicodedata2"]
[[package]]
-category = "main"
-description = "Composable command line interface toolkit"
name = "click"
+version = "7.1.2"
+description = "Composable command line interface toolkit"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "7.1.2"
[[package]]
-category = "dev"
-description = "Cross-platform colored terminal text."
-marker = "sys_platform == \"win32\""
name = "colorama"
+version = "0.4.3"
+description = "Cross-platform colored terminal text."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.4.3"
[[package]]
-category = "main"
-description = "Firebase Admin Python SDK"
name = "firebase-admin"
+version = "2.18.0"
+description = "Firebase Admin Python SDK"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.18.0"
[package.dependencies]
cachecontrol = ">=0.12.4"
+google-api-core = {version = ">=1.7.0,<2.0.0dev", extras = ["grpc"], markers = "platform_python_implementation != \"PyPy\""}
google-api-python-client = ">=1.7.8"
-google-cloud-firestore = ">=0.31.0"
+google-cloud-firestore = {version = ">=0.31.0", markers = "platform_python_implementation != \"PyPy\""}
google-cloud-storage = ">=1.13.0"
six = ">=1.6.1"
-[package.dependencies.google-api-core]
-extras = ["grpc"]
-version = ">=1.7.0,<2.0.0dev"
-
[[package]]
-category = "main"
-description = "A simple framework for building complex web applications."
name = "flask"
+version = "1.1.2"
+description = "A simple framework for building complex web applications."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "1.1.2"
[package.dependencies]
-Jinja2 = ">=2.10.1"
-Werkzeug = ">=0.15"
click = ">=5.1"
itsdangerous = ">=0.24"
+Jinja2 = ">=2.10.1"
+Werkzeug = ">=0.15"
[package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
@@ -151,28 +148,28 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-
dotenv = ["python-dotenv"]
[[package]]
-category = "main"
-description = "A Flask extension adding a decorator for CORS support"
name = "flask-cors"
+version = "3.0.9"
+description = "A Flask extension adding a decorator for CORS support"
+category = "main"
optional = false
python-versions = "*"
-version = "3.0.9"
[package.dependencies]
Flask = ">=0.9"
Six = "*"
[[package]]
-category = "main"
-description = "Simple framework for creating REST APIs"
name = "flask-restful"
+version = "0.3.8"
+description = "Simple framework for creating REST APIs"
+category = "main"
optional = false
python-versions = "*"
-version = "0.3.8"
[package.dependencies]
-Flask = ">=0.8"
aniso8601 = ">=0.82"
+Flask = ">=0.8"
pytz = "*"
six = ">=1.3.0"
@@ -180,43 +177,43 @@ six = ">=1.3.0"
docs = ["sphinx"]
[[package]]
-category = "main"
-description = "Scripting support for Flask"
name = "flask-script"
+version = "2.0.6"
+description = "Scripting support for Flask"
+category = "main"
optional = false
python-versions = "*"
-version = "2.0.6"
[package.dependencies]
Flask = "*"
[[package]]
-category = "main"
-description = "Adds SQLAlchemy support to your Flask application."
name = "flask-sqlalchemy"
+version = "2.4.4"
+description = "Adds SQLAlchemy support to your Flask application."
+category = "main"
optional = false
python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*"
-version = "2.4.4"
[package.dependencies]
Flask = ">=0.10"
SQLAlchemy = ">=0.8.0"
[[package]]
-category = "main"
-description = "Google API client core library"
name = "google-api-core"
+version = "1.22.2"
+description = "Google API client core library"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.22.2"
[package.dependencies]
google-auth = ">=1.21.1,<2.0dev"
googleapis-common-protos = ">=1.6.0,<2.0dev"
+grpcio = {version = ">=1.29.0,<2.0dev", optional = true, markers = "extra == \"grpc\""}
protobuf = ">=3.12.0"
pytz = "*"
requests = ">=2.18.0,<3.0.0dev"
-setuptools = ">=34.0.0"
six = ">=1.10.0"
[package.extras]
@@ -225,12 +222,12 @@ grpcgcp = ["grpcio-gcp (>=0.2.2)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2)"]
[[package]]
-category = "main"
-description = "Google API Client Library for Python"
name = "google-api-python-client"
+version = "1.11.0"
+description = "Google API Client Library for Python"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.11.0"
[package.dependencies]
google-api-core = ">=1.18.0,<2dev"
@@ -241,30 +238,26 @@ six = ">=1.6.1,<2dev"
uritemplate = ">=3.0.0,<4dev"
[[package]]
-category = "main"
-description = "Google Authentication Library"
name = "google-auth"
+version = "1.21.1"
+description = "Google Authentication Library"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.21.1"
[package.dependencies]
cachetools = ">=2.0.0,<5.0"
pyasn1-modules = ">=0.2.1"
-setuptools = ">=40.3.0"
+rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.5\""}
six = ">=1.9.0"
-[package.dependencies.rsa]
-python = ">=3.5"
-version = ">=3.1.4,<5"
-
[[package]]
-category = "main"
-description = "Google Authentication Library: httplib2 transport"
name = "google-auth-httplib2"
+version = "0.0.4"
+description = "Google Authentication Library: httplib2 transport"
+category = "main"
optional = false
python-versions = "*"
-version = "0.0.4"
[package.dependencies]
google-auth = "*"
@@ -272,12 +265,12 @@ httplib2 = ">=0.9.1"
six = "*"
[[package]]
-category = "main"
-description = "Google Cloud API client core library"
name = "google-cloud-core"
+version = "1.4.1"
+description = "Google Cloud API client core library"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.4.1"
[package.dependencies]
google-api-core = ">=1.19.0,<2.0.0dev"
@@ -286,29 +279,25 @@ google-api-core = ">=1.19.0,<2.0.0dev"
grpc = ["grpcio (>=1.8.2,<2.0dev)"]
[[package]]
-category = "main"
-description = "Google Cloud Firestore API client library"
-marker = "platform_python_implementation != \"PyPy\""
name = "google-cloud-firestore"
+version = "1.9.0"
+description = "Google Cloud Firestore API client library"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.9.0"
[package.dependencies]
+google-api-core = {version = ">=1.14.0,<2.0.0dev", extras = ["grpc"]}
google-cloud-core = ">=1.4.1,<2.0dev"
pytz = "*"
-[package.dependencies.google-api-core]
-extras = ["grpc"]
-version = ">=1.14.0,<2.0.0dev"
-
[[package]]
-category = "main"
-description = "Google Cloud Storage API client library"
name = "google-cloud-storage"
+version = "1.31.0"
+description = "Google Cloud Storage API client library"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.31.0"
[package.dependencies]
google-auth = ">=1.11.0,<2.0dev"
@@ -316,13 +305,12 @@ google-cloud-core = ">=1.4.1,<2.0dev"
google-resumable-media = ">=1.0.0,<2.0dev"
[[package]]
-category = "main"
-description = "A python wrapper of the C library 'Google CRC32C'"
-marker = "python_version >= \"3.5\""
name = "google-crc32c"
+version = "1.0.0"
+description = "A python wrapper of the C library 'Google CRC32C'"
+category = "main"
optional = false
python-versions = ">=3.5"
-version = "1.0.0"
[package.dependencies]
cffi = ">=1.0.0"
@@ -331,30 +319,27 @@ cffi = ">=1.0.0"
testing = ["pytest"]
[[package]]
-category = "main"
-description = "Utilities for Google Media Downloads and Resumable Uploads"
name = "google-resumable-media"
+version = "1.0.0"
+description = "Utilities for Google Media Downloads and Resumable Uploads"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
-version = "1.0.0"
[package.dependencies]
+google-crc32c = {version = ">=1.0,<2.0dev", markers = "python_version >= \"3.5\""}
six = "*"
-[package.dependencies.google-crc32c]
-python = ">=3.5"
-version = ">=1.0,<2.0dev"
-
[package.extras]
requests = ["requests (>=2.18.0,<3.0.0dev)"]
[[package]]
-category = "main"
-description = "Common protobufs used in Google APIs"
name = "googleapis-common-protos"
+version = "1.52.0"
+description = "Common protobufs used in Google APIs"
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.52.0"
[package.dependencies]
protobuf = ">=3.6.0"
@@ -363,29 +348,42 @@ protobuf = ">=3.6.0"
grpc = ["grpcio (>=1.0.0)"]
[[package]]
+name = "grpcio"
+version = "1.43.0"
+description = "HTTP/2-based RPC framework"
category = "main"
-description = "A comprehensive HTTP client library."
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+six = ">=1.5.2"
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.43.0)"]
+
+[[package]]
name = "httplib2"
+version = "0.18.1"
+description = "A comprehensive HTTP client library."
+category = "main"
optional = false
python-versions = "*"
-version = "0.18.1"
[[package]]
-category = "main"
-description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
+version = "2.10"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.10"
[[package]]
-category = "dev"
-description = "Read metadata from Python packages"
-marker = "python_version < \"3.8\""
name = "importlib-metadata"
+version = "1.7.0"
+description = "Read metadata from Python packages"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-version = "1.7.0"
[package.dependencies]
zipp = ">=0.5"
@@ -395,31 +393,31 @@ docs = ["sphinx", "rst.linker"]
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]]
-category = "main"
-description = "An ISO 8601 date/time/duration parser and formatter"
name = "isodate"
+version = "0.6.1"
+description = "An ISO 8601 date/time/duration parser and formatter"
+category = "main"
optional = false
python-versions = "*"
-version = "0.6.0"
[package.dependencies]
six = "*"
[[package]]
-category = "main"
-description = "Various helpers to pass data to untrusted environments and back."
name = "itsdangerous"
+version = "1.1.0"
+description = "Various helpers to pass data to untrusted environments and back."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.1.0"
[[package]]
-category = "main"
-description = "A very fast and expressive template engine."
name = "jinja2"
+version = "2.11.2"
+description = "A very fast and expressive template engine."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.11.2"
[package.dependencies]
MarkupSafe = ">=0.23"
@@ -428,12 +426,12 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"]
[[package]]
-category = "main"
-description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
name = "mako"
+version = "1.1.3"
+description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.1.3"
[package.dependencies]
MarkupSafe = ">=0.9.2"
@@ -443,158 +441,151 @@ babel = ["babel"]
lingua = ["lingua"]
[[package]]
-category = "main"
-description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe"
+version = "1.1.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
-version = "1.1.1"
[[package]]
-category = "dev"
-description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
+version = "8.5.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "8.5.0"
[[package]]
-category = "main"
-description = "MessagePack (de)serializer."
name = "msgpack"
+version = "1.0.0"
+description = "MessagePack (de)serializer."
+category = "main"
optional = false
python-versions = "*"
-version = "1.0.0"
[[package]]
-category = "dev"
-description = "Core utilities for Python packages"
name = "packaging"
+version = "20.4"
+description = "Core utilities for Python packages"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "20.4"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
-category = "dev"
-description = "plugin and hook calling mechanisms for python"
name = "pluggy"
+version = "0.13.1"
+description = "plugin and hook calling mechanisms for python"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "0.13.1"
[package.dependencies]
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
-category = "main"
-description = "Protocol Buffers"
name = "protobuf"
+version = "3.13.0"
+description = "Protocol Buffers"
+category = "main"
optional = false
python-versions = "*"
-version = "3.13.0"
[package.dependencies]
-setuptools = "*"
six = ">=1.9"
[[package]]
-category = "dev"
-description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
+version = "1.9.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.9.0"
[[package]]
-category = "main"
-description = "ASN.1 types and codecs"
name = "pyasn1"
+version = "0.4.8"
+description = "ASN.1 types and codecs"
+category = "main"
optional = false
python-versions = "*"
-version = "0.4.8"
[[package]]
-category = "main"
-description = "A collection of ASN.1-based protocols modules."
name = "pyasn1-modules"
+version = "0.2.8"
+description = "A collection of ASN.1-based protocols modules."
+category = "main"
optional = false
python-versions = "*"
-version = "0.2.8"
[package.dependencies]
pyasn1 = ">=0.4.6,<0.5.0"
[[package]]
-category = "main"
-description = "C parser in Python"
-marker = "python_version >= \"3.5\""
name = "pycparser"
+version = "2.20"
+description = "C parser in Python"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "2.20"
[[package]]
-category = "main"
-description = "Pure Python MySQL Driver"
name = "pymysql"
+version = "0.10.1"
+description = "Pure Python MySQL Driver"
+category = "main"
optional = false
python-versions = "*"
-version = "0.10.1"
[package.extras]
ed25519 = ["PyNaCl (>=1.4.0)"]
rsa = ["cryptography"]
[[package]]
-category = "main"
-description = "Python parsing module"
name = "pyparsing"
+version = "2.4.7"
+description = "Python parsing module"
+category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "2.4.7"
[[package]]
-category = "dev"
-description = "pytest: simple powerful testing with Python"
name = "pytest"
+version = "5.4.3"
+description = "pytest: simple powerful testing with Python"
+category = "dev"
optional = false
python-versions = ">=3.5"
-version = "5.4.3"
[package.dependencies]
-atomicwrites = ">=1.0"
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=17.4.0"
-colorama = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
-[package.dependencies.importlib-metadata]
-python = "<3.8"
-version = ">=0.12"
-
[package.extras]
-checkqa-mypy = ["mypy (v0.761)"]
+checkqa-mypy = ["mypy (==v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
-category = "dev"
-description = "Thin-wrapper around the mock package for easier use with py.test"
name = "pytest-mock"
+version = "1.13.0"
+description = "Thin-wrapper around the mock package for easier use with py.test"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.13.0"
[package.dependencies]
pytest = ">=2.7"
@@ -603,118 +594,130 @@ pytest = ">=2.7"
dev = ["pre-commit", "tox"]
[[package]]
-category = "main"
-description = "Extensions to the standard Python datetime module"
name = "python-dateutil"
+version = "2.8.1"
+description = "Extensions to the standard Python datetime module"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-version = "2.8.1"
[package.dependencies]
six = ">=1.5"
[[package]]
-category = "main"
-description = "Programmatically open an editor, capture the result."
name = "python-editor"
+version = "1.0.4"
+description = "Programmatically open an editor, capture the result."
+category = "main"
optional = false
python-versions = "*"
-version = "1.0.4"
[[package]]
-category = "main"
-description = "Translation library for Python"
name = "python-i18n"
+version = "0.3.9"
+description = "Translation library for Python"
+category = "main"
optional = false
python-versions = "*"
-version = "0.3.9"
[package.extras]
yaml = ["pyyaml (>=3.10)"]
[[package]]
-category = "main"
-description = "World timezone definitions, modern and historical"
name = "pytz"
+version = "2019.3"
+description = "World timezone definitions, modern and historical"
+category = "main"
optional = false
python-versions = "*"
-version = "2019.3"
[[package]]
-category = "main"
-description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
name = "rdflib"
+version = "6.1.1"
+description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
+category = "main"
optional = false
-python-versions = "*"
-version = "5.0.0"
+python-versions = ">=3.7"
[package.dependencies]
+importlib-metadata = {version = "*", markers = "python_version < \"3.8.0\""}
isodate = "*"
pyparsing = "*"
-six = "*"
[package.extras]
-docs = ["sphinx (<3)", "sphinxcontrib-apidoc"]
+docs = ["sphinx (<5)", "sphinxcontrib-apidoc"]
html = ["html5lib"]
-sparql = ["requests"]
-tests = ["html5lib", "networkx", "nose", "doctest-ignore-unicode"]
+tests = ["berkeleydb", "html5lib", "networkx", "pytest", "pytest-cov", "pytest-subtests"]
[[package]]
-category = "main"
-description = "Python client for Redis key-value store"
name = "redis"
+version = "3.5.3"
+description = "Python client for Redis key-value store"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "3.5.3"
[package.extras]
hiredis = ["hiredis (>=0.1.3)"]
[[package]]
-category = "main"
-description = "Python HTTP for Humans."
name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "2.24.0"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<4"
-idna = ">=2.5,<3"
-urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
[package.extras]
-security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
-category = "main"
-description = "Pure-Python RSA implementation"
-marker = "platform_python_implementation != \"PyPy\" and python_version >= \"3.5\" or python_version >= \"3.5\""
name = "rsa"
+version = "4.6"
+description = "Pure-Python RSA implementation"
+category = "main"
optional = false
python-versions = ">=3.5, <4"
-version = "4.6"
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]]
-category = "main"
-description = "Python 2 and 3 compatibility utilities"
name = "six"
+version = "1.15.0"
+description = "Python 2 and 3 compatibility utilities"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-version = "1.15.0"
[[package]]
+name = "sparqlwrapper"
+version = "1.8.5"
+description = "SPARQL Endpoint interface to Python"
category = "main"
-description = "Database Abstraction Library"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+rdflib = ">=4.0"
+
+[package.extras]
+keepalive = ["keepalive (>=0.5)"]
+
+[[package]]
name = "sqlalchemy"
+version = "1.3.19"
+description = "Database Abstraction Library"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "1.3.19"
[package.extras]
mssql = ["pyodbc"]
@@ -729,91 +732,94 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql"]
[[package]]
-category = "main"
-description = "Fast, Extensible Progress Meter"
name = "tqdm"
+version = "4.62.3"
+description = "Fast, Extensible Progress Meter"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
-version = "4.56.0"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "wheel"]
+notebook = ["ipywidgets (>=6)"]
telegram = ["requests"]
[[package]]
-category = "main"
-description = ""
name = "umakaparser"
+version = "0.1.7"
+description = ""
+category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "0.1.1"
+python-versions = ">=3.7"
[package.dependencies]
click = ">=7.0,<8.0"
isodate = ">=0.6.0,<0.7.0"
pyparsing = ">=2.4,<3.0"
python-i18n = ">=0.3.9,<0.4.0"
-rdflib = ">=5.0,<6.0"
+rdflib = ">=6.0.0,<7.0.0"
tqdm = ">=4.52.0,<5.0.0"
[[package]]
-category = "main"
-description = "URI templates"
name = "uritemplate"
+version = "3.0.1"
+description = "URI templates"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-version = "3.0.1"
[[package]]
-category = "main"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
+version = "1.25.10"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-version = "1.25.10"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
-category = "dev"
-description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
optional = false
python-versions = "*"
-version = "0.2.5"
[[package]]
-category = "main"
-description = "The comprehensive WSGI web application library."
name = "werkzeug"
+version = "1.0.1"
+description = "The comprehensive WSGI web application library."
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-version = "1.0.1"
[package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"]
[[package]]
-category = "dev"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-marker = "python_version < \"3.8\""
name = "zipp"
+version = "3.1.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
optional = false
python-versions = ">=3.6"
-version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
-content-hash = "47ee2f390a3a95acf128cec725d6e84ec87d08a034727f312caafa0e4a7e90c4"
-lock-version = "1.0"
-python-versions = "^3.6"
+lock-version = "1.1"
+python-versions = "^3.7"
+content-hash = "f2fd6016a040de9f7098522f318a4159bc65a641080c3c3f691587b8a9fcb03b"
[metadata.files]
alembic = [
@@ -873,9 +879,9 @@ cffi = [
{file = "cffi-1.14.2-cp38-cp38-win_amd64.whl", hash = "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c"},
{file = "cffi-1.14.2.tar.gz", hash = "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b"},
]
-chardet = [
- {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
- {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
+charset-normalizer = [
+ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
@@ -964,6 +970,52 @@ googleapis-common-protos = [
{file = "googleapis-common-protos-1.52.0.tar.gz", hash = "sha256:560716c807117394da12cecb0a54da5a451b5cf9866f1d37e9a5e2329a665351"},
{file = "googleapis_common_protos-1.52.0-py2.py3-none-any.whl", hash = "sha256:c8961760f5aad9a711d37b675be103e0cc4e9a39327e0d6d857872f698403e24"},
]
+grpcio = [
+ {file = "grpcio-1.43.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:a4e786a8ee8b30b25d70ee52cda6d1dbba2a8ca2f1208d8e20ed8280774f15c8"},
+ {file = "grpcio-1.43.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:af9c3742f6c13575c0d4147a8454da0ff5308c4d9469462ff18402c6416942fe"},
+ {file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:fdac966699707b5554b815acc272d81e619dd0999f187cd52a61aef075f870ee"},
+ {file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e463b4aa0a6b31cf2e57c4abc1a1b53531a18a570baeed39d8d7b65deb16b7e"},
+ {file = "grpcio-1.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11d05402e0ac3a284443d8a432d3dfc76a6bd3f7b5858cddd75617af2d7bd9b"},
+ {file = "grpcio-1.43.0-cp310-cp310-win32.whl", hash = "sha256:c36f418c925a41fccada8f7ae9a3d3e227bfa837ddbfddd3d8b0ac252d12dda9"},
+ {file = "grpcio-1.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:772b943f34374744f70236bbbe0afe413ed80f9ae6303503f85e2b421d4bca92"},
+ {file = "grpcio-1.43.0-cp36-cp36m-linux_armv7l.whl", hash = "sha256:cbc9b83211d905859dcf234ad39d7193ff0f05bfc3269c364fb0d114ee71de59"},
+ {file = "grpcio-1.43.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:fb7229fa2a201a0c377ff3283174ec966da8f9fd7ffcc9a92f162d2e7fc9025b"},
+ {file = "grpcio-1.43.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:17b75f220ee6923338155b4fcef4c38802b9a57bc57d112c9599a13a03e99f8d"},
+ {file = "grpcio-1.43.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:6620a5b751b099b3b25553cfc03dfcd873cda06f9bb2ff7e9948ac7090e20f05"},
+ {file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_aarch64.whl", hash = "sha256:1898f999383baac5fcdbdef8ea5b1ef204f38dc211014eb6977ac6e55944d738"},
+ {file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47b6821238d8978014d23b1132713dac6c2d72cbb561cf257608b1673894f90a"},
+ {file = "grpcio-1.43.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80398e9fb598060fa41050d1220f5a2440fe74ff082c36dda41ac3215ebb5ddd"},
+ {file = "grpcio-1.43.0-cp36-cp36m-win32.whl", hash = "sha256:0110310eff07bb69782f53b7a947490268c4645de559034c43c0a635612e250f"},
+ {file = "grpcio-1.43.0-cp36-cp36m-win_amd64.whl", hash = "sha256:45401d00f2ee46bde75618bf33e9df960daa7980e6e0e7328047191918c98504"},
+ {file = "grpcio-1.43.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:af78ac55933811e6a25141336b1f2d5e0659c2f568d44d20539b273792563ca7"},
+ {file = "grpcio-1.43.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8b2b9dc4d7897566723b77422e11c009a0ebd397966b165b21b89a62891a9fdf"},
+ {file = "grpcio-1.43.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:77ef653f966934b3bfdd00e4f2064b68880eb40cf09b0b99edfa5ee22a44f559"},
+ {file = "grpcio-1.43.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e95b5d62ec26d0cd0b90c202d73e7cb927c369c3358e027225239a4e354967dc"},
+ {file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:04239e8f71db832c26bbbedb4537b37550a39d77681d748ab4678e58dd6455d6"},
+ {file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b4a7152187a49767a47d1413edde2304c96f41f7bc92cc512e230dfd0fba095"},
+ {file = "grpcio-1.43.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8cc936a29c65ab39714e1ba67a694c41218f98b6e2a64efb83f04d9abc4386b"},
+ {file = "grpcio-1.43.0-cp37-cp37m-win32.whl", hash = "sha256:577e024c8dd5f27cd98ba850bc4e890f07d4b5942e5bc059a3d88843a2f48f66"},
+ {file = "grpcio-1.43.0-cp37-cp37m-win_amd64.whl", hash = "sha256:138f57e3445d4a48d9a8a5af1538fdaafaa50a0a3c243f281d8df0edf221dc02"},
+ {file = "grpcio-1.43.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:08cf25f2936629db062aeddbb594bd76b3383ab0ede75ef0461a3b0bc3a2c150"},
+ {file = "grpcio-1.43.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:01f4b887ed703fe82ebe613e1d2dadea517891725e17e7a6134dcd00352bd28c"},
+ {file = "grpcio-1.43.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0aa8285f284338eb68962fe1a830291db06f366ea12f213399b520c062b01f65"},
+ {file = "grpcio-1.43.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:0edbfeb6729aa9da33ce7e28fb7703b3754934115454ae45e8cc1db601756fd3"},
+ {file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:c354017819201053d65212befd1dcb65c2d91b704d8977e696bae79c47cd2f82"},
+ {file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50cfb7e1067ee5e00b8ab100a6b7ea322d37ec6672c0455106520b5891c4b5f5"},
+ {file = "grpcio-1.43.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57f1aeb65ed17dfb2f6cd717cc109910fe395133af7257a9c729c0b9604eac10"},
+ {file = "grpcio-1.43.0-cp38-cp38-win32.whl", hash = "sha256:fa26a8bbb3fe57845acb1329ff700d5c7eaf06414c3e15f4cb8923f3a466ef64"},
+ {file = "grpcio-1.43.0-cp38-cp38-win_amd64.whl", hash = "sha256:ade8b79a6b6aea68adb9d4bfeba5d647667d842202c5d8f3ba37ac1dc8e5c09c"},
+ {file = "grpcio-1.43.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:124e718faf96fe44c98b05f3f475076be8b5198bb4c52a13208acf88a8548ba9"},
+ {file = "grpcio-1.43.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2f96142d0abc91290a63ba203f01649e498302b1b6007c67bad17f823ecde0cf"},
+ {file = "grpcio-1.43.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:31e6e489ccd8f08884b9349a39610982df48535881ec34f05a11c6e6b6ebf9d0"},
+ {file = "grpcio-1.43.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0e731f660e1e68238f56f4ce11156f02fd06dc58bc7834778d42c0081d4ef5ad"},
+ {file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:1f16725a320460435a8a5339d8b06c4e00d307ab5ad56746af2e22b5f9c50932"},
+ {file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b4543e13acb4806917d883d0f70f21ba93b29672ea81f4aaba14821aaf9bb0"},
+ {file = "grpcio-1.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:594aaa0469f4fca7773e80d8c27bf1298e7bbce5f6da0f084b07489a708f16ab"},
+ {file = "grpcio-1.43.0-cp39-cp39-win32.whl", hash = "sha256:5449ae564349e7a738b8c38583c0aad954b0d5d1dd3cea68953bfc32eaee11e3"},
+ {file = "grpcio-1.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:bdf41550815a831384d21a498b20597417fd31bd084deb17d31ceb39ad9acc79"},
+ {file = "grpcio-1.43.0.tar.gz", hash = "sha256:735d9a437c262ab039d02defddcb9f8f545d7009ae61c0114e19dda3843febe5"},
+]
httplib2 = [
{file = "httplib2-0.18.1-py3-none-any.whl", hash = "sha256:ca2914b015b6247791c4866782fa6042f495b94401a0f0bd3e1d6e0ba2236782"},
{file = "httplib2-0.18.1.tar.gz", hash = "sha256:8af66c1c52c7ffe1aa5dc4bcd7c769885254b0756e6e69f953c7f0ab49a70ba3"},
@@ -977,8 +1029,8 @@ importlib-metadata = [
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
]
isodate = [
- {file = "isodate-0.6.0-py2.py3-none-any.whl", hash = "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81"},
- {file = "isodate-0.6.0.tar.gz", hash = "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8"},
+ {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"},
+ {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"},
]
itsdangerous = [
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
@@ -1153,16 +1205,16 @@ pytz = [
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
]
rdflib = [
- {file = "rdflib-5.0.0-py3-none-any.whl", hash = "sha256:88208ea971a87886d60ae2b1a4b2cdc263527af0454c422118d43fe64b357877"},
- {file = "rdflib-5.0.0.tar.gz", hash = "sha256:78149dd49d385efec3b3adfbd61c87afaf1281c30d3fcaf1b323b34f603fb155"},
+ {file = "rdflib-6.1.1-py3-none-any.whl", hash = "sha256:fc81cef513cd552d471f2926141396b633207109d0154c8e77926222c70367fe"},
+ {file = "rdflib-6.1.1.tar.gz", hash = "sha256:8dbfa0af2990b98471dacbc936d6494c997ede92fd8ed693fb84ee700ef6f754"},
]
redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
{file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
]
requests = [
- {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
- {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
+ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
rsa = [
{file = "rsa-4.6-py3-none-any.whl", hash = "sha256:6166864e23d6b5195a5cfed6cd9fed0fe774e226d8f854fcb23b7bbef0350233"},
@@ -1172,6 +1224,13 @@ six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
+sparqlwrapper = [
+ {file = "SPARQLWrapper-1.8.5-py2-none-any.whl", hash = "sha256:357ee8a27bc910ea13d77836dbddd0b914991495b8cc1bf70676578155e962a8"},
+ {file = "SPARQLWrapper-1.8.5-py2.7.egg", hash = "sha256:17ec44b08b8ae2888c801066249f74fe328eec25d90203ce7eadaf82e64484c7"},
+ {file = "SPARQLWrapper-1.8.5-py3-none-any.whl", hash = "sha256:c7f9c9d8ebb13428771bc3b6dee54197422507dcc3dea34e30d5dcfc53478dec"},
+ {file = "SPARQLWrapper-1.8.5-py3.4.egg", hash = "sha256:8cf6c21126ed76edc85c5c232fd6f77b9f61f8ad1db90a7147cdde2104aff145"},
+ {file = "SPARQLWrapper-1.8.5.tar.gz", hash = "sha256:d6a66b5b8cda141660e07aeb00472db077a98d22cb588c973209c7336850fb3c"},
+]
sqlalchemy = [
{file = "SQLAlchemy-1.3.19-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc"},
{file = "SQLAlchemy-1.3.19-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36"},
@@ -1203,12 +1262,12 @@ sqlalchemy = [
{file = "SQLAlchemy-1.3.19.tar.gz", hash = "sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e"},
]
tqdm = [
- {file = "tqdm-4.56.0-py2.py3-none-any.whl", hash = "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a"},
- {file = "tqdm-4.56.0.tar.gz", hash = "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"},
+ {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"},
+ {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"},
]
umakaparser = [
- {file = "umakaparser-0.1.1-py2.py3-none-any.whl", hash = "sha256:90e7be3ea1721beddddc11287aa4a57ce844ab8f05d1733e11293d0d6e6ff6df"},
- {file = "umakaparser-0.1.1.tar.gz", hash = "sha256:11c05a9a03d472574a3590bb6e9a01a854e47cbdcf582aeb4baa59d495197ec0"},
+ {file = "umakaparser-0.1.7-py3-none-any.whl", hash = "sha256:670638655335544438bd8b5ea3d907b8818f6826cbb82744a2e8dea1daf4ee00"},
+ {file = "umakaparser-0.1.7.tar.gz", hash = "sha256:35cd8f41561add6c939f91d3c11b2377789fefe7c3cc798669600243ce211042"},
]
uritemplate = [
{file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
diff --git a/server/pyproject.toml b/server/pyproject.toml
index 0a6772ce..412307f6 100644
--- a/server/pyproject.toml
+++ b/server/pyproject.toml
@@ -5,7 +5,7 @@ description = ""
authors = ["Your Name "]
[tool.poetry.dependencies]
-python = "^3.6"
+python = "^3.7"
flask = "^1.1"
SQLAlchemy = "^1.3"
Flask-SQLAlchemy = "^2.4"
@@ -17,7 +17,8 @@ flask-cors = "^3.0"
pytz = "^2019.2"
redis = "^3.3"
PyMySQL = "^0.10.1"
-umakaparser = "^0.1.1"
+umakaparser = "^0.1.7"
+SPARQLWrapper = "^1.8.5"
[tool.poetry.dev-dependencies]
pytest = "^5.1"