From 317ba0a095ab9c4ce86b21b99de0de4519311ab7 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Thu, 28 Oct 2021 12:11:51 +0200 Subject: [PATCH 1/2] vue_visgraph: store missing persons from courses, household and relationship --- .../Resources/public/vuejs/VisGraph/App.vue | 10 +-- .../Resources/public/vuejs/VisGraph/store.js | 83 +++++++++++-------- .../public/vuejs/VisGraph/vis-network.js | 8 +- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index 1804460f7..74409c7f1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -36,13 +36,9 @@ export default { }, computed: { ...mapGetters(['nodes', 'edges']), - ...mapState(['households', 'courses', 'excludedNodesIds' - //'persons', - //'links, - //'relationships', - //'householdLoadingIds', - //'courseLoadedIds', - //'relationshipLoadedIds', + ...mapState(['households', 'courses', 'excludedNodesIds', + // not used + 'persons', 'links', 'relationships', 'personLoadedIds', 'householdLoadingIds', 'courseLoadedIds', 'relationshipLoadedIds', ]), visgraph_data() { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js index da13d9bd3..c18323684 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js @@ -13,6 +13,7 @@ const store = createStore({ courses: [], relationships: [], links: [], + personLoadedIds: [], householdLoadingIds: [], courseLoadedIds: [], relationshipLoadedIds: [], @@ -41,9 +42,6 @@ const store = createStore({ state.links.forEach(l => { edges.push(l) }) - //state.relationships.forEach(r => { - // edges.push(r) - //}) return edges }, isHouseholdLoading: (state) => (household_id) => { @@ -54,6 +52,9 @@ const store = createStore({ }, isRelationshipLoaded: (state) => (relationship_id) => { return state.relationshipLoadedIds.includes(relationship_id) + }, + isPersonLoaded: (state) => (person_id) => { + return state.personLoadedIds.includes(person_id) } }, mutations: { @@ -77,6 +78,12 @@ const store = createStore({ console.log('+ addLink from', link.from, 'to', link.to) state.links.push(link) }, + markPersonLoaded(state, id) { + state.personLoadedIds.push(id) + }, + unmarkPersonLoaded(state, id) { + state.personLoadedIds = state.personLoadedIds.filter(i => i !== id) + }, markHouseholdLoading(state, id) { console.log('..loading household', id) state.householdLoadingIds.push(id) @@ -108,7 +115,9 @@ const store = createStore({ * 1) Add a person in state * @param Person person */ + addPerson({ commit, dispatch }, person) { + commit('markPersonLoaded', person.id) commit('addPerson', person) dispatch('fetchInfoForPerson', person) }, @@ -149,7 +158,7 @@ const store = createStore({ * 4) Add an edge for each household member (household -> person) * @param Household household */ - addLinkFromPersonsToHousehold({ commit }, household) { + addLinkFromPersonsToHousehold({ commit, getters, dispatch }, household) { const currentMembers = household.members.filter(v => household.current_members_id.includes(v.id)) currentMembers.forEach(m => { //console.log('-> addLink from person', m.person.id, 'to household', m.person.current_household_id) @@ -163,30 +172,32 @@ const store = createStore({ label: getHouseholdLabel(m), width: getHouseholdWidth(m), }) + if (!getters.isPersonLoaded(m.person.id)) { + console.log(' person is not loaded', m.person.id) + dispatch('addMissingPerson', m.person) + } }) - }, /** * 5) Fetch AccompanyingCourses for the person * @param Person person */ - fetchCoursesByPerson({ dispatch }, person) { + fetchCoursesByPerson({ commit, dispatch }, person) { //console.log('fetchCoursesByPerson', person) getCoursesByPerson(person) .then(courses => new Promise(resolve => { console.log('fetch courses', courses.length) - dispatch('addCourse', courses) + dispatch('addCourses', courses) resolve() })) - }, /** * 6) Add each distinct course * @param array courses */ - addCourse({ commit, getters, dispatch }, courses) { + addCourses({ commit, getters, dispatch }, courses) { //console.log('addCourse', courses) let currentCourses = courses.filter(c => c.closingDate === null) currentCourses.forEach(course => { @@ -204,7 +215,7 @@ const store = createStore({ * 7) Add an edge for each course participation (course <- person) * @param AccompanyingCourse course */ - addLinkFromPersonsToCourse({ commit }, course) { + addLinkFromPersonsToCourse({ commit, getters, dispatch }, course) { let currentParticipations = course.participations.filter(p => p.endDate === null) console.log(' participations', currentParticipations.length) currentParticipations.forEach(p => { @@ -217,6 +228,10 @@ const store = createStore({ font: { color: 'darkorange' }, label: visMessages.fr.visgraph.concerned, }) + if (!getters.isPersonLoaded(p.person.id)) { + console.log(' person is not loaded', p.person.id) + dispatch('addMissingPerson', p.person) + } }) }, @@ -229,7 +244,7 @@ const store = createStore({ getRelationshipsByPerson(person) .then(relationships => new Promise(resolve => { console.log('fetch relationships', relationships.length) - dispatch('addRelationship', relationships) + dispatch('addRelationships', relationships) resolve() })) }, @@ -239,7 +254,7 @@ const store = createStore({ * 9) Add each distinct relationship * @param array relationships */ - addRelationship({ commit, getters, dispatch }, relationships) { + addRelationships({ commit, getters, dispatch }, relationships) { relationships.forEach(relationship => { console.log(' isRelationshipLoaded ?', getters.isRelationshipLoaded(relationship.id)) if (! getters.isRelationshipLoaded(relationship.id)) { @@ -253,40 +268,36 @@ const store = createStore({ /** * 10) Add an edge for each relationship (person -> person) - * @param Relationship relationship + * @param Relationship r */ - addLinkFromRelationship({ commit }, r) { - - console.log( - '-> addLink from person', r.fromPerson.id, 'to person', r.toPerson.id, - //'reverse', r.reverse, - //'relation', r.relation.title.fr, r.relation.reverseTitle.fr - ) - + addLinkFromRelationship({ commit, getters, dispatch }, r) { + //console.log('-> addLink from person', r.fromPerson.id, 'to person', r.toPerson.id) commit('addLink', { from: `person_${r.fromPerson.id}`, to: `person_${r.toPerson.id}`, id: 'r' + r.id + '_p' + r.fromPerson.id + '_p' + r.toPerson.id, - arrows: 'from', + arrows: 'to', color: 'lightblue', dashes: true, - font: { color: 'lightblue' }, + font: { color: '#33839d' }, label: getRelationshipLabel(r, false), }) - - /* - // add reverse relation - commit('addLink', { - from: `person_${r.toPerson.id}`, - to: `person_${r.fromPerson.id}`, - id: 'rr' + r.id + '_p' + r.fromPerson.id + '_p' + r.toPerson.id, - arrows: 'from', - color: 'lightblue', dashes: true, - font: { color: 'lightblue' }, - label: getRelationshipLabel(r, true), - }) - */ + for (let person of [r.fromPerson, r.toPerson]) { + if (!getters.isPersonLoaded(person.id)) { + console.log(' person is not loaded', person.id) + dispatch('addMissingPerson', person) + } + } }, + /** + * Fetch missing person + * @param Person person + */ + addMissingPerson({ commit, getters, dispatch }, person) { + //console.log('addMissingPerson', person) + commit('markPersonLoaded', person.id) + commit('addPerson', person) + }, } }) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js index 3ed6a7e7a..f1e9ab05e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js @@ -167,7 +167,11 @@ const getHouseholdLabel = (member) => { /** * Return edge width for member (depends of position in household) * @param member - * @returns string + * @returns integer (width) + * + * TODO + * to use: holder, shareHousehold + * not use: ordering, position (-> null) */ const getHouseholdWidth = (member) => { switch (member.position.ordering) { @@ -179,6 +183,8 @@ const getHouseholdWidth = (member) => { case 2: //children case 3: //children out of household return 1 + default: + throw 'Ordering not supported' } } From 998295dc5fb5004ca80e13581cefc10b87d49735 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Thu, 28 Oct 2021 15:47:40 +0200 Subject: [PATCH 2/2] vue_visgraph improve (physics, corrections) --- .../Resources/public/vuejs/VisGraph/App.vue | 1 + .../Resources/public/vuejs/VisGraph/store.js | 2 +- .../public/vuejs/VisGraph/vis-network.js | 79 +++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index 74409c7f1..fd45dd750 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -96,6 +96,7 @@ export default { }, toggleLayer(value) { //console.log('toggleLayer') + this.forceUpdateComponent() let id = value.target.value if (this.checkedLayers.includes(id)) { this.removeLayer(id) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js index c18323684..394fd55e6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js @@ -223,7 +223,7 @@ const store = createStore({ from: `${p.person.type}_${p.person.id}`, to: `${course.id}`, id: `p${p.person.id}-c`+ course.id.split('_')[2], - arrows: 'to', + arrows: 'from', color: 'orange', font: { color: 'darkorange' }, label: visMessages.fr.visgraph.concerned, diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js index f1e9ab05e..eeddb7da4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/vis-network.js @@ -20,6 +20,55 @@ window.options = { showButton: true }, */ + physics:{ + enabled: true, + barnesHut: { + theta: 0.5, + gravitationalConstant: -2000, + centralGravity: 0.1, //// 0.3 + springLength: 200, //// 95 + springConstant: 0.04, + damping: 0.09, + avoidOverlap: 0 + }, + forceAtlas2Based: { + theta: 0.5, + gravitationalConstant: -50, + centralGravity: 0.01, + springConstant: 0.08, + springLength: 100, + damping: 0.4, + avoidOverlap: 0 + }, + repulsion: { + centralGravity: 0.2, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 + }, + hierarchicalRepulsion: { + centralGravity: 0.0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 120, + damping: 0.09, + avoidOverlap: 0 + }, + maxVelocity: 50, + minVelocity: 0.1, + solver: 'barnesHut', + stabilization: { + enabled: true, + iterations: 1000, + updateInterval: 100, + onlyDynamicEdges: false, + fit: true + }, + timestep: 0.5, + adaptiveTimestep: true, + wind: { x: 0, y: 0 } + }, manipulation: { enabled: true, initiallyActive: true, @@ -66,12 +115,12 @@ window.options = { } }, nodes: { - physics: true, + //physics: true, borderWidth: 1, borderWidthSelected: 3, }, edges: { - physics: true, + //physics: true, font: { color: '#b0b0b0', size: 9, @@ -130,7 +179,7 @@ const adapt2vis = (entity) => { switch (entity.type) { case 'person': entity._id = entity.id - entity.label = entity.text + entity.label = `${entity.text}\n` + getGender(entity.gender_numeric) +' - '+ getAge(entity.birthdate) entity.id = `person_${entity.id}` break case 'household': @@ -146,13 +195,31 @@ const adapt2vis = (entity) => { case 'relationship': entity._id = entity.id entity.id = `relationship_${entity.id}` - - // sera utilisé pour les links : - //entity.id = 'r' + entity._id + '_p' + entity.fromPerson.id + '_p' + entity.toPerson.id } return entity } +const getGender = (gender) => { + switch (gender) { + case 0: + return 'N' + case 1: + return 'M' + case 2: + return 'F' + default: + throw 'gender undefined' + } +} +const getAge = (birthdate) => { + if (null === birthdate) { + return null + } + const birthday = new Date(birthdate.datetime) + const now = new Date() + return (now.getFullYear() - birthday.getFullYear()) + ' ans' +} + /** * Return member position in household * @param member